Well, it's just as in the AjaxControlToolKit page for the AutoCompleteExtender control. But here is a quick dirty list of the steps:

Option A: (the static page method option)

  1. Add a TextBox to your page
  2. Add the AutoCompleteExtender
  3. Set the ScriptManager property EnablePageMethods to true
  4. Add a static method to the page, one that gets a string and an integer as parameters and returns a string array
  5. Decorate it with [WebMethod(true/false)]. If you set the WebMethod parameter to true, it will have access to the Session
  6. Make sure to return the list of strings depending on the string parameter (which represents what was typed in the TextBox)
  7. Warning! If the method is faulty, you will get no error message, the autocomplete will simply not work.
  8. Warning! Having a different method signature will also cause this to not work.
  9. Set the properties for the AutoCompleteExtender: TargetControlID with the ID of the TextBox, MinimumPrefixLength with the count of typed characters from which to attempt autocomplete, ServiceMethod with the name of the static page method and CompletionInterval with the miliseconds before it attempts autocomplete.

Option B: (the web service option)

  1. Add a TextBox to your page
  2. Add the AutoCompleteExtender
  3. Add a webservice to your web site
  4. The webservice must have [ScriptService] decorating it's class in the codebehind
  5. Add a NOT static method to the webservice, one that gets a string and an integer as parameters and returns a string array
  6. Decorate it with [WebMethod(true/false)] and [ScriptMethod]. If you set the WebMethod parameter to true, it will have access to the Session
  7. Make sure to return the list of strings depending on the string parameter (which represents what was typed in the TextBox)
  8. Warning! If the method is faulty, you will get no error message, the autocomplete will simply not work.
  9. Warning! Having a different method signature will also cause this to not work.
  10. Set the properties for the AutoCompleteExtender: TargetControlID with the ID of the TextBox, MinimumPrefixLength with the count of typed characters from which to attempt autocomplete, ServiceMethod with the name of the WebMethod in the webservice, CompletionInterval with the miliseconds before it attempts autocomplete and ServicePath to the path of the asmx path.


Now it should work.

Code:

//=== AutoComplete.cs - the web service ===
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class AutoComplete : WebService
{

[WebMethod(true)]
[ScriptMethod]
public string[] GetList(string prefixText, int count)
{
string[] arr=new string[] {'list','of','words'};
return arr;
}
}

//=== Web page codebehind
[WebMethod(true)]
[ScriptMethod]
public static string[] GetList(string prefixText, int count)
{
string[] arr=new string[] {'list','of','words'};
return arr;
}


=== Web page ===

<asp:TextBox ID="textboxWithAutoComplete" runat="server">
<cc1:AutoCompleteExtender ID="autoCompleteExtender1" runat="server" TargetControlID="textboxWithAutoComplete"
MinimumPrefixLength="0"
ServiceMethod="GetList"
CompletionInterval="0"
ServicePath="AutoComplete.asmx"
>
</cc1:AutoCompleteExtender>

WARNING! The parameters of the web method must be named prefixText and count or the AutoCompleteExtender will NOT WORK!

A small paragraph that most people miss on the AjaxControlToolKit sample page says: Note that you can replace "GetCompletionList" with a name of your choice, but the return type and parameter name and type must exactly match, including case.

I was trying to make a site using a TabContainer and an UpdatePanel and I kept receiving a PageRequestManagerParserErrorException, but only sometimes. A page refresh would fix it and the message looked like "Error parsing near '<html>

<head>

'". The funny thing is that in the Ajax response output I had no <html> or <head>.
Trying desperately a fix detailed in this very nice post: Sys.WebForms.PageRequestManagerParserErrorException - what it is and how to avoid it , mainly forcing the start of a Session when the Page is first loaded, seemed to fix it. (put a if (!IsPostBack) Session["Siderite"]="rules"; in Page_Load)

So beware: the dreaded PageRequestManagerParserErrorException doesn't appear only when the response output is malformed, but apparently also when in some cases someone tries to start a Session from within an async postback.

If you have some other problem linked to this exception, read this post: ASP.Net Ajax and Response.Write or response filters The message received from the server could not be parsed.

Update: The 30 September 2009 release of the AjaxControlToolkit doesn't have the error that I fix here. My patch was applied in July and from September on the bug is gone in the official release as well. Good riddance! :)

