ASP.NET MVC Xử lý lỗi tùy chỉnh Application_Error Global.asax?


108

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 nay trong dự án của tôi, tôi có một bộ điều khiển được gọi Errorvới các phương pháp hành động HTTPError404(), HTTPError500()General(). Tất cả chúng đều chấp nhận một 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 càng mạnh càng tốt.

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

Câu trả lời:


104

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 mình 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 đó, bộ điều khiển của bạn sẽ nhận được bất kỳ thứ gì bạn muốn:

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

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


Khi bạn nói "cẩn thận với vòng lặp", chính xác thì bạn muốn nói gì? 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 23/07/09

4
Bằng cách lặp lại ý tôi là khi bạn gặp lỗi trong trang lỗi của mình, sau đó bạn sẽ được chuyển hướng đến trang lỗi của mình nhiều lần ... (ví dụ: bạn muốn ghi lỗi của mình vào cơ sở dữ liệu và nó bị lỗi).
andrecarlucci

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

1
@Chris, Có thể chấp nhận được, nhưng không phải là phương pháp hay nhất. Đặc biệt là vì nó thường được chuyển hướng đến một tệp tài nguyên được cung cấp với mã trạng thái HTTP 200, điều này khiến khách hàng tin rằng mọi thứ đều ổn.
Asbjørn Ulsberg

1
Tôi phải thêm <httpErrors errorMode = "Chi tiết" /> vào web.config để làm cho nó hoạt động trên máy chủ.
Jeroen K

28

Để trả lời câu hỏi ban đầu "làm thế nào để chuyển đúng dữ liệu định tuyến đến bộ điều khiển lỗi?":

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

Sau đó, trong lớp ErrorController của bạn, hãy triển khai một hàm như sau:

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

Điều này đẩy ngoại lệ vào Chế độ xem. Trang xem phải đượ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à hàm tập hợp tất cả các thông báo 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);
            }
        }
    }

9

Tôi đã tìm thấy giải pháp cho vấn đề ajax được Lion_cl lưu ý.

global.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");
    }
}

Trong khi thực hiện điều này, tôi gặp lỗi sau: 'System.Web.HttpRequest' không chứa định nghĩa cho 'IsAjaxRequest'. Bài viết này có một giải pháp: stackoverflow.com/questions/14629304/...
Julian Dormon

8

Trước đây, tôi đã phải vật lộn với ý tưởng tập trung quy trình xử lý lỗi toàn cầu trong một ứng dụng MVC. Tôi có một bài đăng trên diễn đàn ASP.NET .

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í bằng [HandlerError]thuộc tính hoặc loay hoay với customErrorsnút trong web.config.


6

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


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

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

Có lẽ bạn nên chỉnh sửa câu trả lời đó thành câu trả lời của mình. Perhaps a better way of handling errorsnghe khá giống Tất cả các lỗi chứ không phải chỉ 500.
Dementic

4

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


4

Đâ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 hiển thị một dạng xem trong Application_Error và ghi nó vào phản hồi http. Bạn không cần phải sử dụng chuyển hướng. Điều này sẽ ngăn chặn yêu cầu thứ hai đến máy chủ, vì vậy liên kết trong thanh địa chỉ của trình duyệt sẽ giữ nguyên. Điều này có thể tốt hoặc 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.
}

Lượt 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>
}

Đây là cách tốt nhất IMO. Chính xác những gì tôi đang tìm kiếm.
Steve Harris

@SteveHarris rất vui vì nó đã giúp ích! :)
burkay

3

Brian, Cách tiếp cận này hoạt động hiệu quả đối với các yêu cầu không phải Ajax, nhưng như Lion_cl đã nêu, nếu bạn gặp lỗi trong khi gọi Ajax, chế độ xem Share / Error.aspx của bạn (hoặc chế độ xem trang lỗi tùy chỉnh của bạn) sẽ được trả về trình 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 của ngoại lệ. Chuỗi truy vấn ngoại lệ Coz đưa ra lỗi nếu nó kéo dài độ dài chuỗi truy vấn.

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

-1

Tôi gặp sự cố với phương pháp xử lý lỗi này: Trong trường hợp web.config:

<customErrors mode="On"/>

Trình xử lý lỗi đang tìm kiếm chế độ xem Error.shtml và luồng điều khiển bước vào Application_Error global.asax chỉ sau ngoại lệ

System.InvalidOperationException: Chế độ xem 'Lỗi' hoặc chế độ xem chính của nó không được tìm thấy hoặc không có công cụ chế độ xem nào hỗ trợ các vị trí được tìm kiếm. Các vị trí sau đã được tìm kiếm: ~ / 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 tại System.Web.Mvc.ViewResult.FindView (ControllerContext context) ........ ............

Vì thế

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

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


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

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

Trả lại trang với mã 302 thay vì mã lỗi thực (402.403, v.v.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.