Monday, September 29, 2014

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas volutpat est et ultrices convallis. Donec erat mauris, semper sed neque eu, interdum elementum magna. Fusce dignissim, justo ac ornare pharetra, ligula sapien sodales nibh, at ultricies est nulla quis justo. Morbi fringilla, erat pretium egestas auctor, enim eros placerat sem, ac rhoncus dui est vitae lorem. Donec in eleifend sem.

Friday, October 23, 2009

ASP.NET Ajax History and callbacks

A few weeks ago I run into a problem with the ASP.NET Ajax History, when I enabled it on my site my callbacks to the server broke and I started to investigate it and found the solution finally.


I found that the WebForm_DoCallBack is using the pages form action as the url to load the callbacks and when the hash like MyPage.aspx#h=352564 is added to the page by the History the callback is invalidated and it throws an exception and the callback won't go through.

So to fix it I made a HttpModule that altered the WebForm_DoCallBack function and removed the history section from the calling url like:

First in the HttpModules BeginRequest event I registered a filter for all .axd files:

        void context_BeginRequest(object sender, EventArgs e)
{
if (this._context.Request.Url.AbsoluteUri.IndexOf(".axd", StringComparison.InvariantCultureIgnoreCase) != -1)
{
this._context.Response.Filter = new CallBackFilter(this._context.Response.Filter);
}
}

Then in the filters Write method I did:


private Stream stream;
private StreamWriter streamWriter;
StringBuilder responseHtml;

public CallbackFilter(Stream strm)
{
stream = strm;
responseHtml = new StringBuilder();
streamWriter = new StreamWriter(stream);
}

public override void Write(byte[] buffer, int offset, int count)
{
string strBuffer = Encoding.Default.GetString(buffer, offset, count);

if (strBuffer.Contains("WebForm_DoCallback"))
{
Match m = Regex.Match(strBuffer, "(xmlRequest\\.open\\(\"POST\", )(theForm.action)(, true\\);)");
string newString = m.Groups[1].Value + m.Groups[2].Value + ".replace(/(#h=\\d+)/, '')" + m.Groups[3].Value;
strBuffer = strBuffer.Replace("xmlRequest.open(\"POST\", theForm.action, true);", newString);


responseHtml.Append(strBuffer);
string finalHtml = responseHtml.ToString();
byte[] data = Encoding.Default.GetBytes(finalHtml);
streamWriter.Write(finalHtml);
streamWriter.Close();
}
else
{
stream.Write(buffer, offset, count);
stream.Close();
}
}

You should notice that the ".replace(/(#h=\\d+)/, '')" is injected to the JavaScript and it assumes that the History key is h and the value is add numeric. Finally I registered the module in web.config and expected it to work, but no.

I Googled a little and found that the HttpModules don't handle .axd files correctly. The reason is that the writer that writes the JavaScript, CSS etc. out doesn't allow any more writes to it, and this can be solved only with reflection in the modules PostRequestHandlerExecute-event like this:

        void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
var response = ((HttpApplication)sender).Context.Response;
var httpWriterField = typeof(HttpResponse).GetField("_httpWriter",
BindingFlags.NonPublic BindingFlags.Instance);
var ignoringFurtherWritesField = typeof(HttpWriter).GetField("_ignoringFurtherWrites",
BindingFlags.NonPublic BindingFlags.Instance);
var httpWriter = httpWriterField.GetValue(response);
ignoringFurtherWritesField.SetValue(httpWriter, false);
}

That was a tip from another blog.

Now the callbacks work perfectly and the line:
xmlRequest.open("POST", theForm.action, true);
in the ASP.NET JavaScript resource file is replaced with:
xmlRequest.open("POST", theForm.action.replace(/(#h=\\d+)/, ''), true);

I'm sure that the are other uses for this "injection" technic too.

This is not the cleanest solution to this problem, I'm sure, but it works!

Hello

Hello