==== Obsolete post follows

Update: On June 20th 2009, Codeplex notified me that the patch I did for the ACT has been applied. I haven't tested it yet, though. Get the latest source (not latest stable version) and you should be fine.

This post was updated on the 1st of July 2008 with some clearer explanations and some error corrections thanks to Santoé who pointed out some mistakes.

My ASP.Net app uses a TabContainer, with a TabPanel in the *x code and with additional TabPanels added dynamically in codebehind.

Well, I got a lot of errors so I've decided to debug and change the control in order to fix it.

Step 1: download the AjaxControlToolKit with source included and open the project locally.

First error : Specified argument was out of the range of valid values. Parameter name: index, somewhere in the TabPanelCollection indexer. The problem actually occurs in TabContainer in LoadClientState(string clientState) where there is a for (int i = 0; i < tabState.Length ; i++). It doesn't take into account the possibility that the number of Tabs and the number of values taken from the tabState can be different. So the code must look like this: for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

Step 2: In the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change for (int i = 0; i < tabState.Length ; i++) to for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

The second error is actually a thrown error in the ActiveTabIndex property setter: if (value >= Tabs.Count) { throw new ArgumentOutOfRangeException("value"); }, but it all comes from this: if (Tabs.Count==0 && !_initialized), because it doesn't take into account the possibility that the Tabs.Count is smaller than the ActiveTabIndex, but not zero. So that should look like this: if (value >= Tabs.Count && !_initialized).

I've downloaded the latest AjaxControlToolKit (version Version 1.0.20229 - Feb 29 2008) and the scratched fix above doesn't seem to work anymore. Instead, try patching the ActiveTabIndex property like this:

[DefaultValue(-1)]
[Category("Behavior")]
[ExtenderControlProperty]
[ClientPropertyName("activeTabIndex")]
public virtual int ActiveTabIndex
{
get
{
if (_cachedActiveTabIndex > -1)
{
return _cachedActiveTabIndex;
}
if (Tabs.Count == 0)
{
return -1;
}
return _activeTabIndex;
}
set
{
if (value < -1)
throw new ArgumentOutOfRangeException("value");
if (Tabs.Count == 0 && !_initialized)
{
_cachedActiveTabIndex = value;
}
else
{
if (ActiveTabIndex != value)
{
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = false;
}
if (value >= Tabs.Count)
{
_activeTabIndex = Tabs.Count-1;
_cachedActiveTabIndex = value;
}
else
{
_activeTabIndex = value;
_cachedActiveTabIndex = -1;
}
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = true;
}
}
}
}
}


In other words, remove the ArgumentException code block and move the condition inside the next block, where you set the real _activeTabIndex to the highest legal value, yet you put the real value in _cachedActiveTabIndex.

Step 3: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change the ActiveTabIndex property with the code above.

The same thing must be done in Javascript, in the Tabs.js file, if you intend to use a TabContainer with no static tabs defined. In case you do that, you will get a javascript error "Microsoft JScript runtime error: Sys.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: value
". The fix is to change the set_activeTabIndex function of the TabContainer in the file tabs.js to this:
set_activeTabIndex : function(value) {
if (!this.get_isInitialized()) {
this._cachedActiveTabIndex = value;
} else {
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(false);
}
if (value < -1 || value >= this.get_tabs().length) {
this._activeTabIndex = this.get_tabs().length-1;
this._cachedActiveTabIndex=value;
} else {
this._activeTabIndex = value;
this._cachedActiveTabIndex=-1;
}
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(true);
}
if (this._loaded) {
this.raiseActiveTabChanged();
}
this.raisePropertyChanged("activeTabIndex");
}
},


Step 4: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a tabs.js file. Change the set_activeTabIndex function with the code above.

This fixed it for me for now.

Step 5: Compile the now patched AjaxControlToolKit and use the resulting dll in your project instead of the default one.

As a reference, my test app does the following things:
  • Starts with a TabContainer with single TabPanel defined in the aspx
  • Has a button that adds new tabs to the TabContainer dynamically on the Click event
  • The panels have buttons in them that can be clicked
  • The active tab must be preserved during postbacks
  • The page must work both on synchronous and asynchronous postbacks


Here is the code for the page

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

