Monday, 19 February 2007

ASP.Net Ajax and Response.Write or response filters The message received from the server could not be parsed.

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);
}

Wednesday, 14 February 2007

Berserk fans, read on! :)

Berserk imageOk, a few days ago I wrote this post that basically said Inuyasha was a great anime and that the manga goes on further and the story is still going. Here I am telling the same thing about Berserk. The story is a cross between Flesh+Blood and Hellraiser. The anime ends suddenly at chapter 11, but the manga has reached chapter 32 and goes on. You can find the manga online here and here and here.

If you can read Japanese, here is the official site for Berserk.

Monday, 12 February 2007

Religion and school

Anyone who knows me knows what my stance is on this. School is something, religion is something else. There are a bunch of (traditional) ways to teach religiousness to a child, in the home as well as in church, special camps :), etc. I see no need for a religious education forcefully imposed on children inside the school, especially in a country that is not that religious. Where is the separation between state an church here?!

Yes, I know that the statistics say 85% of the population of Romania is orthodox, but that's only in name. True, active, church going religious people belong to distinct demographics like old people, some women, or people in the country side. This new surge of religion in Romania is nothing but a scam! Yes, nothing more than a marketing move.

And yet, here it is, a law that (again) makes religion one of the compulsory classes in Romanian schools. If you don't want religion, the parent must write a letter to the school requesting for it to be replaced with "moral-religious education", whatever that is.

So, what if I don't want any religion for my kid? I consider religion, especially organised religion, nothing more than a ploy to prey on human stupidity and fear. I want to raise my child away from superstitious crap. Let him watch Jesus on TV, the same as Ghost Busters!

Let me analyze what I should do in order to insure that my hypothetical offspring is not getting in school an education that contradicts that at home. First step: write the letter that places him in a special class of "moral-religious education". What that essentially does is place the child in a different group, a ready prey for ridicule or any kind of discrimination. Then he sees that in his group there are only the people that consider religion ridiculous, while in the other (larger group) there are the religious kids along with the ones that don't care about it one way or the other. So he will immediately feel the pressure of being in an apparent minority. This is a clear case of discrimination any way you put it, yet they intend to make this a law. What kind of crappy legislators do we have?! AND, after all this, I only managed to get my kid into a less religious class, probably with a priest or highly religious man as a teacher.

What would these religious people think if we were to add a compulsory "God does not exist and religion is stupid" class? They would shout much and high about discrimination and the right to choose and the local decision of allowing religious symbols or education in schools. Yet they have no problem with refusing the right to choose of atheists. Religious hypocrites? what a shock!

Here is a link to a discussion from 2004 about the legality and ethics of compulsory religious classes and their content.

Inuyasha fans, read on!

InuYasha imageUpdate 11 Oct 2009: A new anime started, continuing the InuYasha story, although some people seem to not agree with the interpretation of the manga. It is called Inuyasha Kanketsu-hen (or Inuyasha Final Act) and it starts where the first anime ended. Having just seen the first episode, I must say that I can barely understand what is going on anymore. There are so many characters and I don't really remember how they cam to be as they are now. So my recommendation is to read the chapters of the manga from about chapter 360. Here is another link where you can read InuYasha chapters online: Inuyasha on OneManga.

A while ago I've seen the Inuyasha anime series and fell in love with it. But at that time I didn't have a blog and I also thought that most Japanese anime is that good. As written in my review back then, I was soon crying for more episodes when it ended. I got a bit upset, too, because the series didn't end anywhere. It just stopped!
But, even if it is something obvious, I never thought about searching for the Inuyasha manga! Yes, the anime is based on a manga series that is, as I write this blog, ongoing and (almost) close to its finish. And you can read it! Free! online! :)

Ok, here is the link: Read on!

Friday, 9 February 2007

IE annoying message and bug

Internet Explorer has a lot of "features" that are completely useless. One of them is the infamous "Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus.". So what?! Just return a false value! Why do you have to throw an error?
Anyway, I've stumbled upon a bug accidentally while reviewing a html control on DynamicDrive. This is the basic setup:
<style>
.div {
display:none;
}
</style>
<script>
function loadGoogle() {
document.getElementById('div').style.display='';
document.getElementById('iframe').src='http://www.google.com';
return false;
}
</script>
<div class=div id=div style="">
<iframe id=iframe src="" ></iframe>
</div>
<a href="#" onclick="loadGoogle()">Click me!</a>


And it returns an error. Why? Because the javascript on Google tries to focus something:
document.f.q.focus();
But the display is cleared before changing the src. For all purposes, the div has the display set to an empty string (and you can check it with an alert right after it is set). The funny thing is, and that might come as a surprise, that if you move the display:none to the style attribute of the div, the script above doesn't return any error!

So, bottom line: attribute set in style tag or css cannot be changed dynamically without an error, while a control attribute can. What's up with that?! (Bug found on IE7.0). If you encounter this weird error, try moving the display attribute (I couldn't replicate the bug with the visibility attribute) in the control style attribute.

Tuesday, 6 February 2007

ASP.NET Ajax, enabling/disabling Ajax on the same software project

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.