5

Mam moduł HttpModule, który zebrałem razem, łącząc kilka różnych źródeł online w coś, co (głównie) działa zarówno z tradycyjnymi aplikacjami ASP.NET, jak i ASP.NET MVC Aplikacje. Największa część tego pochodzi z projektu kigg na CodePlex. Mój problem polega na radzeniu sobie z błędami 404 z powodu braku obrazu. W poniższym kodzie musiałem jawnie szukać żądanego obrazu poprzez kolekcję AcceptedTypes w obiekcie Request HttpContext. Jeśli nie wprowadzę tej kontroli, nawet brakujący obraz powoduje przekierowanie do strony 404 zdefiniowanej w mojej sekcji w pliku Web.config.HttpModule do obsługi błędów i brakujących obrazów

Problem z tym podejściem polega na tym, że (poza tym, że pachnie) jest to, że jest to tylko dla obrazów. Zasadniczo musiałbym to zrobić z każdym możliwym typem zawartości, którego nie chcę, aby to działanie przekierowania miało miejsce.

Patrząc na poniższy kod, czy ktoś może polecić coś w rodzaju refaktoryzacji, która mogłaby pozwolić na łagodniejsze podejście do wniosków niestronicowych? Wciąż chciałbym je w dziennikach IIS (więc prawdopodobnie musiałbym usunąć wywołanie ClearError()), ale nie sądzę, że uszkodzony obraz powinien wpłynąć na doświadczenie użytkownika do punktu przekierowania ich do strony błędu.

Kod następująco:

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

Wszelkie uwagi będą mile widziane. Aplikacja, którą obecnie robię, jest aplikacją ASP.NET MVC, ale jak wspomniałem, jest napisana do pracy z handlersem MVC, ale tylko wtedy, gdy CurrentHandler jest tego typu.

Edit: zapomniałem wspomnieć "hack" w tym przypadku byłoby następujące linie w onError():

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

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

Zamiast budować swój własny moduł rejestrowania błędów, Czy rozważałeś użycie jednej z istniejących bibliotek rejestrowania błędów, takich jak ELMAH (http://code.google.com/p/elmah/) lub Monitorowanie kondycji ASP.NET (http://msdn.microsoft.com/ en-us/library/ms998306.aspx)? ELMAH ma bogaty interfejs API do filtrowania błędów, który można określić w sposób zadeklarowany w pliku Web.config lub za pomocą kodu. –

+0

Scott, zdecydowanie to rozważyłem i użyłem ELMAH w przeszłości. To było bardziej kodowanie niż cokolwiek innego. –

Odpowiedz

5

Ostatecznie przyczyną problemu był brak rozróżnienia między różnymi typami kontekstu udostępnianymi przez tradycyjną aplikację ASP.NET i aplikacją ASP.NET MVC. Zapewniając kontrolę w celu określenia rodzaju kontekstu, z którym miałem do czynienia, byłem w stanie odpowiednio zareagować.

Dodałem oddzielne metody dla HttpTransfer i MvcTransfer, które pozwalają mi przekierować na stronę błędu, szczególnie gdy jest to potrzebne. Zmieniłem również logikę tak, żebym mógł łatwo uzyskać mój YSOD na moich maszynach lokalnych i programistycznych, gdyby przewodnik nie połknął wyjątku.

Z wyjątkiem kodu używanego do logowania wyjątek do bazy danych (oznaczone komentarzem TODO), ostateczny kod, który używamy jest:

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

dlaczego nie można złapać 404 w swoim global.asax?

protected void Application_Error(object sender, EventArgs args) { 

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

    } 
} 
+0

Punktem modułu jest rozwiązanie "napisz raz", więc nie będę musiał umieszczać kodu ad-hoc w pliku Global.asax. Myślę, że mogę mieć modyfikację działającego modułu HttpModule. Mam zamiar trochę go ostudzić i przeprowadzić kilka testów, aby zdążyć ... –

+0

er ... Niedawno przesłane. Mam zamiar trochę go ostudzić i przeprowadzić kilka testów, aby upewnić się, że to jest tabakier i jutro go tu zamieścić, jeśli nie widzę żadnych problemów. –

+0

Global.asax nie będzie konsultowany podczas obsługi 404 dla obrazu lub innej statycznej zawartości. – DanO

0

Jeśli dobrze rozumiem, chcesz tylko obsługiwać błędy dla działań, które prowadzą do 404?

Możesz sprawdzić, czy trasa dla żądania jest pusta lub przestać kierować - w ten sposób działa narzędzie do wyznaczania trasy, aby zdecydować, czy żądanie powinno kontynuować przesyłanie do rurociągu 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; 
} 

Tak na marginesie, przekierowanie ze względu na 404 powoduje mniej niż idealny interfejs użytkownika i jest kaca z ASP.NET (gdzie nie można oddzielić przetwarzanie widok z przetwarzaniem życzenie). Prawidłowym sposobem zarządzania 404 jest zwrócenie do przeglądarki kodu statusu 404 i wyświetlenie niestandardowej strony błędu zamiast przekierowania (co skutkuje wysłaniem do przeglądarki kodu statusu 302).

Powiązane problemy