2009-07-23 56 views
93

Tôi có một số mã cơ bản để xác định lỗi trong ứng dụng MVC của mình. Hiện tại trong dự án của tôi, tôi có một bộ điều khiển được gọi là Error với các phương thức hành động HTTPError404(), HTTPError500()General(). Tất cả đều chấp nhận tham số chuỗi error. Sử dụng hoặc sửa đổi mã bên dưới. Cách tốt nhất/thích hợp để chuyển dữ liệu đến bộ điều khiển Lỗi để xử lý là gì? Tôi muốn có một giải pháp mạnh mẽ nhất có thể.ASP.NET MVC Lỗi tùy chỉnh xử lý Application_Error Global.asax?

protected void Application_Error(object sender, EventArgs e) 
{ 
    Exception exception = Server.GetLastError(); 
    Response.Clear(); 

    HttpException httpException = exception as HttpException; 
    if (httpException != null) 
    { 
     RouteData routeData = new RouteData(); 
     routeData.Values.Add("controller", "Error"); 
     switch (httpException.GetHttpCode()) 
     { 
      case 404: 
       // page not found 
       routeData.Values.Add("action", "HttpError404"); 
       break; 
      case 500: 
       // server error 
       routeData.Values.Add("action", "HttpError500"); 
       break; 
      default: 
       routeData.Values.Add("action", "General"); 
       break; 
     } 
     routeData.Values.Add("error", exception); 
     // clear error on server 
     Server.ClearError(); 

     // at this point how to properly pass route data to error controller? 
    } 
} 

Trả lời

94

Thay vì tạo một tuyến mới cho điều đó, bạn chỉ có thể chuyển hướng đến bộ điều khiển/hành động của bạn và chuyển thông tin qua chuỗi truy vấn. Ví dụ:

protected void Application_Error(object sender, EventArgs e) { 
    Exception exception = Server.GetLastError(); 
    Response.Clear(); 

    HttpException httpException = exception as HttpException; 

    if (httpException != null) { 
    string action; 

    switch (httpException.GetHttpCode()) { 
     case 404: 
     // page not found 
     action = "HttpError404"; 
     break; 
     case 500: 
     // server error 
     action = "HttpError500"; 
     break; 
     default: 
     action = "General"; 
     break; 
     } 

     // clear error on server 
     Server.ClearError(); 

     Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message)); 
    } 

Sau đó điều khiển của bạn sẽ nhận được bất cứ điều gì bạn muốn:

// GET: /Error/HttpError404 
public ActionResult HttpError404(string message) { 
    return View("SomeView", message); 
} 

Có một số cân bằng với cách tiếp cận của bạn. Hãy rất cẩn thận với vòng lặp trong loại xử lý lỗi này. Điều khác là vì bạn đang đi qua các đường ống asp.net để xử lý một 404, bạn sẽ tạo ra một đối tượng phiên cho tất cả những người truy cập. Đây có thể là một vấn đề (hiệu suất) cho các hệ thống được sử dụng nhiều.

+0

Khi bạn nói "hãy cẩn thận của Looping" chính xác những gì bạn có ý nghĩa ?Có cách nào tốt hơn để xử lý loại chuyển hướng lỗi này (giả sử nó là một hệ thống được sử dụng nhiều)? – aherrick

+3

Bằng cách lặp lại, tôi có nghĩa là khi bạn gặp lỗi trong trang lỗi, bạn sẽ bị chuyển hướng đến trang lỗi của bạn một lần nữa ... (ví dụ: bạn muốn đăng nhập lỗi trong cơ sở dữ liệu và nó bị hỏng). – andrecarlucci

+112

Chuyển hướng trên các lỗi đi ngược lại với kiến ​​trúc của web. URI sẽ vẫn giữ nguyên khi máy chủ phản hồi mã trạng thái HTTP chính xác để khách hàng biết được ngữ cảnh chính xác của lỗi. Thực hiện HandleErrorAttribute.OnException hoặc Controller.OnException là một giải pháp tốt hơn. Và nếu những thất bại đó, hãy làm một Server.Transfer ("~/Error") trong Global.asax. –

6

Có lẽ cách xử lý lỗi trong MVC tốt hơn là áp dụng thuộc tính HandleError cho bộ điều khiển hoặc tác vụ của bạn và cập nhật tệp Shared/Error.aspx để thực hiện những gì bạn muốn. Đối tượng Model trên trang đó bao gồm một thuộc tính Ngoại lệ cũng như ControllerName và ActionName.

+0

Bạn sẽ xử lý lỗi '404' sau đó như thế nào? vì không có bộ điều khiển/hành động nào được chỉ định cho điều đó? – Dementic

+0

Câu trả lời được chấp nhận bao gồm 404. Cách tiếp cận này chỉ hữu ích cho 500 lỗi. – Brian

+0

Có lẽ bạn nên chỉnh sửa điều đó vào câu trả lời của mình. 'Có lẽ cách xử lý lỗi tốt hơn có vẻ giống như Tất cả lỗi và không chỉ 500. – Dementic

4

Application_Error gặp vấn đề với các yêu cầu Ajax. Nếu lỗi được xử lý trong Hành động được gọi bằng Ajax - nó sẽ hiển thị Chế độ xem Lỗi của bạn bên trong vùng chứa kết quả.

26

Để trả lời câu hỏi ban đầu "làm thế nào để vượt qua đúng RouteData lỗi điều khiển?":

IController errorController = new ErrorController(); 
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); 

Sau đó, trong lớp ErrorController của bạn, thực hiện một chức năng như thế này:

[AcceptVerbs(HttpVerbs.Get)] 
public ViewResult Error(Exception exception) 
{ 
    return View("Error", exception); 
} 

này đẩy ngoại lệ vào Chế độ xem. Trang điểm cần được khai báo như sau:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %> 

Và mã để hiển thị lỗi:

<% if(Model != null) { %> <p><b>Detailed error:</b><br /> <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %> 

Đây là chức năng mà tập hợp tất cả các thông điệp ngoại lệ từ cây ngoại lệ:

public static string GetErrorMessage(Exception ex, bool includeStackTrace) 
    { 
     StringBuilder msg = new StringBuilder(); 
     BuildErrorMessage(ex, ref msg); 
     if (includeStackTrace) 
     { 
      msg.Append("\n"); 
      msg.Append(ex.StackTrace); 
     } 
     return msg.ToString(); 
    } 

    private static void BuildErrorMessage(Exception ex, ref StringBuilder msg) 
    { 
     if (ex != null) 
     { 
      msg.Append(ex.Message); 
      msg.Append("\n"); 
      if (ex.InnerException != null) 
      { 
       BuildErrorMessage(ex.InnerException, ref msg); 
      } 
     } 
    } 
8

Tôi gặp khó khăn với ý tưởng tập trung một lỗi xử lý lỗi toàn cầu trong ứng dụng MVC trước đây. Tôi có một số post on the ASP.NET forums.

Về cơ bản, nó xử lý tất cả các lỗi ứng dụng của bạn trong global.asax mà không cần bộ điều khiển lỗi, trang trí với thuộc tính [HandlerError] hoặc không sử dụng nút customErrors trong web.config.

9

Tôi đã tìm thấy giải pháp cho vấn đề ajax được ghi nhận bởi Lion_cl.

toàn cầu.tuyến Asax:

protected void Application_Error() 
    {   
     if (HttpContext.Current.Request.IsAjaxRequest()) 
     { 
      HttpContext ctx = HttpContext.Current; 
      ctx.Response.Clear(); 
      RequestContext rc = ((MvcHandler)ctx.CurrentHandler).RequestContext; 
      rc.RouteData.Values["action"] = "AjaxGlobalError"; 

      // TODO: distinguish between 404 and other errors if needed 
      rc.RouteData.Values["newActionName"] = "WrongRequest"; 

      rc.RouteData.Values["controller"] = "ErrorPages"; 
      IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 
      IController controller = factory.CreateController(rc, "ErrorPages"); 
      controller.Execute(rc); 
      ctx.Server.ClearError(); 
     } 
    } 

ErrorPagesController

public ActionResult AjaxGlobalError(string newActionName) 
    { 
     return new AjaxRedirectResult(Url.Action(newActionName), this.ControllerContext); 
    } 

AjaxRedirectResult

public class AjaxRedirectResult : RedirectResult 
{ 
    public AjaxRedirectResult(string url, ControllerContext controllerContext) 
     : base(url) 
    { 
     ExecuteResult(controllerContext); 
    } 

    public override void ExecuteResult(ControllerContext context) 
    { 
     if (context.RequestContext.HttpContext.Request.IsAjaxRequest()) 
     { 
      JavaScriptResult result = new JavaScriptResult() 
      { 
       Script = "try{history.pushState(null,null,window.location.href);}catch(err){}window.location.replace('" + UrlHelper.GenerateContentUrl(this.Url, context.HttpContext) + "');" 
      }; 

      result.ExecuteResult(context); 
     } 
     else 
     { 
      base.ExecuteResult(context); 
     } 
    } 
} 

AjaxRequestExtension

public static class AjaxRequestExtension 
{ 
    public static bool IsAjaxRequest(this HttpRequest request) 
    { 
     return (request.Headers["X-Requested-With"] != null && request.Headers["X-Requested-With"] == "XMLHttpRequest"); 
    } 
} 
+0

Trong khi thực hiện điều này tôi nhận được lỗi sau: 'System.Web.HttpRequest' không chứa định nghĩa cho 'IsAjaxRequest'. Bài viết này có giải pháp: http://stackoverflow.com/questions/14629304/httpcontext-current-request-isajaxrequest-error-in-mvc-4 –

3

Brian, Cách tiếp cận này hoạt động tuyệt vời cho không Các yêu cầu n-Ajax, nhưng như Lion_cl đã nói, nếu bạn gặp lỗi trong khi gọi Ajax, khung nhìn Share/Error.aspx của bạn (hoặc khung nhìn trang lỗi tùy chỉnh) của bạn sẽ được trả về cho người gọi Ajax - người dùng sẽ KHÔNG được chuyển hướng đến trang lỗi.

0

Sử dụng mã sau để chuyển hướng trên trang tuyến đường. Sử dụng ngoại lệ.Message instide of exception. Chuỗi truy vấn ngoại lệ Coz cho lỗi nếu nó mở rộng độ dài chuỗi truy vấn.

routeData.Values.Add("error", exception.Message); 
// clear error on server 
Server.ClearError(); 
Response.RedirectToRoute(routeData.Values); 
1

Điều này có thể không phải là cách tốt nhất cho MVC (https://stackoverflow.com/a/9461386/5869805)

Dưới đây là cách bạn làm cho một cái nhìn trong Application_Error và ghi nó vào http phản ứng. Bạn không cần sử dụng chuyển hướng. Điều này sẽ ngăn yêu cầu thứ hai đến máy chủ, do đó liên kết trong thanh địa chỉ của trình duyệt sẽ vẫn giữ nguyên. Điều này có thể tốt hay xấu, nó phụ thuộc vào những gì bạn muốn.

Global.asax.cs

protected void Application_Error() 
{ 
    var exception = Server.GetLastError(); 
    // TODO do whatever you want with exception, such as logging, set errorMessage, etc. 
    var errorMessage = "SOME FRIENDLY MESSAGE"; 

    // TODO: UPDATE BELOW FOUR PARAMETERS ACCORDING TO YOUR ERROR HANDLING ACTION 
    var errorArea = "AREA"; 
    var errorController = "CONTROLLER"; 
    var errorAction = "ACTION"; 
    var pathToViewFile = $"~/Areas/{errorArea}/Views/{errorController}/{errorAction}.cshtml"; // THIS SHOULD BE THE PATH IN FILESYSTEM RELATIVE TO WHERE YOUR CSPROJ FILE IS! 

    var requestControllerName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["controller"]); 
    var requestActionName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["action"]); 

    var controller = new BaseController(); // REPLACE THIS WITH YOUR BASE CONTROLLER CLASS 
    var routeData = new RouteData { DataTokens = { { "area", errorArea } }, Values = { { "controller", errorController }, {"action", errorAction} } }; 
    var controllerContext = new ControllerContext(new HttpContextWrapper(HttpContext.Current), routeData, controller); 
    controller.ControllerContext = controllerContext; 

    var sw = new StringWriter(); 
    var razorView = new RazorView(controller.ControllerContext, pathToViewFile, "", false, null); 
    var model = new ViewDataDictionary(new HandleErrorInfo(exception, requestControllerName, requestActionName)); 
    var viewContext = new ViewContext(controller.ControllerContext, razorView, model, new TempDataDictionary(), sw); 
    viewContext.ViewBag.ErrorMessage = errorMessage; 
    //TODO: add to ViewBag what you need 
    razorView.Render(viewContext, sw); 
    HttpContext.Current.Response.Write(sw); 
    Server.ClearError(); 
    HttpContext.Current.Response.End(); // No more processing needed (ex: by default controller/action routing), flush the response out and raise EndRequest event. 
} 