public partial class _Default : Page
{
private int? _tabCount;

/// <summary>
/// Keep in ViewState the number of dynamically added tabs
/// </summary>
public int TabCount
{
get
{
if (_tabCount == null)
{
if (ViewState["TabCount"] == null)
TabCount = 0;
else
TabCount = (int) ViewState["TabCount"];
}
return _tabCount.Value;
}
set
{
_tabCount = value;
ViewState["TabCount"] = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
InitTabs();
}

/// <summary>
/// Add the dynamical tabs after each postback
/// </summary>
private void InitTabs()
{
for (int c = 0; c < TabCount; c++)
AddPanel();
}

/// <summary>
/// Dynamically add a panel to the TabContainer
/// </summary>
private void AddPanel()
{
TabPanel tp = new TabPanel();
tp.HeaderText = "Test Dinamic";
TextBox tb = new TextBox();
Button btn = new Button();
btn.Text = "Click me!";
tp.Controls.Add(btn);
tp.Controls.Add(tb);
TabContainer1.Tabs.Add(tp);
}

/// <summary>
/// Click event to add a new panel
/// and update the TabCount property
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnAddPanel_Click(object sender, EventArgs e)
{
AddPanel();
// do this if you didn't have any staticly defined
// tabs or else the dynamic tabs will be invisible
If (TabCount==0) TabContainer1.ActiveTabIndex=0;
TabCount++;
}
}

Warning: this is only a partially working solution due to some Javascript issues described (and solved) here.

A requirement I had was to maintain the scroll position of ListBoxes on PostBack. The only solution I could find was to get the scroll through Javascript (the scrollTop property of the select) and restore it on page load, however, that would have meant a lot of custom controls, not to mention lots of work, to which I am usually against.

So, I used a ControlAdapter! The ControlAdapter is something new to the NET 2.0 framework. The Control in 2.0 looks for a ControlAdapter and delegates the usual methods (like OnLoad,OnInit,Render,etc) to the adapter. You tell the site to use an adapter for a specific type of control and possibly a specific browser type (by using a browser file), and it uses that adapter for all of the controls of the selected type and also the ones inherited from them. To disallow the "adaptation" of your control, override ResolveAdapter to always return null.

Ok, the code!
C# code
///<summary>
/// This class saves the vertical scroll of listboxes
/// Set Attributes["resetScroll"] to something when you want to reset the scroll
///</summary>
public class ListBoxScrollAdapter : ControlAdapter
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if ((Page != null) && (Control is WebControl))
        {
            WebControl ctrl = (WebControl) Control;
            string scrollTop = Page.Request.Form[Control.ClientID + "_scrollTop"];
            ScriptManagerHelper.RegisterHiddenField(Page, Control.ClientID + "_scrollTop", scrollTop);
            string script =
                string.Format(
                    "var hf=document.getElementById('{0}_scrollTop');var lb=document.getElementById('{0}');if(hf&&lb) hf.value=lb.scrollTop;",
                    Control.ClientID);
            ScriptManagerHelper.RegisterOnSubmitStatement(Page, Page.GetType(), Control.UniqueID + "_saveScroll",
                                                          script);
            if (string.IsNullOrEmpty(ctrl.Attributes["resetScroll"]))
            {
                script =
                    string.Format(
                        "var hf=document.getElementById('{0}_scrollTop');var lb=document.getElementById('{0}');if(hf&&lb) lb.scrollTop=hf.value;",
                        Control.ClientID);
                ScriptManagerHelper.RegisterStartupScript(Page, Page.GetType(), Control.ClientID + "_restoreScroll",
                                                          script, true);
            } else
            {
                ctrl.Attributes["resetScroll"] = null;
            }
        }
    }
}


Browser file content<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType ="System.Web.UI.WebControls.ListBox"
adapterType="Siderite.Web.WebAdapters.ListBoxScrollAdapter" />
</controlAdapters>
</browser>
</browsers>


Of course, you will ask me What is that ScriptManagerHelper? It's a little something that tries to get the ScriptManager class without having to reference the System.Web.Extensions library for Ajax. That means that if there is Ajax around, it will use ScriptManager.[method] and if it is not it will use ClientScript.[method]. To.Int(object) is obviously something that gets the integer value from a string.

