Update:
I've pinpointed the issue after a few other investigations. The https:// site was returning a security certificate that was issued for another domain. Why it worked in FireFox anyway and why it didn't work in Chrome, but then it worked after an unauthorized call first, I still don't know, but it is already in the domain of browser internals.

I was trying to access an API on https:// from a page that was hosted on http://. Since the scheme of the call was different from the scheme of the hosted URL, it is interpreted as a cross domain call. You might want to research this concept, called CORS, in order to understand the rest of the post.

The thing is that it didn't work. The server was correctly configured to allow cross domain access, but my jQuery calls did not succeed. In order to access the API I needed to send an Authorization header, as well as request the information as JSON. Investigations on the actual browser calls showed the correct OPTIONS request method, as well as the expected headers, only they appeared as 'Aborted'. It took me a few hours of taking things apart, debugging jQuery, adding and removing options to suddenly see it work! The problem was that after resetting IIS, the problem appeared again! What was going on?

In the end I've identified a way to consistently reproduce the problem, even if at the moment I have no explanation for it. The calls succeed after making a call with no headers (including the Content-Type one). So make a bogus, unauthorized call and the next correct calls will work. Somehow that depends on IIS as well as the Chrome browser. In Firefox it works directly and in Chrome it seems to be consistently reproducible.

The scenario is this: You use a ScriptManager, a TextBox and a Button and your project is at least referencing the Ajax Control Tookit library. You expect when pressing Enter in the TextBox to get to the button Click event. Instead you get the Microsoft JScript runtime error: ‘this._postBackSettings.async’ is null or not an object javascript error.

I found the solution here: JScript Exception in AJAX Control Toolkit. Basically, put the textbox and the button in a Panel and set the DefaultButton property to the button id.

Today a new AjaxControlToolkit was released. I had hoped that at least they noticed the patch I made the effort of sending them, the one that fixed the dynamic TabPanel issue. But no. Three more controls and probably most of the same old bugs.

Here are more details: New release of the Ajax Control Toolkit

I have seen a small video presentation about the new ASP.Net 3.5 SP1 Script Combining feature. Basically you take a bunch of scripts (like the ones from AjaxControlToolkit) and you bundle them together in a single cacheable file. This decreases the number of concurrent connections on your production site.

The problem was that you had to use some component to see what script files were being loaded and then manually add them to the ScriptManager CompositeScript Scripts collection. And this applies only to correctly registered scripts, not stuff that is embedded in the HTML or what not. Isn't it easier to just parse the generated HTML and then replace the script tags, I thought?

Well, I did a small Response filter/IHttpHandler in about two hours. It would take all consecutive external file references and combine them in a single cached and cacheable call. Then I tested it with Asynchronous postbacks. Epic Fail! The main problem was that the combined scripts would re-register themselves at postback and throw all kinds of errors therefore. But how did they know if they were registered or not before?!

I vaguely remembered an old post of mine about the notifyScriptLoaded function that must be called at the end of every external javascript registrations. Examining the system a little I realised that the flow was like this:
  1. register the script (either include it in the HTML in regular render or sending it to the UpdatePanel javascript engine to be registered as a new dynamically script element in the Head page section)
  2. if the registration is an async dynamic one, check in an array if the script is loaded and if it is, don't register it
  3. in the script call Sys.Application.notifyScriptLoaded() which would take the src of the script element and use it as a key in the above mentioned array to declare it has been loaded


Of course, that means combining all the scripts in a single file registers only that file and you get the original files registered again. Then you get errors like 'Type [something] has already been registered.' or (because there are more than one script file bundled together) 'The script [something] contains multiple calls to Sys.Application.notifyScriptLoaded(). Only one is allowed.'.

