Thursday, March 12, 2009

ASP.Net UpdatePanel and overriding Page.Render don't mix

I have a website. Well, I have lots of websites but I have one in particularly that has special needs. It needs to be multi-lingual but at the same point it is set up so that translators can change the translations on the fly.

This last point is a killer and renders the conventional ASP.Net localization mechanism null and void. ASP.Net uses resource files for localization. If a translator changes some text, they won't see the change until you rebuild the files for that language. And when you do that, the entire site gets recycled. For everyone. Ick.

Anyway... long story short. I implemented what seemed like a good idea at the time. Whenever there was a block of text I had my controls simply render out text placeholder. For example, $$TextID:123$$. I was then intercepting the Page.Render event and RegEx matching all of these placeholders, building a list of all text IDs to look up, and then doing a single database call to get the results back, then modifying the output on the way through.

At first, all seemed good, but two issues arose.

The first of them is a /facepalm of my own, which is that the approach only works when no post-processing is required on the text. For example, if I was pulling out text that used a wiki markup and I needed to translate that to HTML, it was going to try to interpret $$TextID:123$$ as wiki markup when the control was rendered, and then wasn't going to apply the markup formatting in the Page.Render unless I built something a bit more intelligent.

Fair enough, but doable. But in the end I'm rolling back the approach entirely because, as it turns out, overring Page.Render really borks up the AJAX UpdatePanel.

Now let's be fair: the UpdatePanel is actually a very blunt instrument - too blunt, really - and should be avoided if a more targetted solution can be obtained. If all you're doing is updating HTML text for example, for heaven's sake use web services consumed by the client - they have so much less overhead that UpdatePanels and don't required an entire page postback just to change one bit of text.

However, sometimes you already have UpdatePanels there and sometimes you just need something quick and dirty.

But...

Try the following code on the Render method and check out the result when it's an UpdatePanel postback.


protected override void Render(HtmlTextWriter writer)
{
using (System.IO.MemoryStream msOur = new System.IO.MemoryStream())
using (System.IO.StreamWriter swOur = new System.IO.StreamWriter(msOur))
{
HtmlTextWriter ourWriter = new HtmlTextWriter(swOur);
base.Render(ourWriter);
ourWriter.Flush();
msOur.Position = 0;

using (System.IO.StreamReader oReader = new System.IO.StreamReader(msOur))
{
string sTxt = oReader.ReadToEnd();
}
}
}


...and check out the value of sTxt

Normally you'll see your page in all its full HTML glory, but in an UpdatePanel postback, you get a completely different kind of markup. Something containing lines like this:

|5|onSubmit||null;|90|onSubmit||if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;|


That's only part of it, but it gives you the idea. Now, here's the important bit: those numbers in the pipelines (|5| and |90| for example) actually contain the LENGTH of the text parameter to follow (the "null;" shortly after the |5| is 5 chars long, for example).

So, if you take your rendered output and change "$$TextID:123$$" to "I am a telephone pole" then your client page is not going to interpret the result correctly any more (unless all of your strings happen to be exactly the same length as the ID placeholders).

On its own, this is not insurmountable, but it's a pain for a number of reasons:


  • If the AJAX team had chosen XML for communicating the UpdatePanel changes to the client, it would have been much easier to overcome this: by simply parsing the XML using the built-in parsers and changing the value of a given length field. Without XML, there's much heavier parsing work required. There may well be good reason why they didn't use XML - I'm not saying it was a wrong decision, I'm just lamenting it...

  • It's also a pity that the formatting that they do - where they take the page's HTML and convert it to this pseudocode - seems to be somehow happening internally WITHIN the default Page.Render method. It would be nice if this conversion happened AFTER the Page.Render method so that we could intercept it. What we want to do is to get an override in between the point when the full HTML is generated and when AJAX strips out all the stuff that it doesn't need to send back to the client and creates pseudo-code out of what it does. But because that seems to happen inside Page.Render, I'm not sure it's possible.

  • Finally, in conjunction with the formatting issue I mentioned at the very beginning, I think I'm going to file this approach in the "not worth it" basket. A nice idea, but I think there are better ways to optimize.



But the lesson that I've learnt today is: never override Page.Render to tweak the output OR never use an AJAX UpdatePanel, as the two aren't talking to each other right now. Personally, I think that the UpdatePanel is the one to chuck, but that can be tricksy if you've already got it in there somewhere.

1 comment:

FlameWolf said...

Good post. I'm also facing the same issue.

Any idea how to fix this...?