DCSIMG
ASP.NET Error Handling Using HttpModule, Full and Partial Post Back (Ajax UpdatePanel) - Offir Shvartz

Offir Shvartz

We code with love or we code not...

ASP.NET Error Handling Using HttpModule, Full and Partial Post Back (Ajax UpdatePanel)

This post is the reason for me opening a blog. Recently I encountered a very simple problem that took me around 3 days to solve. I found many posts on this issue but nothing was what I needed.

My task was quite simple I wanted a single point of logic to deal with the fatal errors:

  1. When an error occurs that is not a result of invalid input by the user - and we cannot recover from it.
  2. exception we didn't or couldn't catch (unhandled exceptions).

I must say that most of my experience is with server side so my knowledge in ASP.NET is quite poor (Many thanks to Avi Pinto for all his help ;)

The error handling flow is:

  • Log the Exception
  • Open a generic error page with the error code number and more info.

In ASP.NET there are two ways of doing this. The first  is using the Global.asax file ( which I will not discuss here) and the other is using HttpModule ( a detailed explanation about HttpModules is beyond the scope of this article. You can read about it here).

I choose the HttpModule because it's more reusable - if it needs to be altered you don't need to rebuild your whole web site, just add a reference DLL of the class that implements the interface IHttpModule and add to the web.config file one line:

<configuration>
  <system.web>
    <httpModules>
      <add name="MyHttpModule" 
                      type="MyHttpModule, MyHttpModule" /> 
    </httpModules>
  </system.web>
</configuration>
 

Implementing the IHttpModule:

  • In the Init method enroll to the context error event.
  • Implementing the error handler (my logic): just write to log, build query string for the error page and then Server.Transfer to the error page with the query string generated.

Here I must add two notes about best practice of Exception handling in my opinion:

Note 1: Some developers tend to use the exception technology for logic implementation (e.g. throw DataNotFoundException for empty data). I think it's bad practice, just return empty result. The Exception technology has very high overhead and wasn't design for logic implementation.

Note 2: in your lower levels of logic ( e.g. code behind of a page ) don't catch Exception or WebException unless you have something to do with them: the only reason to catch general Excption is maybe to add information and then rethrow it.

I tested the HttpModule just by removing the catch(Exception ex) lines from the solution I was working on.

It appeared to be working :-) but trying it on another web page it crashed. I checked why and discovered that the error was thrown from ajax UpdatePanel control. Looking for an answer on the web I found that many people encountered this problem - when you modify the response during a partial post back  the response is turned into gibberish). Some solutions allowed redirect from an ajax update panel but not inside a HttpModule which is called during the request life cycle.

First I noticed that in case of a partial post back my module doesn't catch the error. I found the solution to that on the web:

  • enroll to the event PostMapRequestHandler in the module's Init method (there is a parallel way with the Global.asax)
 public void Init(HttpApplication context)
        {
            context.Error += new EventHandler(context_Error);
            context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);
 
        }
  • In the PostMapRequest event handler cast the handler to a Page object and enroll to the Page.Error event
 void context_PostMapRequestHandler(object sender, EventArgs e)
        {
            Page aux = HttpContext.Current.Handler as Page;
            if (aux != null)
            {
                aux.Error += new EventHandler(aux_Error);
            }
        }