Well, I did manage to solve the problem by following these steps:
  1. forcefully remove Sys.Application.notifyScriptLoaded() from bundled scripts
  2. add Sys.Application.notifyScriptLoaded() at the end of all scripts
  3. surround the various scripts with a code that looks like
    sb.AppendFormat(@"if (!window.Sys||!Sys._ScriptLoader||!Sys._ScriptLoader.isScriptLoaded('{0}')) {{
    {1}
    if (window.Array&&Array.add&&window.Sys&&Sys._ScriptLoader) Array.add(Sys._ScriptLoader._getLoadedScripts(), '{2}');
    }}
    ", HttpUtility.HtmlEncode(src), content.Replace("Sys.Application.notifyScriptLoaded();", ";"),src);
.

With problem solved, the AutoScriptCombiner works, but it feels wrong. I wanted to post it on Github, you see, but in the end I've decided not to. However I did learn something about how the Asp.Net Ajax framework functions internally and I wanted to share it with you.

A while ago I wrote a post detailing a fix for Very slow UpdatePanel refresh when containing big ListBoxes of DropDownLists. I have restructured the fix into a ControlAdapter and placed it on Github.

Enjoy!

This is a message I got from the UpdatePanel Shrinker in a site we built:
Shrinkage: from 70000 to 10 = 0.014%.


Almost a year ago I thought of a way of compressing the UpdatePanel asynchronous output based on the previously sent information and created the UpdatePanel Shrinker. I waited all this time to test it and also I've used it in some small projects.

From today, the project is on Github, with an MIT licence, that means do whatever you want with it, but I would appreciate some words from you.

As for the details: it uses a sort of fast and dirty home made diff algorithm to compare the previously sent output for an UpdatePanel with the current one. The problem is that the effect can only be seen from the second async postback on, but when used, it yields fantastic compression rates.

You can use it for sites that are accessed from Internet challenged locations, for sites that have complex Ajax interactions and for sites that you "Ajaxify". And before I get angry comments from purists, yes, I know it is more efficient to use Ajax in a smart way to solve each problem in the best possible way, but if you just want the quick and dirty solution, like a MasterPage with a ScriptManager, an UpdatePanel and the page content in it, the Shrinker is the thing for you.

Take care to look in the Debug Output window. The shrinker will output the compression rate and any warnings it might have.

You are using a PopupControlExtender from the AjaxControlToolkit and you added a Button or an ImageButton in it and every time you click on it this ugly js error appears: this._postBackSettings.async is null or not an object. You Google and you see that the solution is to use a LinkButton instead of a Button or an ImageButton.

Well, I want my ImageButton! Therefore I added to the Page_Load method of my page or control this little piece of code to fix the bug:

private void FixPopupFormSubmit()
{
  var script =
    @"if( window.Sys && Sys.WebForms 
    && Sys.WebForms.PageRequestManager 
	&& Sys.WebForms.PageRequestManager.getInstance) {
  var prm = Sys.WebForms.PageRequestManager.getInstance();
  if (prm && !prm._postBackSettings) {
    prm._postBackSettings = prm._createPostBackSettings(false, null, null);
  }
}";
  ScriptManager.RegisterOnSubmitStatement(
    Page, 
    Page.GetType(), 
    "FixPopupFormSubmit", 
    script);
}



Hope it helps you all.

Update: it has come to my attention that these kind of errors appear only when debug="true" in the web config. In production sites it might work regardless.

You want to programatically load a js in a user control. The user control becomes visible in the page only during async postbacks and it is hidden at page load. So you want to use some method of loading the external javascript file during that asynchronous UpdatePanel magic.

Use ScriptManager.RegisterClientScriptInclude. There is a catch, though. For each script that you want to include asynchronously, you must end it with
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
else the Ajax.Net script loader will throw an error like: Microsoft JScript runtime error: Sys.ScriptLoadFailedException:
The script '...' failed to load. Check for:
Inaccessible path. Script errors. (IE) Enable 'Display a notification about every script error' under advanced settings.
Missing call to Sys.Application.notifyScriptLoaded().
.