Xem

@model HandleErrorInfo 
@{ 
    ViewBag.Title = "Error"; 
    // TODO: SET YOUR LAYOUT 
} 
<div class=""> 
    ViewBag.ErrorMessage 
</div> 
@if(Model != null && HttpContext.Current.IsDebuggingEnabled) 
{ 
    <div class="" style="background:khaki"> 
     <p> 
      <b>Exception:</b> @Model.Exception.Message <br/> 
      <b>Controller:</b> @Model.ControllerName <br/> 
      <b>Action:</b> @Model.ActionName <br/> 
     </p> 
     <div> 
      <pre> 
       @Model.Exception.StackTrace 
      </pre> 
     </div> 
    </div> 
} 
-1

Tôi có vấn đề với xử lý lỗi này cách tiếp cận: Trong trường hợp của web.config:

<customErrors mode="On"/> 

Các xử lý lỗi đang tìm kiếm xem Error.shtml và bước kiểm soát dòng chảy để Application_Error global.asax chỉ sau khi ngoại lệ

System.InvalidOperationException: The view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/home/Error.aspx ~/Views/home/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx ~/Views/home/Error.cshtml ~/Views/home/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml at System.Web.Mvc.ViewResult.FindView(ControllerContext context) ....................

Vì vậy

Exception exception = Server.GetLastError(); 
    Response.Clear(); 
    HttpException httpException = exception as HttpException; 

httpException luôn là null thì customErrors mode =" Trên " :( Đó là gây hiểu lầm Sau đó, <customErrors mode="Off"/> hoặc <customErrors mode="RemoteOnly"/> người dùng xem customErrors html, Sau đó, customErrors mode =" On "mã này sai quá


Một vấn đề khác của mã này rằng trang

Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message)); 

Return với mã 302 mã lỗi thay vì thực (402.403 vv)

Các vấn đề liên quan