5

Tôi có một HttpModule mà tôi đã đặt cùng nhau từ cobbling một vài nguồn khác nhau trực tuyến với nhau thành một cái gì đó (chủ yếu) làm việc với cả hai ứng dụng ASP.NET truyền thống, cũng như ASP.NET MVC các ứng dụng. Phần lớn nhất của điều này xuất phát từ dự án kigg trên CodePlex. Vấn đề của tôi là đối phó với lỗi 404 do một hình ảnh bị thiếu. Trong đoạn mã sau, tôi đã phải tìm kiếm một cách rõ ràng một hình ảnh đang được yêu cầu thông qua bộ sưu tập AcceptedTypes trong đối tượng Request của HttpContext. Nếu tôi không đặt trong kiểm tra này, ngay cả một hình ảnh bị mất đang gây ra một chuyển hướng đến trang 404 được định nghĩa trong phần của tôi trong Web.config.HttpModule để xử lý lỗi và thiếu hình ảnh

Vấn đề với cách tiếp cận này là (ngoài thực tế nó có mùi) là đây chỉ là cho hình ảnh. Tôi về cơ bản sẽ phải làm điều này với mọi loại nội dung đơn lẻ có thể tưởng tượng được rằng tôi không muốn hành vi chuyển hướng này xảy ra.

Nhìn vào mã bên dưới, ai đó có thể đề xuất một số loại tái cấu trúc có thể cho phép nó khoan dung hơn với các yêu cầu không phải trang? Tôi vẫn muốn chúng trong các bản ghi IIS (vì vậy tôi có lẽ sẽ phải loại bỏ cuộc gọi ClearError()), nhưng tôi không nghĩ rằng một hình ảnh bị hỏng sẽ ảnh hưởng đến trải nghiệm người dùng đến điểm chuyển hướng chúng đến trang lỗi.

mã sau:

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

Bất kỳ thông tin phản hồi sẽ được đánh giá. Các ứng dụng mà tôi hiện đang làm điều này với là một ứng dụng ASP.NET MVC, nhưng như tôi đã đề cập nó được viết để làm việc với một trình xử lý MVC, nhưng chỉ khi CurrentHandler là loại đó.

Edit: tôi quên đề cập đến "hack" trong trường hợp này sẽ là những dòng sau trong onerror():

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

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

Thay vì xây dựng mô-đun lỗi đăng nhập của riêng bạn, bạn đã xem xét việc sử dụng một trong các thư viện ghi lỗi hiện có không, chẳng hạn như ELMAH (http://code.google.com/p/elmah/) hoặc ASP.NET Health Monitoring (http://msdn.microsoft.com/ en-us/library/ms998306.aspx)? ELMAH có API lọc lỗi phong phú, bạn có thể chỉ định khai báo trong Web.config hoặc thông qua mã, nếu cần. –

+0

Scott, tôi chắc chắn đã xem xét nó và đã sử dụng ELMAH trong quá khứ. Đây là một bài tập viết mã hơn bất cứ thứ gì khác. –

Trả lời

5

Cuối cùng, vấn đề là do không phân biệt giữa các loại ngữ cảnh khác nhau được cung cấp bởi ứng dụng ASP.NET truyền thống và ứng dụng ASP.NET MVC. Bằng cách cung cấp một kiểm tra để xác định loại bối cảnh mà tôi đã xử lý, tôi đã có thể trả lời cho phù hợp.

Tôi đã thêm các phương thức riêng biệt cho một HttpTransfer và MvcTransfer cho phép tôi chuyển hướng đến trang lỗi, cụ thể khi cần. Tôi cũng thay đổi logic xung quanh để tôi có thể dễ dàng nhận được YSOD của tôi trên các máy địa phương và phát triển của tôi mà không cần xử lý nuốt ngoại lệ.

Với ngoại lệ của mã sử dụng để ghi lại các ngoại lệ đối với cơ sở dữ liệu (biểu thị bằng một comment TODO), mã cuối cùng mà chúng tôi đang sử dụng là:

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

tại sao bạn không bắt 404 trong global.asax của bạn?

protected void Application_Error(object sender, EventArgs args) { 

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

    } 
} 
+0

Điểm của mô-đun là giải pháp "viết một lần" để tôi không phải đặt mã ad-hoc trong Global.asax. Tôi nghĩ rằng tôi có thể có một sửa đổi để HttpModule đó là làm việc. Tôi sẽ đun nhỏ lên một chút và chạy một số bài kiểm tra để làm bài tập –

+0

er ... Gửi quá sớm. Tôi sẽ đun nhỏ lên một chút và chạy một số bài kiểm tra để chắc chắn rằng nó là để snuff và gửi nó trở lại đây vào ngày mai nếu tôi không thấy bất kỳ vấn đề. –

+0

Global.asax sẽ không được tư vấn khi xử lý 404 cho hình ảnh hoặc nội dung tĩnh khác. – DanO

0

Nếu tôi hiểu chính xác, bạn chỉ muốn xử lý lỗi cho các hành động dẫn đến 404?

Bạn có thể kiểm tra xem tuyến đường cho yêu cầu là null hay ngừng định tuyến - về cơ bản cách trình xử lý định tuyến url hoạt động để quyết định xem yêu cầu có nên tiếp tục vào đường ống mvc hay không.

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

Để chuyển sang một bên, chuyển hướng do 404 gây ra trải nghiệm người dùng ít hơn lý tưởng và khó chịu từ ASP.NET (nơi bạn không thể tách chế độ xem khỏi xử lý yêu cầu). Cách đúng để quản lý 404 là trả lại mã trạng thái 404 cho trình duyệt và hiển thị trang lỗi tùy chỉnh của bạn thay vì chuyển hướng (dẫn đến mã trạng thái 302 được gửi tới trình duyệt).