For the aspx version, use the ScriptManagerProxy control:
    <asp:ScriptManagerProxy runat="server">
<Scripts>
<asp:ScriptReference Path="~/js/myScript.js" NotifyScriptLoaded="true" />
</Scripts>
</asp:ScriptManagerProxy>

Actually, I think this applies to any dynamic modification of drop down list options. Read on!

I have used a CascadingDropDown extender from the AjaxControlToolkit to select regions and provinces based on a web service. It was supposed to be painless and quick. And it was. Until another dev showed me a page giving the horribly obtuse 'Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.'. As you can see, this little error message basically says there is a problem with a control, but doesn't care to disclose which. There are no events or overridable methods to enable some sort of debug.

Luckily, Visual Studio 2008 has source debug inside the .Net framework itself. Thus I could see that the error is caused by the drop down lists I mentioned above. Google told me that somewhere in the documentation of the CascadingDropDown extender there is a mention on setting enableEventValidation to false. I couldn't find the reference, but of course, I didn't look too hard, because that is simply stupid. Why disable event validation for the entire page because of a control? It seems reasonable that Microsoft left it enabled for a reason. (Not that I accuse them of being reasonable, mind you).

Analysing further, I realised that the error kind of made sense. You see, the dropdownlists were not binded with data that came from a postback. How can one POST a value from a select html element if the select did not have it as an option? It must be a hack. Well, of course it was a hack, since the cascade extender filled the dropdown list with values.

I have tried to find a way to override something, make only those two dropdownlists not have event validation enabled. Couldn't find any way to do that. Instead, I've decided to register all possible values with Page.ClientScript.RegisterForEventValidation. And it worked. What I don't understand is why did this error occur only now, and not in the first two pages I have built and tested. That is still to be determined.

Here is the code

foreach (var region in regions)
Page.ClientScript.RegisterForEventValidation(
new PostBackOptions(ddlRegions,region)
);


It should be used in a Render override, since the RegisterForEventValidation method only allows its use in the Render stage of the page cycle.

And that is it. Is it ugly to load all possible values in order to validate the input? Yes. But how else could you validate the input? A little more work and a hidden bug that appears when you least expect it, but now even the input from those drop downs is more secure.

Update:
My control was used in two pages with EnableEventValidation="false" and that's why it didn't throw any error. Anyway, I don't recommend setting it to false. Use the code above. BUT, if you don't know where the code goes or you don't understand what it does, better use this solution and save us both a lot of grief.

I had this calendar extender thingie in my site and the client was Italian so I had to translate it into Italian. Nothing easier. Just enable EnableScriptGlobalization and EnableScriptLocalization and set either the Web.config or Page culture settings.

However, the footer of the calendar kept showing "Today" instead of "Oggi". I checked the source and I noticed that it used a AjaxControlToolkit.Resources.Calendar_Today javascript variable to create the footer. Step by step I narrowed it down to the AjaxControlToolkit resources! Funny thing is that they didn't work. I tried just about everything, including Googling, only to find people having the opposite problem: they would have all these resource dlls created in their Bin directory and couldn't get rid of them. I had none! I copied the directories from the ACTK and nothing happened.

After some research I realized that the Ajax Control Toolkit does NOT include the option for multi language UNLESS in Release mode. I was using everything in the solution, including the ACTK, in Debug mode. Changing the AjaxControlToolkit build to Release in the solution made this work.

A few days ago a coworker asked me about implementing an autocomplete textbox with not only text items, but also images. I thought, how hard can it be? I am sure the guys that made the AutoCompleteExtender in the AjaxControlToolkit thought about it. Yeah, right!

So, I needed to tap into the list showing mechanism of the AutoCompleteExtender, then (maybe) into the item selected mechanism. The AutoCompleteExtender exposes the OnClientShowing and the OnClientItemSelected properties. They expect a function name that accepts a behaviour and an args parameters.