There is another thing, at the beginning I've inherited this adapter from a WebControlAdapter, but it resulted in showing all the options in the select (all the items in the ListBox) with empty text. The value was set as well as the number of options. It might be because in WebControlAdapter the Render method looks like this:
protected internal override void Render(HtmlTextWriter writer)
{
  this.RenderBeginTag(writer);
  this.RenderContents(writer);
  this.RenderEndTag(writer);
}

instead of just calling the control Render method.

I was looking for an answer to the problem of a grid inside an update panel. You see, since the rows and cells of a DataGrid or a GridView are special controls that can't be put inside panels, only in specific parent controls like tables and rows, there is no way to update only a row or a cell of a grid. If the grid is big, it takes a long time to render it entirely, it takes the CPU to 100%, it even blocks the animation of gifs. That results in ugly Ajax.

So, my first thought was: is there a way to update only what has changed? As I was saying in a previous post, a Page is rendered as its HTML string the first time it is loaded and then each Ajax postback makes it render like a list of tokens. The token format is this:

length|type|id|content|


For example 100|updatePanel|UpdatePanel1|<inner HTML of panel of 100 bytes>|

What if I would to insert my own tokens, then? Could I, let's say, change the innerHTML of a control outside of the UpdatePanel? And the answer is YES!

There are 20 token types:
  • updatePanel
  • hiddenField
  • arrayDeclaration
  • scriptBlock
  • expando
  • onSubmit
  • asyncPostBackControlIDs
  • postBackControlIDs
  • updatePanelIDs
  • asyncPostBackTimeout
  • childUpdatePanelIDs
  • panelsToRefreshIDs
  • formAction
  • dataItem
  • dataItemJson
  • scriptDispose
  • pageRedirect
  • error
  • pageTitle
  • focus


Most are not interesting, but 4 of them are!

type:updatePanel
If you add a token to the rendered page string that has the type updatePanel and the id is the UniqueID or ClientID of a control, the content will replace the innerHTML of that control, even if the control is not in an UpdatePanel.

type:hiddenField
If you add a token to the rendered page string that has the type hiddenField and the id is the UniqueID or ClientID of a control, the content will replace the value html property of that control. You can use it on hidden fields, but also on any type of input or html element that has a value. If the control does not exist, a hidden input will be created with that id and then the value will be set. You could read that value after a normal PostBack, let's say.

type:expando
If you add a token to the rendered page string that has the type expando a script will be executed in Javascript that looks like this:
id=content

Example: 5|expando|document.getElementById('TextBox1').style.backgroundColor|'red'|
This will result in the change of the background color of the control with id TextBox1 to red.

type:focus
If you add a token to the rendered page string that has the type focus and the content is a ClientID, then the focus will be set to that control provided that the focus.js script has been loaded. This script is loaded when you use Page.SetFocus. So, in order to set the focus to a control using this method, you must use SetFocus in PageLoad on any control you would like.

Why not use SetFocus, then, and be done with it? Well, because this, as all the methods above work on ANY control in the page, not just the ones in the update panel.

And now the code
using System;
using System.IO;
using System.Web.UI;
 
public partial class _Default : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Use SetFocus so that focus.js is loaded
        SetFocus(TextBox1);
    }
 
    // get the token for a javascript property change
    private string TokenizeProperty(string value, string property)
    {
        return string.Format("{0}|expando|{1}|{2}|", value.Length, property, value);
    }
 
    // get the token for a javascript value change
    private string TokenizeValue(string content, string controlID)
    {
        return string.Format("{0}|hiddenField|{1}|{2}|", content.Length, controlID, content);
    }
 
    // get the token for setting focus to a control through javascript
    private string TokenizeFocus(string controlID)
    {
        return string.Format("{0}|focus||{1}|", controlID.Length, controlID);
    }
 
    // get the token to replace the innerHTML through javascript
    private string TokenizeInnerHtml(string content, string controlID)
    {
        return string.Format("{0}|updatePanel|{1}|{2}|", content.Length, controlID, content);
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        // we only do this in the case of an Async Postback
 
        ScriptManager sm = ScriptManager.GetCurrent(this);
        if ((sm == null) || !sm.IsInAsyncPostBack)
        {
            base.Render(writer);
            return;
        }
 
        // Get the rendered page string 
        // (which should be a list of Ajax tokens)
 
        HtmlTextWriter tw = new HtmlTextWriter(new StringWriter());
        base.Render(tw);
        string content = tw.InnerWriter.ToString();
 
        // Get some meaningless text that changes over time
        string insert = DateTime.Now.ToLongTimeString();
 
        //Change the inner html of Panel2 and some 
        // table cell with the id 'testTD' with the string
        content += TokenizeInnerHtml(insert, Panel2.UniqueID);
        content += TokenizeInnerHtml(insert, "testTD");
 
        // Set value of TextBox2 to the string
        content += TokenizeValue(insert, TextBox2.UniqueID);
 
        // change the background color of TextBox2 to red
        string property = string.Format(
            "document.getElementById('{0}').style.backgroundColor",
            TextBox2.ClientID);
        string value = "'red'";
        content += TokenizeProperty(value, property);
 
        // Set focus to TextBox2
        content += TokenizeFocus(TextBox2.ClientID);
 
        // write the content with the extra tokens
        writer.Write(content);
    }
     
}



