Friday, July 14, 2006

ASP.NET 2.0 migration

We're in the middle of migrating all of our websites to the latest version of the Microsoft KoolAid, in this case .NET 2.0 (and ASP.NET 2.0, C# 2.0, and probably SomethingElse 2.0 too).

We did a fair amount of prep work this, reading the various migration guides, talking to another group at Newsgator that has already migrated, I even read the entire list of breaking changes. Of course we still had difficulties, so here is my list of lessons learned.

Field name separator changes

The names for form fields in ASP.NET are constructed by concatenated all the parent control IDs with a delimiter in between. In ASP.NET 1.1 that delimiter was always a colon character. So a field might have a name like "ctl1:ctl2:ctl3:txtTextBox" (if you're not into giving your controls good IDs).

Under 2.0 the delimiter has changed - sometimes. It turns out the which delimiter you get is determined by the xhtmlConformance attribute in your web.config. I which values give you which delimiter is left as an exercise for the reader.

Of course, you're never supposed to construct these name strings yourself - that defeats the whole purpose of ASP.NET. I imagine that's why this wasn't listed as a breaking change (it wasn't, I checked). But people still do it. People like us, evidently.

Event Validation

ASP.NET 2.0 added a new feature called Event Validation. I found a decent explanation for it at OdeToCode. It's a neat sounding feature, and the implementation sounds quite clever, but I'm still baffled and annoyed by this feature. Isn't the first rule of web development "Thou shalt not trust the client"? Haven't we all learned to validate all input from the client? If you're checking input like you should be, this feature is totally redundant. On top of that, it increases page bloat by adding the validation data to the page source and throws unfriendly exceptions to the users.

Turning this off was a no-brainer.

Web Project Formats

With the upgrade to Visual Studio 2005 Microsoft inexplicably decided to change the way web projects are handled. They've already been badly beaten up about this, and frankly they deserved it. It was a really dumb idea. But they've partially atoned for the sin by releasing a patch to support the old formats, which they're now calling the Web Application Project format, as opposed to the Web Site format.

This is all great, but apparently Visual Studio never got the memo. Some of our developer's machines insist on switching our projects to Web Site format every now and then. Just in the last week I've had to rollback that particular change two or three times. Even though it's a fixable and well documented issue, it still really really sucks.

FormsAuthentication changed more than you think

A couple important changes were made to FormsAuthentication. One is a bugfix, the other just a bug.

The bugfix is that in 1.1 if you set a persistent cookie it's lifetime was always 50 years, regardless of what the timeout attribute in the web.config said. In 2.0 the timeout value is respected. Unfortunately it was very easy to rely on that broken behavior before, as we were.

The outright bug is in the FormsAuthentication.GetLoginRedirect() function. This function is supposed to return the original URL the user was trying to get to before he was redirected to the login. But it turns out that if that URL had any query string parameters in it you get an exception like this instead:

System.Web.HttpException: The return URL specified for request redirection is invalid.
at System.Web.Security.FormsAuthentication.GetReturnUrl(Boolean useDefaultIfAbsent)
at System.Web.Security.FormsAuthentication.GetRedirectUrl(String userName, Boolean createPersistentCookie)

The only post we could find on the internet was from this post on HackedBrain. He is of the opinion that only certain characters cause the problem. We didn't test enough to figure it out, instead we just hacked together some replacement code:

string redirectUrl = Request["ReturnUrl"];
if(redirectUrl == null){
redirectUrl = Request.ApplicationPath + "/default.aspx";

Clientside Event Handlers

Perhaps the goofiest undocumented change is the handling of Javascript event handlers. We discovered this error in our third-party forums package. It was adding a clientside event handler using code like this:

myForm.attributes["onsubmit"] = myButton.ClientID + ".disabled = true;";

The control would then be rendered out with that attribute set as specified. For instance:
<form onsubmit="_ctl0_myButton.disabled = true;">
It took me a while to figure out how this ever worked. The onsubmit handler is executed in the context of the form, essentially the magical this pointer is set to the form. And the form automatically gets all the form fields set as properties. So the button is defined in that context. This is clever, but as it turns out it's too clever by half.

Turns out the vendor isn't the only one that's too clever by half. The 2.0 framework no longer blindly sticks things into attributes when you tell it to. It actually puts your script into a function and then calls the function. So the previous example might be rendered out like this:
<form onsubmit="WebForm_Submit()">
<script type="text/javascript">
<!-- function WebForm_Submit(){
_ctl0_myButton.disabled = true;
} -->
That extra level of indirection is enough to break things. The button variable _ctl0_myButton is no longer defined, and a script error is thrown.

How would one fix this? I've got some ideas, but this was in a piece of off the shelf software, and the beauty of COTS software is that it's NMFP (Not My Freakin' Problem).

To be continued...


Matthew Lyon said...

I just encountered the error in FormsAuthentication.GetLoginRedirect().
Looking at it with Reflector and the problem is a check it uses:
In there there is a check for the path to be http: or https: which will return true, but if the path is relative then it uses this:
int num = s.IndexOf(':');
if (num == -1)
return false;

It is a very poor check for cross site scripting I must say

BKR said...

Matt - I'm a little surprised anyone is still using ASP.NET 2.0.

ASP.Net Migration said...

Great news about migration.