Ok, the extender creates an html element to contain the list completion items or gets one from the property CompletionListElementID (which is obsoleted anyway). It creates a LI element for each item (or a DIV in case of setting CompletionListElementID). So all I had to do was iterate through the childNodes of the container element and change their content.

Then, on item selected, unfortunately the AutoCompleteExtender tries to take the text value with firstChild.nodeValue, which pretty much fails if the first child of the item element is not a text node. So we will tap in OnClientItemSelected, which args object contains item, the text extracted as mentioned above (useless to us), and the object that was passed from the web service that provided the completion list. The last one we need, but keep reading on.

So the display is easy (after you get the hang of the Microsoft patterns). But now you have to return a list of objects, not mere strings, in order to get all the information we need, like the text and the image URL. Here is the piece of code that interprets the values received from the web service:
// Get the text/value for the item
try {
var pair = Sys.Serialization.JavaScriptSerializer.deserialize('(' + completionItems[i] + ')');
if (pair && pair.First) {
// Use the text and value pair returned from the web service
text = pair.First;
value = pair.Second;
} else {
// If the web service only returned a regular string, use it for
// both the text and the value
text = pair;
value = pair;
}
} catch (ex) {
text = completionItems[i];
value = completionItems[i];
}


In other words, it first tries to deserialize the string received, then it checks if it is a Pair object (if it has a First property) else it passes the object as value and text! If deserialization fails, the entire original string is considered. Bingo! So on the server side we need to serialize the array of strings we want to send to the client. And we do that by using System.Web.Script.Serialization.JavaScriptSerializer. You will see how it goes into the code.

So far we displayed what we wanted, we sent what we wanted, all we need is to set how we want the completion items to appear. And for that I could have used a simple string property, but I wanted all the goodness of the intellisense in Visual Studio and all the objects I want, without having to Render them manually into strings.

So, the final version of the AutoCompleteExtender with images is this: A class that inherits AutoCompleteExtender, but also INamingContainer. It has a property ItemTemplate of the type ITemplate which will hold the template we want in the item. You also need a web service that will use the JavascriptSerializer to construct the strings returned.

Here is the complete code:
AdvancedAutoComplete.cs

using System;
using System.IO;
using System.Web.UI;
using System.Web.UI.WebControls;
using AjaxControlToolkit;

namespace Siderite.Web.WebControls
{
/// <summary>
/// AutoCompleteExtender with templating
/// </summary>
public class AdvancedAutoComplete : AutoCompleteExtender, INamingContainer
{
private ITemplate _template;

[TemplateContainer(typeof(Content))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ItemTemplate
{
get { return _template; }
set { _template = value; }
}

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
const string script = @"
function AdvancedItemDisplay(behaviour,args) {
var template=behaviour.get_element().getAttribute('_template');
//if (!template==null) template='${0}';
for (var i=0; i<behaviour._completionListElement.childNodes.length; i++) {
var item=behaviour._completionListElement.childNodes[i];
var vals = item._value;
var html=template;
for (var c=0; c<vals.length; c++)
html=html.replace(new RegExp('\\$\\{'+c+'\\}','g'),vals[c]);
item.innerHTML=html;
}
}

function AdvancedSetText(behaviour,args) {
var vals=args._value;
var element=behaviour.get_element();
var control = element.control;
if (control && control.set_text)
control.set_text(vals[0]);
else
element.value = vals[0];
}
"
;
ScriptManager.RegisterClientScriptBlock(this, GetType(), "AdvancedAutoComplete", script, true);

OnClientShowing = "AdvancedItemDisplay";
OnClientItemSelected = "AdvancedSetText";
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string template = GetTemplate();
((TextBox)TargetControl).Attributes["_template"] = template;
}

private string GetTemplate()
{
Content ph=new Content();
ph.Page = Page;
_template.InstantiateIn(ph);
HtmlTextWriter htw = new HtmlTextWriter(new StringWriter());
ph.RenderControl(htw);
return htw.InnerWriter.ToString();
}
}
}


