Introduction
A common problem when you need to manage web app deployment is to take care of breaking changes in Your JavaScript code and CSS files today contain a increasingly fundamental part of the application logic and you can be sure that different devices get updated at first time without user interaction like manual refresh page or clear browser cache.
The mission is to generate JavaScript and CSS resourse URL dynamically with a parameter that forces the browser to load the new version of the resourse and not use its cached version.
The URL should look like this: http://mywebsite.com/scripts/myjavascript.js?v=1.1.2.2
In this case you love even more MVC flexibility and with few line of code you can solve problem in new application and also with already in production application.
Using the code
First of all you must understand how to extend MVC view engine, in this example I explain how to do that with Razor Engine in C# language.
Create two classes name WebViewPageEx
and WebViewPageEx<T>
that extend standard WebViewPage.
The first one is used by MVC for View without a model class, the second is use in case of a specified model class.
public abstract class WebViewPageEx : WebViewPage
{
#region Properties
public new UrlHelperEx Url
{
get;
set;
}
#endregion
#region Public methods
public override void InitHelpers()
{
base.InitHelpers();
Url = new UrlHelperEx(ViewContext.RequestContext);
}
#endregion
}
public abstract class WebViewPageEx<T> : WebViewPage
{
#region Properties
public new UrlHelperEx Url
{
get;
set;
}
#endregion
#region Public methods
public override void InitHelpers()
{
base.InitHelpers();
Url = new UrlHelperEx(ViewContext.RequestContext);
}
#endregion
}
Now second step is extend the standard Url.Content helper method behaviour with a class UrlHelperEx that extend UrlHelper.
This add a new versione of Content with an optional parameter that return an URL that force browser to update that resource.
public class UrlHelperEx : UrlHelper
{
#region Constants
private const string c_VERSION_FORMAT = "{0}?v={1}";
#endregion
#region Initialization
public UrlHelperEx(RequestContext requestContext)
: base(requestContext)
{
}
#endregion
#region Public methods
public string Content(string contentPath,bool forceupdate=false)
{
var content = base.Content(contentPath);
if (!forceupdate) {
return content.ToString();
}
else
{
Version version = WebHelper.GetApplicationVersion(this.RequestContext.HttpContext);
return string.Format(c_VERSION_FORMAT, content
, version.ToString());
}
}
#endregion
}
In this example I use Assembly version as variable to force update, but you could use everything you want.
To get the application assembly version number I use a static class WebHelper that look like this:
using System;
using System.Web;
public static class WebHelper
{
public static Version GetApplicationVersion(this HttpContextBase context)
{
var appInstance = context.ApplicationInstance;
var assemblyVersion = appInstance.GetType().BaseType.Assembly.GetName().Version;
return assemblyVersion;
}
}
Now you need to change the common base class of every page and add a reference to namespace when UrlContentEx was placed, do that by changin the web.config file inside the Views folder like this:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="ContentHelper.Helpers.WebViewPageEx">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="ContentHelper.Helpers"/>
</namespaces>
</pages>
</system.web.webPages.razor>
Finally you can change Url.Content
with a call to the new method with the force update parameter set to true.
In this example only myapp.min.js and site.css force update.
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css",true)" 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>
<script src="@Url.Content("~/Scripts/myapp.min.js",true)" type="text/javascript"></script>
</head>
When you run application browse the source code and you obtain this:
<head>
<meta charset="utf-8" />
<title>Home Page</title>
<link href="http://www.codeproject.com/Content/Site.css?v=1.0.0.0" rel="stylesheet" type="text/css" />
<script src="http://www.codeproject.com/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="http://www.codeproject.com/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
<script src="http://www.codeproject.com/Scripts/myapp.min.js?v=1.0.0.0" type="text/javascript"></script>
</head>
Points of Interest
With this few line of code you get the mission accomplished and you will never have problem caused by browser chache our JavaScript or CSS files.
This solution is easy and simple to develop and also is compatible with pages already in production.