5

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

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

Глядя на код ниже, может кто-то порекомендовать какой-то рефакторинг, который может позволить ему быть более снисходительным к нестраничным запросам? Я бы все еще хотел, чтобы они были в журналах 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 имеет этот тип.

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

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

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

Вместо того, чтобы создать свой собственный модуль протоколирования ошибок, рассмотрели ли вы использование одной из существующих библиотек регистрации ошибок, таких как ELMAH (http://code.google.com/p/elmah/) или мониторинг работоспособности ASP.NET (http://msdn.microsoft.com/) ан-нас/библиотека/ms998306.aspx)? ELMAH имеет богатый API фильтрации ошибок, который можно указать декларативно в Web.config или через код, если это необходимо. –

+0

Скотт, я определенно подумал об этом и использовал ELMAH в прошлом. Это было скорее упражнением по кодированию, чем чем-либо еще. –

ответ

5

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

Я добавил отдельные методы для 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

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

protected void Application_Error(object sender, EventArgs args) { 

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

    } 
} 
+0

Точка модуля - это «одноразовое» решение, так что мне не нужно будет размещать ad-hoc-код в Global.asax. Я думаю, что у меня может быть модификация HttpModule, который работает. Я собираюсь кипеть на нем немного и запустить несколько тестов для mak –

+0

er ... Представлено слишком рано. Я немного подшучиваюсь над этим и запускаю некоторые тесты, чтобы убедиться, что это понюхать и отправить его сюда завтра, если я не вижу никаких проблем. –

+0

При обращении к 404 для изображения или другого статического содержимого с Global_asax не будет обращаться. – DanO

0

Если я правильно понимаю, вы хотите обрабатывать ошибки только для действий, которые приводят к 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 в браузер).

 Смежные вопросы

  • Нет связанных вопросов^_^