MainService.cs

using System.Collections.Generic;
using System.Web.Script.Serialization;
using System.Web.Script.Services;
using System.Web.Services;

/// <summary>
/// Web service to send auto complete items to the AdvancedAutoComplete extender
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class MainService : WebService
{
[WebMethod]
[ScriptMethod]
public string[] GetCompletionList(string prefixText, int count)
{
JavaScriptSerializer jss=new JavaScriptSerializer();
List<string> list=new List<string>();
for (int c = 0; (list.Count< count) && (c < 100000); c++)
{
string s = (c*c).ToString();
if (s.StartsWith(prefixText))
{
object[] item = new object[] {s, "images/demo.gif"};
list.Add(jss.Serialize(item));
}
}
return list.ToArray();
}
}



An example of the use

<asp:TextBox runat="server" ID="tbMain"></asp:TextBox>
<cc1:AdvancedAutoComplete ID="aceMain" runat="server" BehaviorID="mhMain" TargetControlID="tbMain"
ServiceMethod="GetCompletionList" ServicePath="~/MainService.asmx" MinimumPrefixLength="0"
CompletionInterval="0">
<ItemTemplate>
<asp:Image runat="server" ID="imgTest" ImageUrl="${1}" /><asp:Label runat="server"
ID="lbTest" Text="${0}"></asp:Label>
</ItemTemplate>
</cc1:AdvancedAutoComplete>


That's it! All you have to do is make sure the controls in the template render ${N} text that gets replaced with the first, second, Nth item in the list sent by the web service. The text that will be changed in the textbox is always the first item in the list (${0}).

Restrictions: if you want to use this a control in a library and THEN add some more functionality on the Showing and ItemSelected events, you need to take into account that those are not real events, but javascript functions, and the design of the autocompleteextender only accepts one function name. You could create your own function that also call on the one described here, but that's besides the point of this blog entry.

Somebody asked me how to add things at the end of an AutoCompleteExtender div, something like a link. I thought it would be pretty easy, all I would have to do is catch the display function and add a little thing in the container. Well, it was that simple, but also complicated, because the onmousedown handler for the dropdown is on the entire div, not on the specific items. The link was easy to add, but there was no way to actually click on it!

Here is my solution:

function addLink(sender,args) {
var div=sender.get_completionList();
if (div) {
var newDiv=document.createElement('div');
newDiv.appendChild(getLink());
div.appendChild(newDiv);
}
}

function getLink() {
var link=document.createElement('a');
link.href='#';
link.innerHTML='Click me for popup!'
link.commandName='AutoCompleteLinkClick';
return link;
}

function linkClicked() {
alert('Popsicle!');
}

function ACitemSelected(sender,args) {
var commandName=args.get_item().commandName;
if (commandName=='AutoCompleteLinkClick') linkClicked();
}


The AutoCompleteExtender must have the two main functions as handlers for the item selected and popup shown set like this:
<ajaxControlToolkit:AutoCompleteExtender OnClientItemSelected="ACitemSelected" OnClientShown="addLink" ...>


Now, the explaining.

First we hook on OnClientShown and add our link. The link is added inside a div, because else it would have been shown as a list item (with highlight on mouse over). I also added a custom attribute to the link: commandName.

Second we hook on OnClientItemSelected and check if the selected item has a commandName attribute, and then we execute stuff depending on it.

That's it folks!

Update: this fix is now on Github: Github. Get the latest version from there.

The scenario is pretty straightforward: a ListBox or DropDownList or any control that renders as a Select html element with a few thousand entries or more causes an asynchronous UpdatePanel update to become incredibly slow on Internet Explorer and reasonably slow on FireFox, keeping the CPU to 100% during this time. Why is that?

