I don’t write a lot here because I just don’t have the inspiration to fill up a blog, but sometimes I spend a lot of time trying to get my head around an issue. When I find a solution, I try to post it here so the world can benefit.
So, the last couple of weeks I spent some time trying to solve an issue in a load balanced web farm. The balancing works with – well – load on the servers. No sticky sessions, no round-robin. When performing a callback, the users received javascript errors. Sometimes.
I started by looking at the javascript we emit in the response. The javascript was valid. Even more: the error occured even before our startup script was hit. Strange. Some googling indicated the – stupid – way the .net framework generates web resource urls for GAC assemblies: it uses a hash over the resource name and assembly (including version). This becomes the ‘d’ querystring parameter of the emitted webresource.axd url. Then (God knows why!) the lastmodified date of the assembly, expressed in ticks, is added as the ‘t’ parameter.
We deploy our assembly with homebrewn extenders in the GAC; that’s where all of our assemblies are. Turns out there was a difference of 2 (two!) seconds in the lastmodified date of the assemblies between the webservers. This resulted in – tadaaa – a different ‘t’ parameter for the webresource url.
The most cited solution in google results: force the dates to be the same accross all servers in the webfarm. Well… do we really want to do that? Is it really that necessary?
Opens reflector looking for the AssemblyResourceLoader. Turns out it doesn’t use the ‘t’ parameter to resolve the request. So: no need to provide the ‘t’ parameter in a request. Next step: try to override the generation of the webresource url. Well, unless I’d copy all the code in the AssemblyResourceLoader class to modify one or two lines there’s no way: everything is internal or private and static.
And now, for you reader who made it all the way here: the solution I came up with.
The asp.net ajax client framework uses a clever trick to see if a script resource in the partial update response is already loaded: it takes all the script tags in the document and sees if the same src attribute is present. Since our ‘t’ parameter is different, but the rest isn’t: omit inspecting the ‘t’. Simple and because in javascript we can overwrite everything my best bet.
Here’s the code (only two javascript functions need tweaking) we now add to the page automatically as a startupscript during the initial page load. As you can see, the comparison now happens without the dreaded ‘t’. Problem solved. No powershell script intervention needed and, more importantly: we don’t have to remember setting all dates in the GAC the same.
Sys.Application.add_load(applicationLoadHandlerForScriptLoading); function applicationLoadHandlerForScriptLoading() { Sys.Application.remove_load(applicationLoadHandlerForScriptLoading); Sys._ScriptLoader.readLoadedScripts = function Sys$_ScriptLoader$readLoadedScripts() { if(!Sys._ScriptLoader._referencedScripts) { var referencedScripts = Sys._ScriptLoader._referencedScripts = []; var existingScripts = document.getElementsByTagName('script'); for (var i = existingScripts.length - 1; i >= 0; i--) { var scriptNode = existingScripts[i]; var scriptSrc = CleanScriptReference(scriptNode.src); if (!Array.contains(referencedScripts, scriptSrc)) { Array.add(referencedScripts, scriptSrc); } } } } Sys._ScriptLoader.isScriptLoaded = function Sys$_ScriptLoader$isScriptLoaded(scriptSrc) { var dummyScript = document.createElement('script'); dummyScript.src = CleanScriptReference(scriptSrc); var scriptArray = Sys._ScriptLoader._getLoadedScripts(); for(var i=0; i<scriptArray.length; i++) { scriptArray[i] = CleanScriptReference(scriptArray[i]); } return Array.contains(scriptArray, CleanScriptReference(dummyScript.src)); } function CleanScriptReference(scriptSrc) { var output = scriptSrc; if (scriptSrc.length) { if(scriptSrc.indexOf('Resource.axd') > -1 && (scriptSrc.indexOf('&t=') > -1 || scriptSrc.indexOf('&t=') > -1)) { var idx = scriptSrc.indexOf('&t='); if(idx < 0) { idx = scriptSrc.indexOf('&t='); } if(idx > 0) { output = scriptSrc.substring(0, idx); } } } return output; } }
Recent Comments