HttpModule для обработки ошибок и пропущенных изображений - PullRequest
5 голосов
/ 19 сентября 2009

У меня есть HttpModule, который я собрал из объединения нескольких разных источников в сети, во что-то, что (в основном) работает как с традиционными приложениями ASP.NET, так и с ASP.NET MVC. Большая часть этого происходит от проекта Kigg на CodePlex. Моя проблема связана с 404 ошибками из-за отсутствия изображения. В следующем коде мне пришлось явно искать запрашиваемое изображение через коллекцию AcceptedTypes в объекте запроса HttpContext. Если я не включу эту проверку, даже отсутствующее изображение вызывает перенаправление на страницу 404, определенную в моем разделе в файле Web.config.

Проблема с этим подходом заключается в том, что (помимо того, что он пахнет), что это только для изображений. В основном мне придется делать это с каждым мыслимым типом контента, для которого я не хочу, чтобы происходило такое поведение перенаправления.

Глядя на приведенный ниже код, может ли кто-нибудь порекомендовать какой-либо рефакторинг, который позволил бы ему быть более снисходительным при запросах не на странице? Я все еще хотел бы, чтобы они были в журналах IIS (поэтому мне, вероятно, пришлось бы удалить вызов ClearError ()), но я не думаю, что разорванное изображение должно влиять на работу пользователя до такой степени, что он перенаправляет их на страницу ошибки.

Код следует:

/// <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);
            }
        }
    }
}

Любые отзывы будут оценены. Приложение, с которым я сейчас работаю, является приложением ASP.NET MVC, но, как я уже упоминал, оно написано для работы с обработчиком MVC, но только когда CurrentHandler относится к этому типу.

Редактировать: Я забыл упомянуть, что "взломать" в этом случае будут следующие строки в OnError ():

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

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

Ответы [ 3 ]

5 голосов
/ 23 октября 2009

В конечном счете, проблема была вызвана тем, что не различались различные типы контекста, предоставляемые традиционным приложением ASP.NET и приложением ASP.NET MVC. Предоставив проверку для определения типа контекста, с которым я имел дело, я смог ответить соответствующим образом.

Я добавил отдельные методы для HttpTransfer и MvcTransfer, которые позволяют мне перенаправлять на страницу ошибки, особенно при необходимости. Я также изменил логику так, чтобы я мог легко получить свой YSOD на своих локальных компьютерах и машинах разработки без обработчика, проглатывающего исключение.

За исключением кода, используемого для регистрации исключения в базе данных (обозначается комментарием TODO), окончательный код, который мы используем:

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
}
0 голосов
/ 10 октября 2009

Если я правильно понимаю, вы хотите обрабатывать ошибки только для действий, которые приводят к 404?

Вы можете проверить, является ли маршрут для запроса нулевым или остановить маршрутизацию - именно так работает обработчик URL-маршрутизации, чтобы решить, следует ли продолжать запрос в конвейер mvc.

var iHttpContext = new HttpContextWrapper( httpContext );
var routeData = RouteTable.Routes.GetRouteData( iHttpContext );
if( routeData == null || routeData.RouteHandler is StopRoute )
{
  // This is a route that would not normally be handled by the MVC pipeline
  httpContext.ClearError();
  return;
}

Кроме того, перенаправление из-за 404 вызывает не совсем идеальный пользовательский опыт и является похмелье от ASP.NET (где вы не могли отделить обработку представления от обработки запроса). Правильный способ управления 404 - возвращать код состояния 404 в браузер и отображать пользовательскую страницу ошибки, а не перенаправлять (в результате код состояния 302 отправляется в браузер).

0 голосов
/ 20 сентября 2009

почему бы вам не поймать 404 в вашем global.asax?

protected void Application_Error(object sender, EventArgs args) {

    var ex = Server.GetLastError() as HttpException;
    if (ex != null && ex.ErrorCode == -2147467259) {

    }
}
...