Delving into the UpdatePanel inner workings one can see that the actual update is done through an _updatePanel Javascript function. It contains three major parts: it runs all dispose scripts for the update panel, then it executes _destroyTree(element) and then sets element.innerHTML to whatever content it contains. Amazingly enough, the slow part comes from the _destroyTree function. It recursively takes all html elements in an UpdatePanel div and tries to dispose them, their associated controls and their associated behaviours. I don't know why it takes so long with select elements, all I can tell you is that childNodes contains all the options of a select and thus the script tries to dispose every one of them, but it is mostly an IE DOM issue.

What is the solution? Enter the ScriptManager.RegisterDispose method. It registers dispose Javascript scripts for any control during UpdatePanel refresh or delete. Remember the first part of _updatePanel? So if you add a script that clears all the useless options of the select on dispose, you get instantaneous update!

First attempt: I used select.options.length=0;. I realized that on Internet Explorer it took just as much to clear the options as it took to dispose them in the _destroyTree function. The only way I could make it work instantly is with select.parentNode.removeChild(select). Of course, that means that the actual selection would be lost, so something more complicated was needed if I wanted to preserve the selection in the ListBox.

Second attempt: I would dynamically create another select, with the same id and name as the target select element, but then I would populate it only with the selected options from the target, then use replaceChild to make the switch. This worked fine, but I wanted something a little better, because I would have the same issue trying to dynamically create a select with a few thousand items.

Third attempt: I would dynamically create a hidden input with the same id and name as the target select, then I would set its value to the comma separated list of the values of the selected options in the target select element. That should have solved all problems, but somehow it didn't. When selecting 10000 items and updating the UpdatePanel, it took about 5 seconds to replace the select with the hidden field, but then it took minutes again to recreate the updatePanel!

Here is the piece of code that fixes most of the issues so far:

/// <summary>
/// Use it in Page_Load.
/// lbTest is a ListBox with 10000 items
/// updMain is the UpdatePanel in which it resides
/// </summary>
private void RegisterScript()
{
string script =
string.Format(@"
var select=document.getElementById('{0}');
if (select) {{
// first attempt
//select.parentNode.removeChild(select);


// second attempt
// var stub=document.createElement('select');
// stub.id=select.id;
// for (var i=0; i<select.options.length; i++)
// if (select.options[i].selected) {{
// var op=new Option(select.options[i].text,select.options[i].value);
// op.selected=true;
// stub.options[stub.options.length]=op;
// }}
// select.parentNode.replaceChild(stub,select);


// third attempt
var stub=document.createElement('input');
stub.type='hidden';
stub.id=select.id;
stub.name=select.name;
stub._behaviors=select._behaviors;
var val=new Array();
for (var i=0; i<select.options.length; i++)
if (select.options[i].selected) {{
val[val.length]=select.options[i].value;
}}
stub.value=val.join(',');
select.parentNode.replaceChild(stub,select);

}};"
,
lbTest.ClientID);
ScriptManager sm = ScriptManager.GetCurrent(this);
if (sm != null) sm.RegisterDispose(lbTest, script);
}



What made the whole thing be still slow was the initialization of the page after the UpdatePanel updated. It goes all the way to the WebForms.js file embedded in the System.Web.dll (NOT System.Web.Extensions.dll), so part of the .NET framework. What it does it take all the elements of the html form (for selects it takes all selected options) and adds them to the list of postbacked controls within the WebForm_InitCallback javascript function.

The code looks like this:

if (tagName == "select") {
var selectCount = element.options.length;
for (var j = 0; j < selectCount; j++) {
var selectChild = element.options[j];
if (selectChild.selected == true) {
WebForm_InitCallbackAddField(element.name, element.value);
}
}
}
function WebForm_InitCallbackAddField(name, value) {
var nameValue = new Object();
nameValue.name = name;
nameValue.value = value;
__theFormPostCollection[__theFormPostCollection.length] = nameValue;
__theFormPostData += name + "=" + WebForm_EncodeCallback(value) + "&";
}