This way we catch all the errors (partial and full post back). This allowed me to log the error but the Server.Transfer (and Response.Redirect) was not working :-(  - again, because you cannot change the request in partial post back. This sent me back to the web for more information. I found one answer that suggested I add the module to the web.config file:    

<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

This will enable the use of Response.Redirect, but.....not from a HttpModule. In one of my tests I entered the wrong error page URL and then got a PageNotFound Error (404).  When I checked, i found that the error was back in a full post back. From here the way to the solution was near.

The solution (pay attention it's tricky):

  • I am enrolling to both errors of full post back and errors of all types (full and partial post back).
  • In the error handler of all typed post back: log my error and redirect to URL that doesn't exist.
  • In the error handler of full post back: check if the error was as a result of my page not found error (404) or not. If not just clear the error otherwise Server.Transfer to the error page.

the code:

public class ClientErrorHandlingModule : IHttpModule
{
    public const string USER_ID_QS = "UserId";
    public const string ERROR_CODE_QS = "ErrorCode";
    public const string DATE_TIME_QS = "DateTime";
    public const string SOURCE_PAGE_URL_QS = "SorucePageUrl";
    public const string ADMIN_MAIL_ADD_QS = "AdminMailAdd";
    private const string ERROR_PAGE_URL = "Error.aspx";
    private const string NO_PAGE_STR = "No+Page.aspx";
 
    public ClientErrorHandlingModule()
    {
        //
        // TODO: Add constructor logic here
        //
    }
 
    #region IHttpModule Members
 
    public void Dispose()
    {
    }
 
    public void Init(HttpApplication context)
    {
        context.Error += new EventHandler(context_Error);
        context.PostMapRequestHandler += 
                new EventHandler(context_PostMapRequestHandler);
 
    }
 
    void context_PostMapRequestHandler(object sender, EventArgs e)
    {
        Page aux = HttpContext.Current.Handler as Page;
        if (aux != null)
        {
            aux.Error += new EventHandler(aux_Error);
        }
    }
 
    //all error full and partial post back handler
    void aux_Error(object sender, EventArgs e)
    {
        string srcPageUrl = 
                   HttpContext.Current.Request.Url.ToString();
        Exception ex = HttpContext.Current.Server.GetLastError();
        int userId = retrieveUserId();
        //log the error
        ExceptionMgr.HandleClientException(ex, 
               EventIDTypes.GeneralWebError, userId, srcPageUrl);
        //retrieve the error page query string
        string errorPageQueryStr = getErrorPageQueryStr
                                             (srcPageUrl, ex);
        //redirect to no place in order to generate post back
        // normal error
        HttpContext.Current.Response.Redirect(NO_PAGE_STR + "?"
                                    + errorPageQueryStr, false);
 
 
 
    }
 
    //only full postback handler
    void context_Error(object sender, EventArgs e)
    {
        try
        {
            string errorPageQueryStr = string.Empty;
            string srcPageUrl = 
                    HttpContext.Current.Request.Url.ToString();
 
            //if the error comes our handler we retrieve the
            //query string
            if (srcPageUrl.Contains(NO_PAGE_STR))
            {
 
                errorPageQueryStr = srcPageUrl.Substring
                                (srcPageUrl.IndexOf(NO_PAGE_STR) 
                                     + NO_PAGE_STR.Length + 1);
            }
            //otherwise we just clear the error
            else
            {
                HttpContext.Current.Server.ClearError();
                return;
            }
 
            //clear the error and redirect it to the error page
            HttpContext.Current.Server.ClearError();
            HttpContext.Current.Server.Transfer(ERROR_PAGE_URL +
                               "?" + errorPageQueryStr, true);
 
        }
        catch (Exception ex)
        {
            LogMgr.Write(LogCategories.Fatal, 
               "context_Error exception:"
              + ex.Message,EventIDTypes.GeneralInfraError, ex);
        }
    }
 
    private string getErrorPageQueryStr(string i_srcPageUrl,
                                                Exception i_ex)
    {
        ClientErrorConfigurationSection confSec = ConfigMgr.Read
        (ConfigSectionTypes.ClientError) as 
                              ClientErrorConfigurationSection;
        if (confSec == null)
            throw new Exception("Problem in reading Client 
                                           Error config file");
        SoapExceptionInfo sei = getSoapExceptionInfo(i_ex);
        string errorPageQueryStr = getErrorPageQueryString(sei, 
             (int)EventIDTypes.GeneralWebError, 
                        confSec.AdminMailAddress, i_srcPageUrl);
        return errorPageQueryStr;
    }
 
 
 
    private string getErrorPageQueryString(SoapExceptionInfo i_sei,
                    int i_errorCode, string i_adminMailAddress,
                                            string i_srcPageUrl)
    {
        StringBuilder sb = new StringBuilder();
        string template = "{0}={1}&";
        sb.Append(string.Format(template, DATE_TIME_QS,
                                                  DateTime.Now));
        sb.Append(string.Format(template, ADMIN_MAIL_ADD_QS,
                                            i_adminMailAddress));
        sb.Append(string.Format(template, SOURCE_PAGE_URL_QS,
                                                   i_srcPageUrl));
        if (i_sei != null)
        {
            sb.Append(string.Format(template, ERROR_CODE_QS,
                                                i_sei.ErrorCode));
        }
        else
        {
 
            sb.Append(string.Format(template, ERROR_CODE_QS,
                                                   i_errorCode));
        }
 
        return sb.ToString();
    }
 
    private SoapExceptionInfo getSoapExceptionInfo(Exception i_ex)
    {
        SoapExceptionInfo res = null;
        if (i_ex is SoapException)
        {
            res = SoapExceptionHandler.TranslateSoapException(
                                            (SoapException)i_ex);
 
        }
        return res;
    }
 
    private static int retrieveUserId()
    {
        int userId = -1;
        string userIdStr = 
                  HttpContext.Current.Request.Params[USER_ID_QS];
        if (!string.IsNullOrEmpty(userIdStr))
        {
            userId = int.Parse(userIdStr);
        }
        return userId;
    }
 
    #endregion
}

To conclude, the goal was reached: only one place holding the logic for error handling. It works not only with ajax updatepanel but also for all regular post backs.

So that's all for now I hope this post will save others some time.

Cheers Offir

Comments

Avi Pinto said:

אחלה פוסט :)

עכשיו אין ברירה...

# May 17, 2008 11:54 PM

Dean Hiller said:

That's good for using on the NHibernate patterns as well.  I sure miss the Java servlet filters, but this post solved my problems with NHibernate transactions and sessions with AJAX....thanks!!!!

# June 2, 2008 5:37 AM

Kamran Shahid said:

I were expereincing compilation error as there are problem in some of the classes used there [SoapExceptionInfo, ExceptionMgr e.t.c]

It will be better if u could upload a sample project which using this module some where and provide a link .

# July 25, 2008 3:03 AM

Offir Shvartz said:

the classes SoapExceptionInfo, ExceptionMgr are part of our infrastructures dealing with exception (in general uses the execption application block)any way this was not the purpose of this post so you can just mark it out from the code

# July 27, 2008 3:08 PM

Kamran Shahid said:

Thanks Offir Shvartz

# August 4, 2008 2:21 AM

Yaniv L said:

You rule man !!!!

saved me lots of time !!

# September 3, 2008 3:51 AM

Ron said:

Hi,

I've encountered the same problem while handling session timeout with http-module inside ajax web-form.

I modified your solution i.e. registered to OnPreRequestHandlerExecute event and Error event.

I detect a session timeout and redirect to a non existed page and OnError event I redirect to a session timeout notification page.

Sadly it doesn't work all the time and sometime I don't reach to the HttpModule OnError handler

Any ideas ???

# October 23, 2008 10:10 AM

rel said:

I found this which might offer a much simpler solution:

public void Init(HttpApplication context)

   {

       context.PostMapRequestHandler += new EventHandler(OnPostMapRequestHandler);

   }

   private static void OnPostMapRequestHandler(object sender, EventArgs args)

   {

       HttpContext context = ((HttpApplication) sender).Context;

       if (!IsAsyncPostBackRequest(context.Request.Headers))

           return;

       Page page = context.Handler as Page;

       if (page == null)

           return;

       page.Error += new EventHandler(OnPageError);

   }

read it here:

markmail.org/.../v3hhwl7y5pec7cms

# February 5, 2009 11:30 PM

soyncagog said:

Where outside columbus insurance .car insurance and coll image as michigan car insurance whil stat farm auto insurance qu ,without  lowest car insurance rates canadian cost

In or recreational car insurance comparis You could get  it contain  health insurance plan .stuff <a href=wiki.svnkit.com/CarInsurance insurance quote</a> car insurance rates ri .

# March 1, 2009 9:21 AM

Foettasty said:

crime scene photos of the wineville chicken coop murders <a href=fussball.lefant.net/.../LufofiMudaj crime scene</a> adrienne shelly crime scene photos <a href=fussball.lefant.net/.../TonupaVikeh scene technician</a> .often dogs at crime scenes .ought to field tests for impressions at crime scenes .news articles on crime scenes using blood .must be  a day in the life of a crime scene investigator oj simpson crime scene when valerie percy  crime scene photos .

# April 8, 2009 9:30 AM

Resharper said:

Hi to all!

I use this approach for my web site. Locally it works good. But when we deployed our site at customer's test environment when exception occurs they receive 404 error and URL that points to Error+Page.aspx (this page is not existed) Do you have any ideas about it?

# May 6, 2009 2:56 PM

ASP.NET Error Handling Using HttpModule, Full and Partial Post … | Errors & Geeks said:

Pingback from  ASP.NET Error Handling Using HttpModule, Full and Partial Post &#8230; | Errors &amp; Geeks

# November 4, 2009 11:44 PM

dipak said:

its really amazing... thankz...

# March 23, 2010 12:50 PM

sandiegocarinsurance said:

Pingback from  sandiegocarinsurance

# June 11, 2010 11:07 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: