ASP.NET 2.0 : Best Practice for writing Error Page

In asp.net 2.0 web site, what is the best way of writing Error page. I have seen following section at following location:

  • Web.Config
    <customErrors mode="RemoteOnly" defaultRedirect="~/Pages/Common/DefaultRedirectErrorPage.aspx">
  • Global.asax
    void Application_Error(object sender, EventArgs e) 
    { 
    }
  • I am not getting how to use both of them in best way for Error handling.

    Please guide me best approach.

    Answers:

    Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

    Method 1

    In my global asax i always check to see what type of http error it is…

    then transfer to the correct error page specified in web.config
    I like to handle the usual suspects, 404 ( lost page ) and 500 ( server error )

    some background on http status code is importaint to know why they are handled:

    http://en.wikipedia.org/wiki/List_of_HTTP_status_codes

    my web.config look something like this

    <customErrors mode="On"  defaultRedirect="~/error.aspx"  >
      <error statusCode="404" redirect="~/lost.aspx"  />
      <error statusCode="500" redirect="~/error.aspx"  />
    </customErrors>

    my lost page has logic in it to attempt to find a link to the page that they might have been looking for, as well as some other formatting.

    my error page is a bit different, showing some error messages ,

    so i handle both differently.

    depending if you have secured areas of your site you might want handle the 401/403 ?

    protected void Application_Error(object sender, EventArgs e)
    {
        var context = Context;
    
    
        var error = context.Server.GetLastError() as HttpException;
        var statusCode = error.GetHttpCode().ToString();
    
        // we can still use the web.config custom errors information to
        // decide whether to redirect
        var config = (CustomErrorsSection)WebConfigurationManager.GetSection("system.web/customErrors");
        if (config.Mode == CustomErrorsMode.On ||
            (config.Mode == CustomErrorsMode.RemoteOnly && context.Request.Url.Host != "localhost"))
        {
            // set the response status code
            context.Response.StatusCode = error.GetHttpCode();
    
            // Server.Transfer to correct ASPX file for error
            if (config.Errors[statusCode] != null)
            {
    
                HttpContext.Current.Server.Transfer(config.Errors[statusCode].Redirect);
            }
            else
                HttpContext.Current.Server.Transfer(config.DefaultRedirect);
        }
    }

    the reason i server transfer is so search engines don’t get confused, and to keep my webmaster logs meaningful… if you redirect you return a http status 302 which tell the browser to go to the page redirected to… then this next page returns a status code 200 ( ok ).

    302 –> 200 , or even 302 –> 404 has different meaning that just a 404…

    then on say my 404 error page i make sure i set the status code of the http error:

    protected void Page_PreRender(object sender, EventArgs e)
    {
        Response.Status = "404 Lost";
        Response.StatusCode = 404;
    
    }

    This article was helpful to me, I knew what I wanted to do but I like how this code looked at the web.config settings… http://helephant.com/2009/02/improving-the-way-aspnet-handles-404-requests/

    Return the right status code

    By default the page handling a 404
    error page doesn’t return a 404 status
    code to the browser. It displays the
    error message that you provided to the
    user but doesn’t have any extra
    information to flag the page as an
    error page.

    This is called a soft 404. Soft 404
    pages aren’t as good as ones that
    return the 404 status code because
    returning the 404 status code lets
    anything accessing your file that the
    page is an error page rather than a
    real page of you site. This is mostly
    useful for search engines because then
    they know they should remove dead
    pages from their index so users won’t
    follow dead links into your site from
    results pages.

    Pages that return 404 status codes are
    also useful for error detection
    because they’ll be recorded in your
    server logs so if you have unexpected
    404 errors, they’ll be easy to find.
    Here’s an example of the 404 error
    report in Google Webmaster tools:

    EDITS

    Is it require to write
    server.clearerror() in global.asax?
    What does it impact

    • No, You can do it on you error pages,
      not sure the impact? if you do a
      transfer non, if you redirect, there
      might be the possibility another
      error happened between the requests?
      I don’t know

    Why in web.config we should write error.aspx
    twice one with status code 500 and another is
    defaultredirect

    • I use 2 because a lost page should
      show/and do different things than a
      server error. the error page shows
      the user there was an error that we
      could not recover from… and its
      probably our fault. I leave a
      default redirect for any of the other
      error codes as well. 403,401, 400 (
      they are more rare, but should be
      handled )

    Can you also tell me the code of error.aspx and lost.aspx.

    • this depends on the type of website
      you have. you get the error the same
      way, but what you do with it is up to
      you. on my lost page i search for
      some content the user might have been
      looking for. the error pages i log
      the error and so a user friendly oops
      page… you will need to figure out
      what is needed.

    Method 2

    BigBlondeViking’s response worked great for me except that I found it did not process 403’s (which ASP generates when you try to directly access the /Scripts/ or /Content/ directories.) It appears that this is not propegated as an exception and thus not trappable in the Application_Error handling. (this was identified as a “security vulnerability” by an external firm – don’t get me started on that!)

    protected void Application_PostRequestHandlerExecute(object sender, EventArgs e)
    {
        if (!Context.Items.Contains("HasHandledAnError")) // have we alread processed?
        {
            if (Response.StatusCode > 400 &&  // any error
                Response.StatusCode != 401)   // raised when login is required
            {
                Exception exception = Server.GetLastError();    // this is null if an ASP error
                if (exception == null)
                {
                    exception = new HttpException((int)Response.StatusCode, HttpWorkerRequest.GetStatusDescription(Response.StatusCode));
                }
                HandleRequestError(exception); // code shared with Application_Error
            }
        }
    }

    I also made a minor change in my common Error Handling. As we are using ASP.NET MVC, I wanted to explicitly call into a controller, as well as pass the exception object. This allowed me to access the exception itself so that I can log / send detailed e-mail depending on the code;

    public ActionResult ServerError(Exception exception)
    {
        HttpException httpException = exception as HttpException;
        if(httpException != null)
        {
            switch (httpException.GetHttpCode())
            {
                case 403:
                case 404:
                    Response.StatusCode = 404;
                    break;
            }
            // no email...
            return View("HttpError", httpException);
        }
        SendExceptionMail(exception);
        Response.StatusCode = 500;
        return View("ServerError", exception);
    }

    In order to pass the exception OBJECT (not just the message and code) I explicitly invoked the controller:

    protected void HandleRequestError(Exception exception)
    {
        if (Context.Items.Contains("HasHandledAnError"))
        {
            // already processed
            return;
        }
        // mark as processed.
        this.Context.Items.Add("HasHandledAnError", true);
    
        CustomErrorsSection customErrorsSection = WebConfigurationManager.GetWebApplicationSection("system.web/customErrors") as CustomErrorsSection;
    
        // Do not show the custom errors if
        // a) CustomErrors mode == "off" or not set.
        // b) Mode == RemoteOnly and we are on our local development machine.
        if (customErrorsSection == null || !Context.IsCustomErrorEnabled ||
            (customErrorsSection.Mode == CustomErrorsMode.RemoteOnly && Request.IsLocal))
        {
            return;
        }
    
        int httpStatusCode = 500;   // by default.
        HttpException httpException = exception as HttpException;
        if (httpException != null)
        {
            httpStatusCode = httpException.GetHttpCode();
        }
    
        string viewPath = customErrorsSection.DefaultRedirect;
        if (customErrorsSection.Errors != null)
        {
            CustomError customError = customErrorsSection.Errors[((int)httpStatusCode).ToString()];
            if (customError != null && string.IsNullOrEmpty(customError.Redirect))
            {
                viewPath = customError.Redirect;
            }
        }
    
        if (string.IsNullOrEmpty(viewPath))
        {
            return;
        }
    
        Response.Clear();
        Server.ClearError();
    
        var httpContextMock = new HttpContextWrapper(Context);
        httpContextMock.RewritePath(viewPath);
        RouteData routeData = RouteTable.Routes.GetRouteData(httpContextMock);
        if (routeData == null)
        {
            throw new InvalidOperationException(String.Format("Did not find custom view with the name '{0}'", viewPath));
        }
        string controllerName = routeData.Values["controller"] as string;
        if (String.IsNullOrEmpty(controllerName))
        {
            throw new InvalidOperationException(String.Format("No Controller was found for route '{0}'", viewPath));
        }
        routeData.Values["exception"] = exception;
    
        Response.TrySkipIisCustomErrors = true;
        RequestContext requestContext = new RequestContext(httpContextMock, routeData);
        IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
        IController errorsController = factory.CreateController(requestContext, controllerName);
        errorsController.Execute(requestContext);
    }


    All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

    0 0 votes
    Article Rating
    Subscribe
    Notify of
    guest

    0 Comments
    Inline Feedbacks
    View all comments
    0
    Would love your thoughts, please comment.x
    ()
    x