Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpModule for Error Handling and Missing Images

Tags:

I have an HttpModule that I have put together from cobbling a couple of different sources online together into something that (mostly) works with both traditional ASP.NET applications, as well as ASP.NET MVC applications. The largest part of this comes from the kigg project on CodePlex. My problem is in dealing with 404 errors due to a missing image. In the following code, I have had to explicitly look for an image being requested through the AcceptedTypes collection in the HttpContext's Request object. If I don't put in this check, even a missing image is causing a redirect to the 404 page defined in my section in the Web.config.

The problem with this approach is that (beyond the fact it smells) is that this is just for images. I would basically have to do this with every single content type imaginable that I do not want this redirect behavior to happen on.

Looking at the code below, can someone recommend some sort of refactoring that could allow for it to be more lenient with non-page requests? I would still want them in the IIS logs (so I would probably have to remove the ClearError() call), but I do not think that a broken image should impact the user experience to the point of redirecting them to the error page.

The code follows:

/// <summary>
/// Provides a standardized mechanism for handling exceptions within a web application.
/// </summary>
public class ErrorHandlerModule : IHttpModule
{
    #region Public Methods

    /// <summary>
    /// Disposes of the resources (other than memory) used by the module that implements 
    /// <see cref="T:System.Web.IHttpModule"/>.
    /// </summary>
    public void Dispose()
    {
    }

    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">
    /// An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events 
    /// common to all application objects within an ASP.NET application.</param>
    public void Init(HttpApplication context)
    {
        context.Error += this.OnError;
    }

    #endregion

    /// <summary>
    /// Called when an error occurs within the application.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private void OnError(object source, EventArgs e)
    {
        var httpContext = HttpContext.Current;

        var imageRequestTypes =
            httpContext.Request.AcceptTypes.Where(a => a.StartsWith("image/")).Select(a => a.Count());

        if (imageRequestTypes.Count() > 0)
        {
            httpContext.ClearError();
            return;
        }

        var lastException = HttpContext.Current.Server.GetLastError().GetBaseException();
        var httpException = lastException as HttpException;
        var statusCode = (int)HttpStatusCode.InternalServerError;

        if (httpException != null)
        {
            statusCode = httpException.GetHttpCode();
            if ((statusCode != (int)HttpStatusCode.NotFound) && (statusCode != (int)HttpStatusCode.ServiceUnavailable))
            {
                // TODO: Log exception from here.
            }
        }

        var redirectUrl = string.Empty;

        if (httpContext.IsCustomErrorEnabled)
        {
            var errorsSection = WebConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
            if (errorsSection != null)
            {
                redirectUrl = errorsSection.DefaultRedirect;

                if (httpException != null && errorsSection.Errors.Count > 0)
                {
                    var item = errorsSection.Errors[statusCode.ToString()];

                    if (item != null)
                    {
                        redirectUrl = item.Redirect;
                    }
                }
            }
        }

        httpContext.Response.Clear();
        httpContext.Response.StatusCode = statusCode;
        httpContext.Response.TrySkipIisCustomErrors = true;
        httpContext.ClearError();

        if (!string.IsNullOrEmpty(redirectUrl))
        {
            var mvcHandler = httpContext.CurrentHandler as MvcHandler;
            if (mvcHandler == null)
            {
                httpContext.Server.Transfer(redirectUrl);                    
            }
            else
            {
                var uriBuilder = new UriBuilder(
                    httpContext.Request.Url.Scheme, 
                    httpContext.Request.Url.Host, 
                    httpContext.Request.Url.Port, 
                    httpContext.Request.ApplicationPath);

                uriBuilder.Path += redirectUrl;

                string path = httpContext.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
                HttpContext.Current.RewritePath(path, false);
                IHttpHandler httpHandler = new MvcHttpHandler();

                httpHandler.ProcessRequest(HttpContext.Current);
            }
        }
    }
}

Any feedback would be appreciated. The app that I am currently doing this with is an ASP.NET MVC application, but like I mentioned it is written to work with an MVC handler, but only when the CurrentHandler is of that type.

Edit: I forgot to mention the "hack" in this case would be the following lines in OnError():

        var imageRequestTypes =
        httpContext.Request.AcceptTypes.Where(a => a.StartsWith("image/")).Select(a => a.Count());

    if (imageRequestTypes.Count() > 0)
    {
        httpContext.ClearError();
        return;
    }
like image 257
Joseph Ferris Avatar asked Sep 19 '09 00:09

Joseph Ferris


People also ask

What is the use of Httpmodule?

HTTP modules overview Modules are called before and after the handler executes. Modules enable developers to intercept, participate in, or modify each individual request. Modules implement the IHttpModule interface, which is located in the System. Web namespace.