Of course, that doesn't solve my initial problem, of speeding up the Ajax rendering of large grids. That's because, even if I would solve the ViewState issues and the quirks that are bound to appear, I still can't change the innerHTML property of tables or table rows, as it is a readonly property.

So where am I to use this? It's easy: first of all, put a button (and only a button) inside an UpdatePanel. Any click on that button will trigger an Ajax postback, but will send a minimal amount of data. Then, put outside the UpdatePanel a Panel. Now you can override the Render of the page and on every Ajax postback, add whatever HTML you want to that panel. If you want to do it from javascript, put the button in a div with style="display:none" and then trigger the button click whenever you want to cause the postback. I am certain that for large readonly grids, that is a way faster method than the putting the grid inside the updatepanel.

To do this the traditional Atlas way you would have had to declare a web service, and then to set a javascript onclick event on the button, that would have executed a WebService method that returned a string, and manually change the innerHTML of the panel.

If you look for solutions to get rid of huge ViewStates from your pages you will get a lot of people telling you to override SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium in your pages and do complicated stuff like keeping the ViewState in the Cache or in a database, calculating strange keys, etc.

No more! Net 2.0 has something called a PageStatePersister. It is an abstract class and every Page has one. In case no override occurs, the default is a HiddenFieldPageStatePersister, but you can also use the provided SessionPageStatePersister like this:
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}


And that's it! It works with Ajax and UpdatePanel, too.

However, this is no "Silver Bullet", as the SessionPageStatePersister will have issues with multiple windows open with the same session (like pop up windows) as exampled in this nice article. Also check out this situation when, during Ajax callbacks, a full ViewState is returned due to the ever troublesome ImageButtons.

There is no reason not to create your own PageStatePersister, though. The abstract class is public (not internal and sealed as Microsoft likes their most useful classes) and you can inherit it. You can even store the state in the Cache! :)

A very comprehensive article on ViewState is here.

A previous post of mine detailed the list of ASP.Net controls that cannot be used with UpdatePanel and ASP.Net Ajax. Since I provided a fix for the validators earlier on, I've decided to try to fix the Menu, as well. And I did! At least for my problem which involved using a two level dynamic menu inside an UpdatePanel.
Here is the code:
<script>
function FixMenu() {
if (typeof(IsMenuFixed)!='undefined') return;
if (!window.Menu_HideItems) return;
window.OldMenu_HideItems=window.Menu_HideItems;
window.Menu_HideItems=function(items) {
try {
OldMenu_HideItems(items);
} catch(ex)
{
if (items && items.id) {
PopOut_Hide(items.id);
}
}
}
IsMenuFixed=true;
}
</script>

Now all you have to do is load it at every page load:
ScriptManager.RegisterStartupScript(this,GetType(),"FixMenu","FixMenu();",true);


Explanation: the error I got was something like "0.cells is null or not an object", so I looked a little in the javascript code, where there was something like " for(i = 0; i < rows[0].cells.length; i++) {" and rows[0] was null. All this in a function called Menu_HideItems.

Solution 1: Copy the entire function (pretty big) and add an extra check for rows[0]==null.

Solution 2: Hijack the function, put it in a Try/Catch block and put the bit of the original function that appeared after the error in the catch. That I did.

