web 2.0

Prevent .js caching in asp.net

This is like a very common issue, specially for those who are working on public site which is live and they have to release the builds every week or month and if the new build contain JS files then your change will not reflect on the client browser until someone there presses ctrl + F5.So, after googling this issue. I came to know it is possible to prevent the browser from accessing the cache copy by writing the script tag as below
   1: <script type="text/javascript" src="../Includes/main.js?random=556"></script>
 It is good, when I have a single or some number of pages to change. Unfortunately, That is not the case I have hundreds of pages and that will be hassle to make those changes again and again for every build. So, I try to make this thing happen using Response Filter.

Create Response Filter:

So, to write the Response Filter we need to create a class and which is extended from Stream (System.IO.Stream) and I named it BuildTokenFilter.
   1: Imports Microsoft.VisualBasic
   2: Imports System.IO
   3: Imports System.Text.RegularExpressions
   4: Imports System.Configuration
   5: 
   6: 
   7: Public Class BuildTokenFilter
   8:     Inherits Stream
   9:     Private _responseStream As Stream
  10:     Public Sub New(ByVal responseStream As Stream)
  11:         _responseStream = responseStream
  12:     End Sub
  13:     Public Overrides ReadOnly Property CanRead() As Boolean
  14:         Get
  15:             Return _responseStream.CanRead
  16:         End Get
  17:     End Property
  18:     Public Overrides ReadOnly Property CanSeek() As Boolean
  19:         Get
  20:             Return _responseStream.CanSeek
  21:         End Get
  22:     End Property
  23: 
  24:     Public Overrides ReadOnly Property CanWrite() As Boolean
  25:         Get
  26:             Return _responseStream.CanWrite
  27:         End Get
  28:     End Property
  29: 
  30:     Public Overrides Sub Flush()
  31:         _responseStream.Flush()
  32:     End Sub
  33: 
  34:     Public Overrides ReadOnly Property Length() As Long
  35:         Get
  36:             Return _responseStream.Length
  37:         End Get
  38:     End Property
  39: 
  40:     Public Overrides Property Position() As Long
  41:         Get
  42:             Return _responseStream.Position
  43:         End Get
  44:         Set(ByVal value As Long)
  45:             _responseStream.Position = value
  46:         End Set
  47:     End Property
  48: 
  49:     Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer
  50:         Return _responseStream.Read(buffer, offset, count)
  51:     End Function
  52:     Public Overrides Function Seek(ByVal offset As Long, ByVal origin As System.IO.SeekOrigin) As Long
  53:         _responseStream.Seek(offset, origin)
  54:     End Function
  55: 
  56:     Public Overrides Sub SetLength(ByVal value As Long)
  57:         _responseStream.SetLength(value)
  58:     End Sub
  59:     Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)
  60:         Dim strRegex As String = "src=(?<Link>.*js)"
  61:         Dim BuildTokenString As String = "?token=" & IIf(ConfigurationManager.AppSettings("BuildToken") = Nothing, "1.0", ConfigurationManager.AppSettings("BuildToken"))
  62:         Dim objRegex As New Regex(strRegex)
  63:         Dim html As String = System.Text.Encoding.UTF8.GetString(buffer)
  64:         Dim extCharCount As Integer = 0
  65: 
  66:         Dim objCol As MatchCollection = objRegex.Matches(html)
  67:         For Each m As Match In objCol
  68:             extCharCount += BuildTokenString.Length
  69:             Dim newJSValue As String = m.Value & BuildTokenString
  70:             html = html.Replace(m.Value, newJSValue)
  71:         Next
  72:         buffer = System.Text.Encoding.UTF8.GetBytes(html)
  73:         _responseStream.Write(buffer, offset, count + extCharCount)
  74:     End Sub
  75: End Class
"Write" is the function which is doing the whole stuff. It is really simple to understand. Let me go linewise.
60. Create a string variable and assign and regular expression which will search all the js files tags in html61. Create a string which will append to the JS file. Checking the App key in web.config So that the token can be extended in future.62. Create a Regex Object63. Get the html from Buffer64. Create an integer variable which will keep track of the characters that are added to the html variable. 66. Save the matches in a collection67. Get each mach object from Match Collection68. Add the character count which will added to the html variable69. Create a variable and assign value of match and concatenated with build token70. Replace the old value with the new with build token in html variable72. Encode the html variable back to the buffer73. Write the buffer to the stream by adding count with build token character count.
Now lets create a HttpModule which will responsible of attaching this response filter with every request.