That is funny enough, because __theFormPostCollection is only used to simulate a postback by adding a hidden input for each of the collection's items to a xmlRequestFrame (just like my code above) in the function WebForm_DoCallback which in turn is called only in the GetCallbackEventReference(string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync) method of the ClientScriptManager which in turn is only used in rarely used scenarios with the own mechanism of javascript callbacks of GridViews, DetailViews and TreeViews. And that is it!! The incredible delay in this javascript code comes from a useless piece of code! The whole WebForm_InitCallback function is useless most of the time! So I added this little bit of code to the RegisterScript method and it all went marvelously fast: 10 seconds for 10000 selected items.

string script = @"WebForm_InitCallback=function() {};";
ScriptManager.RegisterStartupScript(this, GetType(), "removeWebForm_InitCallback", script, true);



And that is it! Problem solved.

Thanks for Raheel for presenting me this problem and the opportunity to fix it.

As you may know from a previous post of mine, Ajax requests differ from normal requests. At the Page level, the rendered content is changed to reflect only the things that changed in the UpdatePanels and the format is different from the usual HTML and it is also very strict. Any attempt to blindly add things to the string sent from the server will result in an ugly alert error. And who does indiscriminately add ugly content to your Ajax requests? "Free" servers that inject commercials in your pages!

So, what can you do? Patch the Ajax engine to remove the offending ads. Here is a simple example for a server that added crap at THE END of the content, crap that did not contain any '|' characters. This is important, as the patch looks for the last '|' character and removes all the things after it.

C# Code

private void LoadApplyPatch()
{
ScriptManager sm = ScriptManager.GetCurrent(Page);
if (sm == null) return;

string script =@"
if (window.Sys&&Sys.WebForms&&Sys.WebForms.PageRequestManager) {
Sys$Net$WebRequestExecutor$get_responseData=function() {
if (arguments.length !== 0) throw Error.parameterCount();
if (!this._responseAvailable) {
throw Error.invalidOperation(String.format(Sys.Res.cannotCallBeforeResponse, 'get_responseData'));
}
if (!this._xmlHttpRequest) {
throw Error.invalidOperation(String.format(Sys.Res.cannotCallOutsideHandler, 'get_responseData'));
}

var content=this._xmlHttpRequest.responseText;

// this is the added code, the rest is taken
// from the ASP.Net Ajax original code
var index=content.lastIndexOf('|');
if (index<content.length-1)
content=content.substring(0,index+1);


return content;
}
}"
;

ScriptManager.RegisterStartupScript(Page,
Page.GetType(), "adMurderer", script, true);
}

I have been working on an idea, one that assumed that if you remember the content sent to an UpdatePanel, you can send only the difference next time you update it. That would mean that you could use only one UpdatePanel, maybe in the MasterPage, and update only changes, wherever they'd be and no matter how small.

However, I also assumed that if I set the innerHTML property of an HTML element, the value will remain the same. In other words that elem.innerHTML=content; implied that elem.innerHTML and content are identical at the end of the operation. But they were not! Apparently the browser (each flavour in its own way) interprets the string that you feed it then creates the element tree structure. If you query the innerHTML property, it uses the node structure to recreate it.

So comparing the value that you've fed it to the actual value of innerHTML after the operation, you see quoted attribute values, altogether removed attributes when they have the default value, uppercased keywords, changed attribute order and so on and so on. FireFox only adds quotes as far as I see, but you never know what they'll do next.

On the bright side, now that my idea had been torn to shreds by the browser implementation, I now have an answer to all those stuck up web developers that consider innerHTML unclean or unstructured and criticize the browsers for not being able to render as fast when using DOM methods. The innerHTML property is like a web service. You feed it your changes in (almost) XML format and it applies it on the browser. Since you pretty much do the same when you use any form of web request, including Ajax, you cannot complain.