Did you get a "0.cells is null or not an object" Javascript error while trying to use UpdatePanel and something like Menu or TreeView? The reason is that some controls are incompatible with UpdatePanel! Most amazingly, the newest controls seem to be the least compatible.

Update 2nd of July 2008:
The solution below doesn't always work. For a validator to not work you need to have .NET 2.0 installed without installing Service Pack 1 for it and you also need to add the validator dynamically (so it is not there when the page is loaded, but it appears there during an async postback).

The problem lies in the BaseValidator class itself, after the postback, the validator is not in the Page_Validators array. I've tried rehooking the validators, even enumerating all validators in the page and manually entering them in the Page_Validators array. It does not work. Mainly because the html spans of the validators are not the same thing as the validators and stuff like the id of the control to validate and other details are never rendered.

So you absolutely need to install the .Net 2.0 SP1 in order to use UpdatePanels with validators.
=====

Today I've encountered a strange error, where the validation did work inside an UpdatePanel, but the validator message would not appear. This in a project where I used validation in GridViews inside their own UpdatePanel and they worked!

So I guess part of the reason the error occurs could be any one of these:
  • validated control and validator are both in a UserControl
  • validator is loaded at start of the page, it does not appear during Ajax calls, but it is replaced during Ajax calls
  • validated control is a ListBox, worse, an object inherited from a ListBox


Anyway, the true reason why the validators behaved in this fashion was that they were different from the html node of the validators. In other words, the Javascript array Page_Validators was filled with the validators correctly, did have the evaluation function, did return isvalid=false, but then, when the style.display attribute was changed to inline, the validator was not part of the html page DOM, as it was changed by Ajax.

Solution: a Javascript function that enumerates the validators, gets the validator DOM node by way of document.getElementById, stores the validator properties into this node and then replaces the element in the Page_Validators array.
Problem: where should one load it? Possible solutions include submit button onclick events, form onsubmit events and in the Page_Load method. I would not use onclick, since I should have added onclick events on all possible submit buttons. I would not use RegisterOnSubmitStatement, since it ran the code after checking if the validators are valid or not (even if the validation itself took place afterward; now that is weird). The only solution is to use Page_Load.

There are various ways of doing that, too, since you could use MasterPages, custom made Page objects and even HttpHandlers or HttpModules. You also could have custom controls or objects that don't have a reference to the Ajax ScriptManager object or even to the Ajax library itself. Yes, I know, I am very smart. In my project, all the pages were inherited from a custom Page object, also in the project. So it was relatively easy.

Here is the code:

// in Page_Load
string script=@"
if (typeof(Page_Validators)!='undefined')
for (var i=0; i<Page_Validators.length; i++)
{
// get DOM node
var vld=document.getElementById(Page_Validators[i].id);
if (vld) {
for (var key in Page_Validators[i])
// check if the Page_Validators element has extra attributes
// and add them to the node
if ((vld[key]==null)&&(Page_Validators[i][key]!==null))
vld[key]=Page_Validators[i][key];
// replace the Page_Validators element with the reconstructed validator
Page_Validators[i]=vld;
}
}
"
;
// get current ScriptManager for this page
ScriptManager sm=ScriptManager.GetCurrent(this);
if ((sm!=null)&&(sm.IsInAsyncPostback)) {
// if we did an Ajax postback, fix validators
ScriptManager.RegisterStartupScript(Page, GetType(),"FixValidators",script,true);
}

Ok, so you have the greatest control library ever made and Microsoft releases Asp.Net Ajax and none of them work anymore. What is one to do?

Eilon Lipton to the rescue! He writes a very good article about Ajax enabling your controls without linking to the System.Web.Extensions dll.

