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 :)!

Post a Comment