Create HttpModule:

Now to write HttpModule, I will create a class which will Implements IHttpModule (interface) and I name it "BuildToken"/
   1: Imports Microsoft.VisualBasic
   2: Imports System.Web
   3: Public Class BuildToken
   4:     Implements IHttpModule
   5:     Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
   6: 
   7:     End Sub
   8:     Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
   9:         AddHandler context.BeginRequest, AddressOf Application_BeginRequest
  10:     End Sub
  11:     Private Sub Application_BeginRequest(ByVal source As Object, ByVal e As EventArgs)
  12:         Dim context As HttpApplication = CType(source, HttpApplication)
  13:         If context.Request.RawUrl.Contains(".aspx") = True Then
  14:             context.Response.Filter = New BuildTokenFilter(context.Response.Filter)
  15:         End If
  16:     End Sub
  17: End Class
 In this case the magical function is Application_BeginRequest which can easily be understand but the question might arises why I have put the If condition on line number 13. Well, I don't want my module to attach response filter against all the files. Keep in mind, HttpModule is always called no matter what content type are you requesting. when somebody write http://www.sitename.net/images/border2.jpg it will still process through HttpModule and that is what I don't want.

Configuration Setting:

We are almost done, just a web.config entry is left which we keep for the modification of token string.
   1: <appSettings>
   2:     <add key="BuildToken" value="7.0"/>
   3: </appSettings>
 

Test the filter:

Now to check the the response filter create a page that is called default.aspx and paste the following markup
   1: <html xmlns="http://www.w3.org/1999/xhtml" >
   2: <head runat="server">
   3:     <title>Untitled Page</title>
   4:     <script type="text/javascript" src="../Script/samain.js"></script>
   1: 
   2:     <script type="text/javascript" src="../ControlsScripts/preloadshare.js">
   1: </script>
   2:     <script type="text/javascript" src="../Script/mainview.js">
</script>
   5: </head>
   6: <body>
   7:     <form id="form1" runat="server">
   8:     <div>
   9:     <p>
  10:     Right click and view source you will find JS files with query string</p>
  11:     </div>
  12:     </form>
  13: </body>
  14: </html>
Now Run the application and view source the page you will notice some thing like give below.sc_responsefiltercacheEach time, when you made some changes in JS file just change Build Token entry in the web.config file and your visitors will get the updated copy of JS. You can also download the project files.

Comments

Marc , on 2/15/2009 8:01:20 PM Said:

Marc

Ahmed,

Great example, just a little modification to prevent the script tag for WCF service: )
to become:


I would recommend to change the strRegex to:

"src=(?.*([.]js))"

Thank you.

Marc

Mark , on 3/7/2009 5:52:28 PM Said:

Mark

What happens if you haven't changed all of the javascript files, and have just made a change to one of them? The client will be forced to request files that it could have just used from the cache making the page load slower.

Rick Strahl , on 3/7/2009 6:02:46 PM Said:

Rick Strahl

This is one reason why many libraries ship their .js files with specific version numbers for each release which seems annoying at first, but makes sense in hindsight.

webbes , on 3/7/2009 6:04:31 PM Said:

webbes

Maybe it's just me, but if you can create a module dat uses a regex to replace some values, you can use a console app to replace that value in your sourcefiles just as well. This in turn would be a lot more efficient.

Instead of creating a module that does the heavy work of regex matching and string replacement you could also create a WebControl and instead of a  tag add this webcontrol in your page head. The webcontrol in turn can get a build number from the assembly and automagically append that built number to your querystring.

You should also consider a redisign of your application if you have a lot of code duplication. Maybe you could use a masterpage where you place the  tag just once.

Response Filters are pretty heavy and quite errorprone so I'm not really fond of them... as you probably know by now Laughing

Bryan Migliorisi , on 3/7/2009 6:09:51 PM Said:

Bryan Migliorisi

Seems like a lot of extra processing.  My method is to store an application setting in web.config and to simply output that variable on the page.  Since its from web.config you can easily update the value when you update the scripts.

