Monday, April 28, 2008

AjaxControlToolkit & ScriptResource.axd load balancer (web farm) related issue

It turns out that approaches that create resource url based onto methods that are in turn based on the AppRoot IIS metabase value can be actually very error prone.

One of such is the AjaxControlToolkit currently. Lets see how its ToolkitScriptManager creates script references:

protected override void OnResolveScriptReference(ScriptReferenceEventArgs e)
{

....
if (this._combinedScriptUrl == null)
{
this._combinedScriptUrl = string.Format(CultureInfo.InvariantCulture, "{0}?{1}={2}&{3}={4}", new object[] { (null != this._combineScriptsHandlerUrl) ? this._combineScriptsHandlerUrl.ToString() : this.Page.Request.Path, "_TSM_HiddenField_", this.HiddenFieldName, "_TSM_CombinedScripts_", HttpUtility.UrlEncode(SerializeScriptEntries(this._scriptEntries, false)) });
}

It can be an idea to do some normalization of path, something like ToUpper() or ToLower would do. Can though imagine REST path based case sensitive scenarios ... But the stable part of a path should/may be definitely normalized anyway.

What is the issue that happens? .

During partial page updates (when you use update panels), script loads may be requested by the partial updates that are coming back from the server. For example by the elements inside the |id|ScriptPath| .....

ScriptManager is doing a smart job by trying to see if any of the scripts referred by this partial update response is not actually present in its previously loaded list of scripts:

Sys._ScriptLoader.isScriptLoaded = function Sys$_ScriptLoader$isScriptLoaded(scriptSrc) {
// For Firefox we need to resolve the script src attribute
// since the script elements already in the DOM are always
// resolved. To do this we create a dummy element to see
// what it would resolve to.
var dummyScript = document.createElement('script');
dummyScript.src = scriptSrc;
return Array.contains(Sys._ScriptLoader._getLoadedScripts(), dummyScript.src);

Check happens based on the url of the script resource. Now imagine script resource from one server to be "/YOURVIRTUALDIRNAME/ScriptResource.axd?bla" and from another one: "/YourVirtualDirName/ScriptResource.axd?bla" .

In this case ScriptManager downloads the script, and again doing a smart thing, validates if any of the types defined by it are not already present on the page.

This is how in case of AjaxControlToolkit it first hits the:

Microsoft JScript runtime error: Sys.InvalidOperationException: Type AjaxControlToolkit.BoxSide has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update.

And then in the post script load handler:


Microsoft JScript runtime error: Sys.ScriptLoadFailedException: The script '/YOURVIRTUALDIRNAME/ScriptResource.axd?d=TmjKPE4RZ97qx5GW9RVSkqSX0rxZscPBsKSyhveagjALaOxgNSakbVbBWc9HKZhJiVrfl3LartocAklCD5-ciQ2&t=633425518675405868' failed to load. Check for:
Inaccessible path.

As again the idea of just using ToUpper() or ToLower() would not work for all of the cases (like REST), the best way out looks to be just to ensure that AppRoot of those virtual dirs is exactly the same on all servers in the farm.

You can use Metabase Editor utility coming with the IIS Resource kit for the convenient editing of this. As you update it please pay attention to the whole part starting Root to be the same on all servers and better be Root and not ROOT. I don't have enough time to play with it now, but it looks like some magic converts the virtual dir app root name to capital if ROOT is used! This happens as it seems when IIS is flushing in-memory metabase snapshot to the disk. To be confirmed and be more funded hopefully soon.

Your problems with scripts can be though absolutely unrelated to this as there is quite a broad range of other issues you can have:
Non-matching machineKey on your farm, faulty script compression on IIS6, and range of others - happy googling :)!

6 comments:

dussel said...

Yes that is exactly what I experience at the moment. The curiosity is that this only happens on remote clients, the application works fine on localhost (http://localhost). BUT when I use the IP address of the local host, the error even occurs at the local machine. Unfortenately I'm running W2K so I can not install the resource kit and my knowledge about administration is pretty small. Maybe someone can resolve the problem, I would be very thankfull...

Stanislav Dvoychenko said...

Hi dussel,
if you suspect you expirience the same issue can try to use Fiddler to see what exactly is the url it is getting the scripts from first, and then how the relative url is different (if different) in the second case (just before it fails).

Paul Hay said...

Thanks for this Stan. This pointed me to a solution for a problem I've had for the last few days. I've got a web application running on 2 load balanced w2k servers, The app uses Ajax update panels and occasionally I would get the can't load ScriptResource.axd error. After reading your blog I checked the names of the virtual directories on the servers and found that one was named AbcDef and the other was abcDef (notice the difference in case) when I made them match the error went away Yippee!!!. Once again many thanks

Anonymous said...

I have a similar problem - Im getting the same javascript error but I am not using load balanced servers, it is all happening on my local machine (http://localhost:port/MyWebApp).

I have a usercontrol with an updatepanel and a treeview inside it. This all works fine when putting the usercontrol on a standard web page. However, I get the ScriptResource.axd error (as above) when i put the usercontrol into a page that already has an updatepanel on it. Any suggestions? please?
Thanks,
Eug

ErrorDetails: Sys.ScriptLoadFailedException: The script 'http://localhost:1234/MyWebApp/ScriptResource.axd?d=....' contains multiple calls to Sys.Application.notifyScriptLoaded(). Only one is allowed.
Source File: http://localhost:1234/MyWebApp/ScriptResource.axd?d=....

Anonymous said...

Hi its me anonymous again. I fixed the problem. I got an identical javascript error message, but just to clarify it was nothing to do with loadbalanced servers. Instead, mine was to do with having nested updatepanels: the main updatepanel contains a few web controls and tabs and a usercontrol, and inside the usercontrol an updatepanel with a treeview usercontrol. If it helps, I managed to fix the javascript error by simplifying the parent updatepanel.

To be specific I removed the ajax-Tabs control to outside the main updatepanel, and now my page works fine with an updatepanel containing a few controls, and a usercontrol that in turn contains an updatepanel with a treeview control inside it. My ajaxTabs control sits outside the main updatepanel, but thats ok because the tabs change quite smoothly and do not really need an updatepanel. Strange error though.

Thanks,
Eug

Anonymous said...

We had this issue too.

When we compared Metabase.xml on the web servers... one server used capital letters, the other lowercase... so we made them consistent and it fixed the problem.