However, the article is a bit outdated. Here is a piece of code that solves the problems (at least for the latest version of Asp.Net Ajax):
Type scriptManagerType = Type.GetType("System.Web.UI.ScriptManager, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
 if (scriptManagerType != null)
 {
 RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource", new Type[] { typeof(Control), typeof(Type),typeof(string) });
 RegisterStartupScriptMethod = scriptManagerType.GetMethod("RegisterStartupScript", new Type[] { typeof(Control), typeof(Type), typeof(string), typeof(string), typeof(bool) });
 }


This is because the namespace has changed since the writing of Elion's article from Microsoft.Web.UI to System.Web.UI and there are two methods named RegisterClientScriptResource and two named RegisterStartupScript so you have to get the right one. Else you get the "Ambiguous match found" error.

There you have it!

Update: If you've experienced random PageRequestManagerParserErrorException errors that seem to vanish at a simple page refresh, read this post instead: An intermittent PageRequestManagerParserErrorException

I've built a TranslationFilter object that tries to.. well... translate ASP.Net pages. Everything ran smoothly until I had to use ASP.Net Ajax. I got the infamous error "Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.".

Starting analysing the problem, I soon understood that the Ajax requests go through the same Response mechanism as a normal page request, but the output is different. In case of normal page requests you get the HTML of the page, while in Ajax requests you get something formated like this:
contentLength|controlType|controlID|content|


If one uses Response.Write, the text is inserted both in the page HTML and the Ajax request format, resulting in something like "blablabla10|updatePanel|UpdatePanel1|0123456789" which cannot be parsed correctly and results in an error. The ScriptManager.IsInAsyncPostBack property shows us if the request is Ajax or not, so we can condition the Response.Write on this.

Also, if changing the content with a HttpResponse.Filter, the length of the content is no longer equal with the declared value. So what must be done is first detect if the content is Ajax. Unfortunately we cannot check the state of the ScriptManager from inside the HttpResponse.Filter, but we can check the format of the string to modify, then modify the content AND the contentLength, else it will all result in error.

Update: the content might not be changed by you! As one of the people asking me for help on the chat discovered, the web server provider might want to put in some ads, regardless if the request is an Ajax one, thus breaking the format. You need to patch the javascript ajax engine in order to work, that means changing the content the javascript function will get in order to not cause errors. You may find the solution here.

As an example, here is my Translate method:
        private string RecursiveTranslateAjax(string content)
        {
            Regex reg = new Regex(@"^(\d+)\|[^\|]*\|[^\|]*\|",
                         RegexOptions.Singleline);
            Match m = reg.Match(content);
            if (m.Success)
            {
                int length = To.Int(m.Groups[1]);
                reg = new Regex(
                         @"^(\d+)(\|[^\|]*\|[^\|]*\|)(.{" + length + @"})\|"
                         , RegexOptions.Singleline);
                m = reg.Match(content);
                if (m.Success)
                {
                    string trans = Translate(m.Groups[3].Value);
                    return trans.Length + m.Groups[2].Value 
                       + trans + "|"
                       + RecursiveTranslateAjax(content.Substring(m.Length));
                }
            }
            return Translate(content);
        }


Update:
I met this problem also when in the page there were Unicode characters. Everything works perfectly, then you can't postback anything, because some user text contains Unicode chars. The solution I used for this was to get the offending text (whether in Page.Render or in some other places based on specific situations) and take every character and check if it is ASCII. Web Pages should be UTF8 so any character bigger than 127 should be translated into a web page Unicode char &#[char code];

The code:

string s=[my string]
StringBuilder sb=new StringBuilder();
for (int c=0; c<s.Length; c++)
{
if (s[c]>127) sb.Append("&#"+((int)s[c])+";");
else sb.Append(s[c]);
}
s=sb.ToString();


Here is the full code
    private string RecursiveTranslateAjax(string content)
    {
        // look for the basic Ajax response syntax
        Regex reg = new Regex(@"^(\d+)\|[^\|]*\|[^\|]*\|", 
              RegexOptions.Singleline);
        Match m = reg.Match(content);
        // if found, search deeper, by taking 
        // into account the length of the html text
        if (m.Success)
        {
            // custom method to get an integer value
            int length = To.Int(m.Groups[1]); 
            reg = new Regex(@"^(\d+)(\|[^\|]*\|[^\|]*\|)(.{" + length + @"})\|",
                  RegexOptions.Singleline);
            m = reg.Match(content);
            if (m.Success)
            {
                string trans = Translate(m.Groups[3].Value);
                return
                    trans.Length + m.Groups[2].Value + 
                    trans + "|" + 
                    RecursiveTranslateAjax(content.Substring(m.Length));
            }
        }
        // if not Ajax, just translate everything,
        // it must be a normal PostBack or a string of some sort.
        return Translate(content);
    }
 
    // this method only fixes the weird characters
    // but you can put here any string change you would like
    // like search and replace some words.
    private string Translate(string content)
    {
        // Html code all chars that are not ASCII, thus getting rid of strange or Unicode characters
        StringBuilder sb = new StringBuilder();
        for (int c = 0; c < content.Length; c++)
        {
            if (content[c] > 127) sb.Append("&#" + ((int) content[c]) + ";");
            else sb.Append(content[c]);
        }
        return sb.ToString();
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        //base.Render(writer);
        // render to my own text writer
        HtmlTextWriter tw=new HtmlTextWriter(new StringWriter());
        base.Render(tw);
        // get the Rendered content of the page
        string content = tw.InnerWriter.ToString();
        content = RecursiveTranslateAjax(content);
        writer.Write(content);
    }


To.Int method
public static int Int(object o)
{
    if (o == null) return 0;
    if (IsNumericVariable(o)) return (int) CastDouble(o);
    string s = o.ToString();
    if (s == "") return 0;
    Match m = Regex.Match(s, "(-\\d+|\\d+)");
    if (m.Success)
        try
        {
            return Int32.Parse(m.Groups[0].Value);
        }
        catch
        {
        }
    return 0;
}
private static double CastDouble(object o)
{
    if (o is byte) return (byte) o;
    if (o is int) return (int) o;
    if (o is long) return (long) o;
    if (o is float) return (float) o;
    if (o is double) return (double) o;
    if (o is decimal) return (double) (decimal) o;
    throw new ArgumentException("Type is not convertable to double: " + o.GetType().FullName);
}

First of all, to turn a normal ASP.NET application to Ajax takes only a few minutes with the Microsoft Ajax.Net platform. It's as easy as moving some of the content in UpdatePanels and adding a ScriptManager. Of course, you need the Ajax.Net package installed.

What I am going to talk about is a simple way to enable/disable Ajax, and keeping the entire basic functionality intact. Why would I do that? Because sometimes you need to see the project working on a computer that doesn't have the Ajax framework installed. You might even want to work on the project itself. So this is what you do:

First of all, the ScriptManager control must appear on every Ajax page in the project, so why not move it to the MasterPage? Yes. Only one ScriptManager in the MasterPage suffices. Second of all, the UpdatePanel and the UpdateProgress controls are nothing more than normal Panels with the cool Ajax functionality added to them. So enabling or disabling Ajax surmounts to nothing more than replacing these controls with normal panels.

So here is the quick method of enabling, disabling Ajax.Net whenever you want:
Start off from the Ajax enabled application and save the web.config as web.config.ajax. Remove everything from it that resembles System.Web.Extensions and all other weird things that Ajax.Net adds to the web.config except this:
<system.web>
<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</controls>

this you only replace with this:
<system.web>
<pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="MockAspNetAjax"/>
</controls>

Save it to Web.config.noajax

You might see where I am going already. Now create a new Visual Studio project, a class library, one that you will use for all these conversions, and call it MockAspNetAjax. Go to the project Properties and change the default namespace to System.Web.UI. Add three classes that do nothing but inherit from Panel: ScriptManager, UpdatePanel, UpdateProgress. The UpdateProgress will have some additional code:
 public UpdateProgress()
{
Init += new EventHandler(UpdateProgress_Init);
}

void UpdateProgress_Init(object sender, EventArgs e)
{
Visible = false;
}

because you don't want to see the Ajax Update Progress message continuously on your page.

In order to convert an ASP.NET Ajax application to a normal postback application you follow two simple steps:
1. overwrite the web.config with web.config.noajax
2. add MockAspNetAjax as a reference or include in the project if previously excluded.

back to Ajax:

1. overwrite the web.config with web.config.ajax
2. remove MockAspNetAjax as a reference or exclude the dll from the project, while keeping the dll there.

That's it!

Of course, Ajax has a lot more stuff to it, like for example the [ScriptService] web services that are directly accessed from Javascript. Or Ajax enabled controls or other controls which don't even work without the framework. These are more complex situations which cannot be solved with such a general solution, but the same basic principles apply: use web config to avoid Register tags in each page, replace controls with mockup controls, remove HttpHandlers and HttpModules, replace javascript code that uses Ajax with something that does nothing or emulates the same behaviour (preferably) through postbacks or hidden iframes, etc.