I usually create a static readonly string who's value is that of the application setting key found in the web.config file.  Then I can access the value from anywhere in the application like so:

myNamespace.settings.settingName

then I just have a normal script tag that writes out the variable above as part of the script src value.

This is for performance reasons - we do not want to process the html with regex on every single request.  This is sure to cause performance hiccups.  Instead, we store the value in a readonly static string that we can access from anywhere in the application without needing to instantiate any additional objects.

admin , on 3/7/2009 6:16:32 PM Said:

admin

@Mark

What about if you skip the token logic, and place the JS file last modified date in query string for each JS. In this way the browser will download only the file which is modified.

@webbes

Totally agree with you, Masterpages are the very easy and effective way of doing this but changing the source code from console application will not that feasible, I mean altering the source code from application does not make sense to me, what if your project is under source control like TFS are you going to check out all the source code ?

@Bryan Migliorisi

I thought I have learned many ways from you guys. But the matter of fact is I had to implement the way I describe because I have 400+ pages and hundreds of JS file with hundreds of controls without master pages and the best part is I had to release the build just with in 2 days. So that's the quickest method as I see.

INSHALLAH, I will write detailed stuff on this subject by implementing your suggestions Smile

Anyway, Thanks alot for the feedback

webbes , on 3/7/2009 6:20:06 PM Said:

webbes

Sometimes you just have to come up with a pragmatic solution like you did.

Cheers,

Wes

black diamond earrings United States, on 10/31/2009 7:19:17 PM Said:

black diamond earrings

You really make it seem so easy with your presentation but I find this topic to be really something which I think I would never understand. It seems too complicated and very broad for me. I am looking forward for your next post, I will try to get the hang of it!

gold buyers Houston United States, on 11/1/2009 8:57:21 PM Said:

gold buyers Houston

Excellent read, I just passed this onto a colleague who was doing a little research on that. And he actually bought me lunch because I found it for him smile So let me rephrase that: Thanks for lunch!

self defense techniques United States, on 11/2/2009 12:46:13 AM Said:

self defense techniques

Dude.. I am not much into reading, but somehow I got to read lots of articles on your blog. Its amazing how interesting it is for me to visit you very often.

Alaskan Smoked Salmon Recipes United States, on 11/2/2009 1:20:12 AM Said:

Alaskan Smoked Salmon Recipes

Well, this is my first visit to your blog! We are a group of volunteers and starting a new initiative in a community in the same niche. Your blog provided us valuable information to work on. You have done a marvellous job!

Linda Mirano United States, on 11/10/2009 8:24:53 PM Said:

Linda Mirano

The blog was absolutely fantastic! Lots of great information and inspiration, both of which we all need!

Linda Mirano United States, on 11/10/2009 8:32:39 PM Said:

Linda Mirano

Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon.

Rocket Italian United States, on 11/12/2009 7:22:52 PM Said:

Rocket Italian

You got a really useful blog I have been here reading for about an hour. I am a newbie and your success is very much an inspiration for me.

florist in singapore United States, on 11/14/2009 1:23:17 AM Said:

florist in singapore

Valuable information and excellent design you got here! I would like to thank you for sharing your thoughts and time into the stuff you post!! Thumbs up

Window Cleaning United States, on 11/18/2009 1:49:02 AM Said:

Window Cleaning

Thank you for the sensible critique. Me & my neighbour were preparing to do some research about that. We got a good book on that matter from our local library and most books where not as influensive as your information. I am very glad to see such information which I was searching for a long time.This made very glad Smile

Acai Berry United States, on 11/23/2009 7:00:18 PM Said:

Acai Berry

Pretty Interesting post. Couldnt be written any better. Reading this post reminds me of my old room mate! He always kept talking about this. I will forward this post to him. Pretty sure he will have a good read. Thanks for sharing!

Acai Berry United States, on 11/23/2009 9:10:28 PM Said:

Acai Berry

Pretty Interesting post. Couldnt be written any better. Reading this post reminds me of my old room mate! He always kept talking about this. I will forward this post to him. Pretty sure he will have a good read. Thanks for sharing!

Reverse Cell Phone Lookup United States, on 11/29/2009 12:44:57 AM Said:

Reverse Cell Phone Lookup

Never seen such cool post. I read it all the way to the end. Keep them coming.

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading