Sunday, November 29, 2009

Automatically compress embedded JavaScript resources with Microsoft Ajax Minifier

Recently Microsoft has announced yet another useful addition to an ASP.NET developer tool belt: Microsoft Ajax Minifier, a tool that enables you to reduce the size of a JavaScript file by removing unnecessary content from it. Clearly this is an extremely useful tool and the ASP.NET development community has gladly embraced it (just google it up to see a lot of positive responses). The tool indeed works very well and there have even been a number of articles comparing it to other well-known JavaScript compression tools that proved its quality. So there are no doubts whether to use the Minifier or not.

The question however is how to use it. Microsoft Ajax Minifier package does include a set of documentation that explains how to use the tool and even discusses a couple of usage scenarios. Unfortunately the way I wanted to use the Minifier is not covered in the documentation and for some reason I haven't found useful information on the Internet too so I've decided to do some research and share my findings with the community.

So how would I like to use the Minifier? Here are the requirements:
  1. I want to automatically compress embedded JavaScript resources in any project of my web application solution.
  2. I want the compressed JavaScripts have the original names so I don't have to change the references in HTML mark-up.
  3. I want the compression to be done only when I switch to the Release mode in Visual Studio. When I am in the Debug mode I want all the JavaScript files to be uncompressed for easier debugging.
  4. I want compressed JavaScript files never overrides the original JavaScript files and I don't want to keep the compressed JavaScripts so whenever I modify my JavaScript code the compressed resources are always up-to-date.

I believe that the requirements above describe one of the most common web application solution configuration so if there is a way to achieve them it would be very useful.


Can I achieve the requirements above with the Microsoft Ajax Minifier? The answer is yes. The solution is kind of obvious: since the Microsoft Ajax Minifier includes an MSBuild task I just need to modify a project file where I have embedded JavaScript resources that I need to be compressed to include the Microsoft Ajax Minifier build task. It does not sound complicated and below is the solution which is simple indeed. Just include the following XML in your project file right before the closing </Project> tag (in most cases):


<!-- Minify all JavaScript files that were embedded as resources -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<PropertyGroup>
<ResGenDependsOn>
MinifyJavaScript;
$(ResGenDependsOn)
</ResGenDependsOn>
</PropertyGroup>
<Target Name="MinifyJavaScript" Condition=" '$(ConfigurationName)'=='Release' ">
<Copy SourceFiles="@(EmbeddedResource)" DestinationFolder="$(IntermediateOutputPath)" Condition="'%(Extension)'=='.js'">
<Output TaskParameter="DestinationFiles" ItemName="EmbeddedJavaScriptResource" />
</Copy>
<AjaxMin SourceFiles="@(EmbeddedJavaScriptResource)" SourceExtensionPattern="\.js$" TargetExtension=".js" />
<ItemGroup>
<EmbeddedResource Remove="@(EmbeddedResource)" Condition="'%(Extension)'=='.js'" />
<EmbeddedResource Include="@(EmbeddedJavaScriptResource)" />
<FileWrites Include="@(EmbeddedJavaScriptResource)" />
</ItemGroup>
</Target>

What this script does is when the solution configuration is 'Release' it finds all the project embedded resource files with the extension '.js' and creates their compressed versions with the same names in the intermediate output folder where they are picked from later by the build process.


A few tips


As you may have noticed the Microsoft Ajax Minifier MSBuild task is referenced from the default folder where it was copied to by the installer. If you want to reference it from a different location, for example if you have a shared development environment and want to have similar setting for everyone, just copy two files ajaxmin.dll and ajaxmintask.dll to another location and include the <UsingTask> tag (below) instead of the <Import> tag in the script above:


<UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\..\Build\AjaxMinTask.dll" />

The presented script performs so called 'Normal Crunching' (see the Ajax Minifier documentation) of the JavaScript code which already does a pretty good job that is good enough in most of the cases: compression rate is over 50%. If you would like to turn on the 'Hypercrunching' mode (see the Ajax Minifier documentation) you only need to modify one line of the script to include the 'LocalRenaming' option of the Ajax Minifier:


<AjaxMin SourceFiles="@(EmbeddedJavaScriptResource)" SourceExtensionPattern="\.js$" TargetExtension=".js" LocalRenaming="CrunchAll" />

And the last note is: don't use the default Hypercrunching mode or RemoveUnneededCode option with the ASP.NET AJAX Framework as it does not work properly with it.

Thursday, November 19, 2009

Using the Microsoft Ajax Library 3.5 with the CDN

Recently Microsoft has announced its Microsoft Ajax content delivery network (CDN) which can significantly improve the performance of any ASP.NET AJAX web application. When it was first announced the CDN was almost useless for the current web applications based on ASP.NET 3.5 since it did not host the Microsoft Ajax library 3.5 and did not support content delivery via SSL. However in a very short period of time Microsoft was able to fix those problems (good job!) and now web applications built on ASP.NET 3.5 can benefit from using Microsoft Ajax CDN.

So how do you make MicrosoftAjax.js file being referenced from the CDN as opposed to the embedded file? Quite easy actually with the help of the ScriptManager control. Add the following declaration to a page (or Master page) where there is a ScriptManager control:

What that declaration means is that the script with a name MicrosoftAjax.js which is always automatically referenced by a ScriptManager from the System.Web.Extensions dll now should be referenced from that location: http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js.

This is how it is referenced by default:
<script src="/ScriptResource.axd?d=eYUqBJhfSVL41hIDYkBL0tfaps9hoQId_48PydfbcyWH41vNvL68sk-l7P9FLAPz7b4vtI8WkZ-ezAF0b_ZkyG52wt9oUtaQ5ezFfGBr7LY1&t=ffffffffef976216" type="text/javascript"></script>

and after we include the new ScriptReference declaration:
<script src="http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js" type="text/javascript"></script>

ScriptManager is even smart enough to automatically reference the debug version MicrosoftAjax.debug.js from the CDN when debugging is enabled in the web.config file:
<compilation debug="true">
<script src="http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.debug.js" type="text/javascript"></script>

So this is clear: we have our MicrosoftAjax.js referenced from the CDN and that improves our web application performance and saves us and the visitors some bandwidth since an internet browser will reuse the same cached copy of the MicrosoftAjax.js from the CDN for different web applications that reference it.

Using the CDN conditionally

What if we only wanted to reference MicrosoftAjax.js from the CDN when the web application is deployed to a production environment and use an embedded version in a development environment? That would make sense in order for the developers to work without having to be connected to the Internet. Once again it can be done but this time we'll need to write some code. We are going to add the MicrosoftAjax.js script reference dynamically depending on the debug value in the web.config file; we only add the script reference when debug="false":

Using the CDN via SSL

Another major case is when our web application has pages that are served via SSL. In this case we want to automatically select the correct CDN URL for the MicrosoftAjax.js. In order to do that we just modify the previous code:
So that's it for now. Enjoy using the Microsoft Ajax CDN.

Thursday, October 8, 2009

Talk about P&P Web Client Software Factory on Toronto Architecture User Group meeting

Developing modular web applications is a popular topic in development communities especially after ASP.NET MVC framework has been introduced by Microsoft. However ASP.NET MVC is not the first and only framework that can help on that way.

I'll be talking about building modular enterprise-class modern web applications with the help of MVP design pattern and Microsoft Patterns and Practices Web Client Software Factory framework on the next meeting of Toronto Architecture User Group on Thursday October 29th. To attend please register online here.

Wednesday, September 23, 2009

Problem with Internet Explorer 8 Debugger on Windows 7

I continue to evaluate Windows 7 after upgrading from Windows Vista and share some experience (mostly bad of course, so people would not step on the same problem again). As I mentioned before, overall upgrade was smooth and mostly everything works OK. However time to time I stumble upon some really annoying problems that did not exist on Windows Vista and therefore need to be solved for Windows 7 from scratch.

One of the recent problems was Daemon Tools not working on Windows 7 and it was solved by installing Magic Disc. Now there is one more thing: after upgrading to Windows 7 I am not able to use IE8 Debugger. When I pressed F12 the Debugger icon appeared on the task bar but I can not switch to its window: it's not visible.

So obviously I searched over the Internet and luckily there was one post on Stack Overflow (apart from those guessing about a window being outside of the screen) that suggested that there may be some wrong setting in Windows registry. And there are some registry keys related to IE8 debugger! So the solution was to remove WindowsPos, Pinned and UseDevToolsDebugger keys from the [HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IEDevTools] section. That completely solved the problem.

Unfortunately I can not tell whether those keys existed in Windows Vista and were incompatible with Windows 7 or their values have been incorrectly modified during the upgrade. But after removing them and starting IE8 debugger they haven't appeared again. So probably they are not needed anymore.

Thursday, September 17, 2009

How to map .ISO file to a drive in Windows 7

So, recently I have upgraded my laptop from Windows Vista to Windows 7. The upgrade process was straight forward and flawless and everything so far was OK except one thing: the Daemon Tools that I've been using for mapping .ISO image files since 2004 on various Windows systems refuses to work on Windows 7. Even Windows 7 compatibility mode does not help: Daemon Tools manages to recognize Windows 7 anyways. Luckily there is another tool that provides with absolutely the same functionality and also free: Magic Disc. It works under Windows 7 flawlessly and really makes my life easier: no need to burn ISO files on CD/DVD anymore.

Wednesday, June 24, 2009

ASP.NET user name availability check using WCSF Validation Bundle

An average web site offering membership and protected content for its members requires users to sign up by visiting a sign-up or register page that allows a user to enter some information and create an account. A couple of pieces if information that users are commonly asked to enter are a User name or Login name and user's email. One potential problem with such user input is checking that entered login name and/or email address are available for registration and haven't been used by other users. When it's the case multiple attempts of entering available values can be very annoying and frustrating for users even making them to refuse registration on a web site. So improving user experience when validating login name and email is quite an important task.

There is a number of ways of how to implement a user input validation and display an error message, for instance that one suggested by Dave Ward. In this post I am going to show you another relatively simple and elegant solution that provides with both  reliability and reasonably pleasant user experience.

The solution is based on using a ServerSideValidationExtender control from Web Client Software Factory validation bundle. The ServerSideValidationExtender control allows asynchronious invocation of a server-side validation handler that performs a validation right after a user finished entering data without a postback thus signifcantly improving the user experience at the same time leveraging robustness and security of server-side validation.

Code example

For this example I will be using a really simple markup code:

<asp:Label ID="lblEmail" runat="server" Text="Email: ">
<asp:TextBox ID="txtEmail" runat="server">
<asp:CustomValidator ID="cvEmail" runat="server"
ErrorMessage="Email is already used"
ControlToValidate="txtEmail"
onservervalidate="cvEmail_ServerValidate" />
<wcsf:ServerSideValidationExtender ID="ssveEmail"
TargetControlID="cvEmail"
ValidateEmptyText="false" runat="server" />
<br />
<asp:Label ID="lblLogin" runat="server" Text="Login: ">
<asp:TextBox ID="txtLogin" runat="server">
<asp:CustomValidator ID="cvLogin" runat="server"
ErrorMessage="Login name is already used"
ControlToValidate="txtLogin"
onservervalidate="cvEmail_ServerValidate" />
<wcsf:ServerSideValidationExtender ID="ssveLogin"
TargetControlID="cvLogin"
ValidateEmptyText="false" runat="server" />
<br />
<asp:Button ID="cmdRegister" runat="server" Text="Register" />

that effectively results in that UI in a browser:


For each TextBox control requiring asynchronious server-side validation I've added a CustomValidator control and a ServerSideValidationExtender that has its TargetID property set to an ID of a corresponding CustomValidator control.

Next I've added some code to the validation handlers for the CustomValidator controls to the page's code behind and that's it:

protected void cvEmail_ServerValidate(object source,
ServerValidateEventArgs args)
{
args.IsValid = string.IsNullOrEmpty(
Membership.GetUserNameByEmail(args.Value));
}

protected void cvLogin_ServerValidate(object source,
ServerValidateEventArgs args)
{
args.IsValid = Membership.GetUser(args.Value) == null;
}