What is Httpmodule in asp net?

An HTTP module is an assembly that is called on every request that is made to your application. HTTP modules are called as part of the ASP.NET request pipeline and have access to life-cycle events throughout the request. HTTP modules let you examine incoming and outgoing requests and take action based on the request.


1 Answers

Ultimately, the problem was being caused by not distinguishing between the different types of Context provided by a traditional ASP.NET Application and an ASP.NET MVC Application. By providing a check to determine the type of context I was dealing with, I was able to respond accordingly.

I have added separate methods for an HttpTransfer and MvcTransfer that allow for me to redirect to the error page, specifically when needed. I also changed the logic around so that I could easily get my YSOD on my local and development machines without the handler swallowing the exception.

With the exception of the code used to log the exception to the database (denoted by a TODO comment), the final code that we are using is:

using System;
using System.Net;
using System.Security.Principal;
using System.Web;
using System.Web.Configuration;
using System.Web.Mvc;

using Diagnostics;

/// <summary>
/// Provides a standardized mechanism for handling exceptions within a web application.
/// </summary>
public sealed class ErrorHandlerModule : IHttpModule
{
    #region Public Methods

    /// <summary>
    /// Disposes of the resources (other than memory) used by the module that implements 
    /// <see cref="T:System.Web.IHttpModule"/>.
    /// </summary>
    public void Dispose()
    {
    }

    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">
    /// An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events 
    /// common to all application objects within an ASP.NET application.</param>
    public void Init(HttpApplication context)
    {
        context.Error += OnError;
    }

    #endregion

    #region Private Static Methods

    /// <summary>
    /// Performs a Transfer for an MVC request.
    /// </summary>
    /// <param name="url">The URL to transfer to.</param>
    /// <param name="currentContext">The current context.</param>
    private static void HttpTransfer(string url, HttpContext currentContext)
    {
        currentContext.Server.TransferRequest(url);
    }

    /// <summary>
    /// Performs a Transfer for an MVC request.
    /// </summary>
    /// <param name="url">The URL to transfer to.</param>
    /// <param name="currentContext">The current context.</param>
    private static void MvcTransfer(string url, HttpContext currentContext)
    {
        var uriBuilder = new UriBuilder(
            currentContext.Request.Url.Scheme,
            currentContext.Request.Url.Host,
            currentContext.Request.Url.Port,
            currentContext.Request.ApplicationPath);

        uriBuilder.Path += url;

        string path = currentContext.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
        HttpContext.Current.RewritePath(path, false);
        IHttpHandler httpHandler = new MvcHttpHandler();

        httpHandler.ProcessRequest(HttpContext.Current);
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Called when an error occurs within the application.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private static void OnError(object source, EventArgs e)
    {
        var httpContext = HttpContext.Current;
        var lastException = HttpContext.Current.Server.GetLastError().GetBaseException();
        var httpException = lastException as HttpException;
        var statusCode = (int)HttpStatusCode.InternalServerError;

        if (httpException != null)
        {
            if (httpException.Message == "File does not exist.")
            {
                httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                httpContext.ClearError();
                return;
            }

            statusCode = httpException.GetHttpCode();
        }

        if ((statusCode != (int)HttpStatusCode.NotFound) && (statusCode != (int)HttpStatusCode.ServiceUnavailable))
        {
            // TODO : Your error logging code here.
        }

        var redirectUrl = string.Empty;

        if (!httpContext.IsCustomErrorEnabled)
        {
            return;
        }

        var errorsSection = WebConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
        if (errorsSection != null)
        {
            redirectUrl = errorsSection.DefaultRedirect;

            if (httpException != null && errorsSection.Errors.Count > 0)
            {
                var item = errorsSection.Errors[statusCode.ToString()];

                if (item != null)
                {
                    redirectUrl = item.Redirect;
                }
            }
        }

        httpContext.Response.Clear();
        httpContext.Response.StatusCode = statusCode;
        httpContext.Response.TrySkipIisCustomErrors = true;
        httpContext.ClearError();

        if (!string.IsNullOrEmpty(redirectUrl))
        {
            var mvcHandler = httpContext.CurrentHandler as MvcHandler;
            if (mvcHandler == null)
            {
                try
                {
                    HttpTransfer(redirectUrl, httpContext);
                }
                catch (InvalidOperationException)
                {
                    MvcTransfer(redirectUrl, httpContext);
                }
            }
            else
            {
                MvcTransfer(redirectUrl, httpContext);
            }
        }
    }

    #endregion
}
like image 133
Joseph Ferris Avatar answered Oct 12 '22 09:10

Joseph Ferris