Thursday, May 24, 2012

Versioning Static Web Content (css & js) in Asp.Net

Often when building or maintaining a web application the supposedly static content turns out not to be that static in real life. The trouble is that is you have applied an aggressive caching policy to improve client perceived performance by marking the content never expiring, any changed static content will never be pulled down by the browser.

The end user can force the browser to re-download the content, and this is something we all as web developers find ourselves doing several times a day, by using the Ctrl+F5 keys. This will force the browser to re-download all items, however is a standard end user going to know to do this? I think not, and more importantly they shouldn’t have to!

Possible Solutions

While googling for methods to achieve this for a project at work, it became apparent that there are two main possibilities within the Asp.Net framework, one of which being much simpler to achieve. Firstly you could employ the url rewriting module to re-write the urls to include a version string. The new url will fool the browser to request the file when the version changes, however this is going to take a hit for every request to actually perform the re-writing.

A simpler, and as it turns out widely used method (stackoverflow being one example), is to just append a version query-string to the end of the url. Within the Asp.Net MVC framework we have the Url.Content extension that will resolve the provided address to a true absolute address. It is possible to write our own method to perform the same function but also append the version string.

Appending a version string

So we can define our own class with our implementation of the Content method. I have chosen to change the name of the method to VersionedContent, to better describe the purpose of the method; and we should continue to use the existing Content method where we do not require the versioning behaviour, such as when referencing third part libraries that already contain versions within their filenames.

   1:  public static class UrlHelperExtensions
   2:  {
   3:      private static string _versionQueryString;
   4:      
   5:      static UrlHelperExtensions()
   6:      {
   7:          Assembly asembly = Assembly.GetExecutingAssembly();
   8:          AssemblyName name = assembly.GetName();
   9:          _versionQueryString = "?v=" + name.Version.GetHashCode().ToString();
  10:      }
  11:      
  12:      public static string VersionedContent(this UrlHelper urlHelper, string url)
  13:      {
  14:          return UrlHelper = urlHelper.Content(url) + _versionQueryString;
  15:      }
  16:  }



Then in the page/view we just need to include the relevent namespace and replace the use of Url.Content with Url.VersionedContent.


Razor View Engine (MVC)


   1:  <link type="text/css" rel="styleSheet" href="@Url.Content("~/content/css/style-base.css")" />

 


Standard View Engine and Regular Aspx Pages


   1:  <link type="text/css" rel="styleSheet" href="<%=Url.Content("~/content/css/style-base.css")%>" />



 


Summary


So as you can see it is actually a simple change to your views to ensure that the static content can be versioned. These links can also be applied to other forms of static content such as images, however images generally do stay more static than the js and css files, so generally this is not required.


One thing to note is that the method of generating the version string based on the version of the assembly is only going to work if the version of the assembly is being changed! I would strongly recommend that you define a version numbering scheme ensure that the version number is changed each time the site is changed.

Friday, May 4, 2012

Strongly Typing Asp.Net MVC Master/Layout Pages

When developing Asp.Net MVC pages it is best practice to strongly type the views so that data passed from the controller can be accessed in a controlled and safe manner.

Strongly Typing Views

This is achieved in a couple of ways depending on which view engine is being used. When adding the view it is possible to choose the data type of the model that is to be used within the view.

Aspx view engine

If you choose to use the aspx view engine then the model class can be selected in the drop down and this will generate the correct markup to strongly type the model to the selected class.

image

The standard aspx view engine employs the markup style notation for defining the type of the Model.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/View.Master" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.CustomerViewModel>" %>

Razor view engine


The razor view engine employs a much cleaner approach to typing the model. Again the class for the model can be selected in the drop down in the Add View dialog, like the one shown below.


image


This will generate code similar to that shown below, that uses the new @model razor notation to type the view to the specified class.

@model MvcApplication1.Models.CustomerViewModel

Master / Layout pages


There are a number of reasons that you may want to strongly type the model on the master page, as doing this is preferable to using TempData or ViewData directly. Depending on the model semantics that you are building it may be that all pages need to build a top level menu, however this is built from dynamic data. In this case it would make sense to make this data available in the model, and also add the code to generate the menu to the layout or master page, as it is not desirable to replicate this on multiple pages.


Anyone that has created a new layout / master page will realise that when adding a layout or master view you are not asked about strongly-typing the view. However just because you are not asked doesn’t mean that it isn’t possible!


The usual method of adding a new layout or master page is to choose the add new item, menu item and this will allow you to choose the type of view to add, and generate the view from the visual studio file template.


With a couple of tweaks to the generated code it is possible to strongly-type the master/layout page and get all the benefits that this brings.


Standard inheritance rules apply here!


As long as you follow basic inheritance rules strongly typing the model on the master/layout page is perfectly safe.


The thing to remember is that all models used on views that use the master/layout page must derive from the model defined on the master/layout page. Obvious really isn’t it!


So for example using the following classes


image


It is possible to strongly type the master/layout views to use the BaseViewModel class. Allowing the base classes Message property to be used directly within the master/layout page.


Aspx view engine


Utilize the System.Web.Mvc.ViewMasterPage<T> generic base class to specify the type of the model.

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<MvcApplication1.Models.BaseViewModel>" %>
<!DOCTYPE html>
<
html>
<
head runat="server">
<
title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
</
head>
<
body>
<
div>
<
asp:ContentPlaceHolder ID="MainContent" runat="server">
<%: Model.Message %>
</asp:ContentPlaceHolder>
</
div>
</
body>
</
html>

Razor view engine


Utilize the same @model notation as used in the view page to specify the type of the model.

@model MvcApplication1.Models.BaseViewModel
<!DOCTYPE html>
<
html>
<
head>
<
meta charset="utf-8" />
<
title>@ViewBag.Title</title>
<
link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<
script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<
script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</
head>
<
body>
<
div class="message">
<
p>@Model.Message</p>
</
div>
@RenderBody()
</body>
</
html>

Summary


That's all there is to it! Have fun changing all your master/layout pages to be strongly typed! Obviously if the layout/master page is only being used for layout then there is no benefit in doing this. However with the real life pages that I have experience in building the master/layout pages nearly always need so data which could be on the model.