Note that the IsValid equals true when a user can not be found. The server-side validation handler is being executed asynchroniously every time a corresponding TextBox loses its focus and if validation has failed (meaning that a user was found and login name or email is not available) a corresponding error message is displayed immediately on the page without a postback so a user does not have to click a submit button and wait for the page to render again to see the result of the validation.


The only trick here is that in the server-side validation handler we can only access a value of the control being validated because values of other controls on the page haven't been updated on the server side (no postback, remember?!).

Note. In the production code you may want to add some layout and styling to your HTML markup, and of course there will be perhaps other input boxes on a form with may be more validator controls but the approach won't change: no additional code required, the same well-known validation pattern but with nicer user experience. I would also recommend to implement a simple but very useful change to the ServerSideValidationExtender control suggested by Jarod Ferguson.

Monday, June 22, 2009

How to set programmatically a value of a watermarked TextBox via JavaScript

There is an updated version of the article for the latest Ajax Control Toolkit release: http://blog.turlov.com/2010/05/how-to-set-programmatically-value-of.html

If you use ASP.NET AJAX and AJAX Control Toolkit you may find it useful to decorate some of your TextBox controls on a page with a TextBoxWatermark AJAX extender control from the AJAX Control Toolkit. It's fairly easy to use and a nice tool for improving both the page look and user experience.

However if you need to set a value of a TextBox decorated with the TextBoxWatermark extender programmatically via JavaScript on the client-side you may notice that the behavior of the watermarked TextBox is different. If you use the regular way to set a value like this:

$get(textBoxId).value = someText;

then the value displayed in the TextBox on the page will change to the set value but the watermarked look will still be present and other AJAX Control Toolkit TextBox extenders (like a Calendar extender, for instance) will not recognize the new value.

This happens because the TextBoxWatermark extender can not recognize and intercept your JavaScript operation since it's designed to serve user interactions. However there is a correct way of programmatic access to the TextBox's value. We modify the code above like that:

var textBox = $get(textBoxId);
if (textBox.AjaxControlToolkitTextBoxWrapper) {
textBox.AjaxControlToolkitTextBoxWrapper.set_Value(someText);
}
else {
textBox.value = someText;
}

The explanation is that most of the TextBox extenders from the AJAX Control Toolkit including a TextBoxWatermark extender use a special proxy class AjaxControlToolkitTextBoxWrapper to manipulate with the input elements. The proxy adds a special property AjaxControlToolkitTextBoxWrapper to the input element that contains a reference to itself allowing to access the value of the input element correctly. Similarly, if you need to get a value of a watermarked TextBox you do it like this:

var textBox = $get(textBoxId), text;
if (textBox.AjaxControlToolkitTextBoxWrapper) {
text = textBox.AjaxControlToolkitTextBoxWrapper.get_Value();
}
else {
text = textBox.value;
}

Tuesday, June 2, 2009

New version of ASP.NET AJAX Control Toolkit released

The new release of ASP.NET AJAX Control Toolkit is available for download. Bertrand Le Roy has written a very informative post about it. What particularly concerns me in this new release is that a Color Picker extender is now included in the Ajax Control Toolkit. Hooray!

Along with the new Toolkit release there come a whole bunch of new tutorials and videos among them there are one particularly nice tutorial about the Color Picker extender and an excellent video tutorial by Joe Stagner. Thank you folks for such a great job!

What that means for the Color Picker Extender users is that they don't have to download it separately from Codeplex anymore because it is included in the Ajax Control Toolkit. For those who can not switch to the new version of ACT yet the original Color Picker Extender control will continue to be available on Codeplex. During the next few days I will prepare and upload a new release synced up with the Ajax Control Toolkit.

It is also very interesting to read people's opinions about the new release of ACT that they write in response to blog posts and on Codeplex. In general the responses are positive however some folks complain about something, mostly about insignificant things or because they missed something. I would like to remind everybody that Ajax Control Toolkit is completely a community initiative (with of course the initial kick-off help from Microsoft) and people who work on it take time for it out of their families and other important things so let's be more tolerant and if you've come across something really bad please use the Issue Tracker.

In particular I was interested in reading responses about the ColorPicker (but of course ;-)). Interestingly all the complaints were about how its look and feel, not about functionality. I admit that the UI it provides is rather simplistic but here is my question back: where have you all folks been since the control was published almost a year ago and I was asking for your feedbacks and suggestions? Since then it's been downloaded more then 2000 times (and I don't count downloads of ACT source code) and almost no one except just a few people suggested anything about improving it. I take it as everybody has been satisfied. However now with a bit more visibility I suggest if you folks really want some progress with what concerns you please speak up.

Wednesday, May 27, 2009

Survey: Ajax usage among .NET developers

If you haven’t already and you are a .NET developer, please take a couple minutes and answer this survey, whether you use Ajax or not. There are a number of Ajax surveys around, but this is the only one that focuses on .NET developers.

The survey:
http://www.zoomerang.com/Survey/?p=WEB22973CYKW2H

Friday, May 1, 2009

Enabling a WCSF solution for sub-Web Application Project

If in your WCSF solution you use Web Application Project (as opposed to Web Site Project) for your web site you are able to use a so called sub-Web Application Project in order to further modularize your web application.


If you created a WAP WCSF solution from scratch this option will be available to your all right. However if you converted your Web Site WCSF solution to WAP WCSF solution or upgraded from the previous version of WCSF this option may not appear when you are trying to add a new business module to your solution.

Visibility of the "Create as sub-Web Application Project" checkbox depends on one single property of a solution file and if you don't see the checkbox open the .sln file with any text editor and check out that it contains the follwoing content:

GlobalSection(ExtensibilityGlobals) = postSolution
...
IsWCSFSolutionWAP = True
...
EndGlobalSection

Usually this section is located in the bottom of a .sln file and the red line is the key to solving the problem. If you don't see the line in your solution file simply add it and reload the solution in Visual Studio. Problem solved.

Monday, April 27, 2009

How to Improve ASP.NET UpdatePanel Performance

Since ASP.NET AJAX UpdatePanel was first introduced it has earned a strange mix of reputation. From one hand it has become a tool of first choice for many ASP.NET developers who wanted an easy way of introducing an AJAX-like behavior for their ASP.NET web apps. From another hand it has earned a lot of criticism from seasoned web developers because of certain performance consequences associated with complex usage scenarios.

Well everything may be good and may be evil based on how we use it. From my experience consious and judicious use of UpdatePanel is the key to saving its benefits and avoiding potential problems.

Below I suggest a number of rules that help achieving better results when using UpdatePanel.
  • Avoid automatic refreshing of UpdatePanel; always stay in control of which UpdatePanel and when refreshes: set UpdateMode property to Conditional (the default value is Always).

  • Minimize the content of the UpdatePanel: the <ContentTemplate> should only include controls that are neccessary to refresh. For instance if user input requires server-side validation include only an error message mark-up in the UpdatePanel and leave the rest of the form outside.

  • Try to keep the partial postback trigger controls outside of their respective UpdatePanels unless its neccessary to change their markup.

  • Try to stick to a simple rule: one trigger for one UpdatePanel. If you need to refresh multiple UpdatePanels during one request add a trigger control to only one of those UpdatePanels and refresh the others programmatically in an event handler on the server. The idea is to avoid uncontrollable refreshing of unnecessary UpdatePanels.

  • Since ViewState is updated with every partial postback request turn the ViewState off on a page that contains the UpdatePanel wherever possible or store the ViewState on the server to avoid transferring it forth and back with every async request.

  • Since Page runs through its lifecycle during every partial postback and executes methods like Page_Load or Page_PreRender make sure that logic that is unneccessary for refreshing UpdatePanel is not executed by wrapping it in if(!ScriptManager.IsInAsyncPostBack).

  • If you use UpdatePanel event handlers like Init, Load, PreRender and Unload make sure that code inside these event handlers does not execute unless neccessary by checking Page.IsPostBack and ScriptManager.IsInAsyncPostBack properties.

  • If you trigger an UpdatePanel programmatically from the client-side (via JavaScript) make sure that its event handler check for the event trigger value using Request.Params["__EVENTTARGET"] ot avoid unnecessary execution path.

  • If you programmatically update Page's Header (Title, etc.) or other Page's content that is outside UpdatePanel make sure that this code never gets executed during partial postbacks. First of all its not neccessary since page does not refresh but also it may be dangerous because the content may not be handled properly by a browser.

Conclusion

There may probably be more tricks and tips regarding usage of UpdatePanel but those mentioned above have been proven by real exeprience. I would also recommend reading an excellent post by Dave Ward that helps understanding how UpdatePanel works behind the scene and never hesitate using Fiddler to investigate what your web app's doing.

Wednesday, March 11, 2009

Using ColorPickerExtender Client-side Events

There have been question about how to utilize the client-side functionality of the ColorPickerExtender in order to improve customer experience. Here is an example of how to use a colorSelectionChanged client event.

Subscribing to the colorSelectionChanged event

You can subscribe to the event either via JavaScript code or ASP.NET mark-up:

    var cpe = $find("ColorPickerExtender1");
    cpe.add_colorSelectionChanged(changeColor);

Here $find is a standard function of the ASP.NET AJAX Framework that returns an instance of the AJAX component by BehaviourID; ColorPickerExtender1 is a BehaviourID of the ColorPickerExtender component and changeColor is your JavaScript function that will be called when the event occurs.

<ajaxToolkit:ColorPickerExtender ID="defaultCPE" runat="server"
TargetControlID="Color1"
    BehaviorID="ColorPicker1"
    OnClientColorSelectionChanged="changeColor"
/>

Make sure that the changeColor JavaScript function exists on the page otherwise you'll see a JavaScript error "changeColor is undefined". You may define a changeColor function like this:

function changeColor(sender) {
    sender.get_element().style.color = "#" + sender.get_selectedColor();
}

When your function is called after a user picked a color (i.e. clicked a mouse on the color palette) a sender argument will point to the instance of the ColorPickerExtender component that you can use inside your function. For instance, call a method get_element() to retrieve a reference to an input DOM element which the extender is attached to or use a get_selectedColor() method to retrieve a hexadecimal color code that the user selected. When assigning the color value to a DOM element style don't forget to prepend it with a '#' character.

Thursday, January 29, 2009

ASP.NET AJAX Compatibility Patch for Safari 3.x and Google Chrome

The problem I am going to talk about is not new but it somehow managed to concern not so many developers so far. However it is a growing concern and more people would need a solution at some point.

The heart of the problem is there are two browsers with a groing number of users - Safari 3.x and Google Chrome, that ASP.NET AJAX framework is not compatible with. One part of the problem is induced by a fact that both browsers report themselves as "Webkit" which is not supported by ASP.NET AJAX. This part of the problem affects both Safari and Chrome users. The second part of the problem mostly affects Safari users because ASP.NET AJAX framework does "support" Safari (version 2.x) but in such a manner that makes your web application look a total disaster in Safari version 3.x. The suggested fix will help both cases. At least my tests showed quite a success.

Ideally we would need an official patch from Microsoft but since they have their own plans and busy with the next .NET/ASP.NET 4.0 and VS 2010 I would assume that there will be no fix till those versions are out. So let's stick to this solution for now.

The solutions for the problem were suggested by a couple of people: http://forums.asp.net/p/1252014/2898429.aspx and http://blog.lavablast.com/post/2008/10/Gotcha-WebKit-(Safari-3-and-Google-Chrome)-Bug-with-ASPNET-AJAX.aspx. Since both solutions are absolutely the same I want to give both of them a credit.

So to the point, ASP.NET AJAX framework has a class Sys.Browser that represents a current browser and supports cross-browser compatibility for everything else. The solution simply extends the Sys.Browser class to support a Webkitbrowser:

Sys.Browser.WebKit = {};
if( navigator.userAgent.indexOf('WebKit/') > -1 ) {
Sys.Browser.agent = Sys.Browser.WebKit;
Sys.Browser.version = parseFloat(navigator.userAgent.match(/WebKit\/(\d+(\.\d+)?)/)[1]);
Sys.Browser.name = 'WebKit';
}

That is all the code you need. To apply this code to your web application you will need to create a javascript file, let's say webkit.js and reference it in your application using standard ScriptManager server control. You can reference the fix as a file using this syntax:

<asp:ScriptManager ID="sm" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/webkit.js" />
</Scripts>
</asp:ScriptManager>

or embed it into an assembly (my preferred way) and reference it from the assembly as an embedded resource:

<asp:ScriptManager ID="sm" runat="server">
<Scripts>
<asp:ScriptReference Assembly="Scripts" Name="Scripts.webkit.js" />
</Scripts>
</asp:ScriptManager>

Note that when you reference the fix from an assembly don't forget to add an assembly default namespace to the file name in the Nameattribute.

In conclusion, according to my tests the fix eliminated problems with UpdatePanel, AjaxControlToolkitand Virtual Earth map control.

Thursday, January 15, 2009

Search Engine Friendly Error Handling

ASP.NET provides a standard way to handle errors in web applications by configuring a customError section in the Web.config file. Standard behaviour is to redirect a user from an erroneous page to an error page that in its turn shows some kind of more or less friendly explanation of what has happened.

This approach is quite OK for intranet web applications but usually is not appropriate for a public web app accessible for search engines.

The main problem here is that when error happens ASP.NET returns 302 "temporary redirect" response redirecting browser to the error page configured in the Web.config.

If the erroneous page was accessed by a SE bot the bot would index a content of the error handling page under the original page's URL thus creating a wrong index entry.

Another problem is if the actual error on the page was that the requested content was not found. This scenario is quite regular on modern dynamic web sites that construct content pages dynamically based on a Url. In http world such a situation should be called as 404 "Not found" especially in the case of a bot visiting such a page. But ASP.NET standard handling will once again respond 302 and let the bot include a not existing page in a search index.

What I am driving at is the correct error handling should always return a corresponding http status code for a SE bot with the appropriate content for the user. So how can we do that without writing too much code?

Actually quite easy. Starting version 3.5 SP1 ASP.NET has a new attribute redirectMode in the customError configuration section:



The new redirectMode attribute can be assigned one of two values: ResponseRedirect (default) or ResponseRewrite. ResponseRewrite prevents the erroneous page being redirected with the code 302 to an error page however the content of the error page will be shown on the original page. Internally ASP.NET is doing Server.Execute of the error page instead of Response.Redirect as usual.

So this is already a one third of work. The next thing we need to do is to return a correct http status code. It can be achieved by adding a few lines of code to the error page let's say to a Page_Load event handler:

       int httpCode = 500;
       Exception ex = Server.GetLastError();
       if (ex is HttpException)
       {
           httpCode = ((HttpException) ex).GetHttpCode();
       }
       Response.StatusCode = httpCode;


This code checks if the reason of the error was an HttpException (a standard .NET exception class) then it assigns an http code from the exception otherwise it returns 500. Now it's two third of work done.

What left is to handle exceptions in your code properly. The best practices here are:
  • If your application can not return a requested content for any reason except your application's internal problems then throw an HttpException with the code 404 and it will be friendly handled by your error page.

    throw new HttpException(404, "Not found");

  • Map other your application's specific exceptions to standard  http codes and return them too.
  • If your application throws an exception internally then wrap the internal exception with HttpException using an appropriate http code.

    try
    {
    ...
    }
    catch (Exception ex)
    {
    throw new HttpException(code, message, ex);
    }
That's basically it. In conclusion just a few more notes.

If an erroneous page happened to start rendering content before the error occurred you may want to clear it on the error page before outputting an error message:

Response.Clear();

If for some reason you can not use the new redirectMode attribute in the .config file (older framework, application specifics, etc.) then just add a few lines of code to the global.asax that do the same:

void Application_Error(object sender, EventArgs e)
{
// Do something with the error, i.e. log, notify, etc. 
Server.Transfer(errorpage);
}

It may be a good idea to clear an error status on the error page after you're done with error handling:

ClearError();

Now that is all.