简体   繁体   English

ASP.NET MVC自定义错误处理Application_Error Global.asax?

[英]ASP.NET MVC Custom Error Handling Application_Error Global.asax?

I have some basic code to determine errors in my MVC application. 我有一些基本代码来确定我的MVC应用程序中的错误。 Currently in my project I have a controller called Error with action methods HTTPError404() , HTTPError500() , and General() . 当前,在我的项目中,我有一个名为Error的控制器,该控制器具有操作方法HTTPError404()HTTPError500()General() They all accept a string parameter error . 它们都接受字符串参数error Using or modifying the code below. 使用或修改下面的代码。 What is the best/proper way to pass the data to the Error controller for processing? 将数据传递给错误控制器进行处理的最佳/正确方法是什么? I would like to have a solution as robust as possible. 我想有一个尽可能强大的解决方案。

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

Instead of creating a new route for that, you could just redirect to your controller/action and pass the information via querystring. 无需为此创建新路由,您只需重定向到控制器/操作并通过querystring传递信息即可。 For instance: 例如:

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

Then your controller will receive whatever you want: 然后,您的控制器将收到您想要的任何东西:

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

There are some tradeoffs with your approach. 您的方法需要权衡取舍。 Be very very careful with looping in this kind of error handling. 在这种错误处理中循环时要非常小心。 Other thing is that since you are going through the asp.net pipeline to handle a 404, you will create a session object for all those hits. 另一件事是,由于要通过asp.net管道来处理404,因此将为所有这些匹配创建会话对象。 This can be an issue (performance) for heavily used systems. 对于频繁使用的系统,这可能是一个问题(性能)。

To answer the initial question "how to properly pass routedata to error controller?": 要回答最初的问题“如何正确地将routedata传递给错误控制器?”:

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

Then in your ErrorController class, implement a function like this: 然后在您的ErrorController类中,实现如下功能:

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

This pushes the exception into the View. 这会将异常推送到视图中。 The view page should be declared as follows: 视图页面应声明如下:

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

And the code to display the error: 以及显示错误的代码:

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

Here is the function that gathers the all exception messages from the exception tree: 这是从异常树中收集所有异常消息的函数:

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

I struggled with the idea of centralizing a global error handling routine in an MVC app before. 之前,我不敢在MVC应用程序中集中全局错误处理例程的想法。 I have a post on the ASP.NET forums . 在ASP.NET论坛上有一篇文章

It basically handles all your application errors in the global.asax without the need for an error controller, decorating with the [HandlerError] attribute, or fiddling with the customErrors node in the web.config. 它基本上可以处理global.asax中的所有应用程序错误,而无需错误控制器,使用[HandlerError]属性进行[HandlerError]或使用web.config中的customErrors节点。

I found a solution for ajax issue noted by Lion_cl. 我找到了Lion_cl指出的ajax问题的解决方案。

global.asax: 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 ErrorPagesController

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

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

Perhaps a better way of handling errors in MVC is to apply the HandleError attribute to your controller or action and update the Shared/Error.aspx file to do what you want. 在MVC中处理错误的更好方法可能是将HandleError属性应用于控制器或操作,并更新Shared / Error.aspx文件以执行所需的操作。 The Model object on that page includes an Exception property as well as ControllerName and ActionName. 该页面上的Model对象包括Exception属性以及ControllerName和ActionName。

Application_Error having issue with Ajax requests. Application_Error与Ajax请求有关。 If error handled in Action which called by Ajax - it will display your Error View inside the resulting container. 如果在由Ajax调用的Action中处理了错误,它将在结果容器内显示您的错误视图。

This may not be the best way for MVC ( https://stackoverflow.com/a/9461386/5869805 ) 这可能不是MVC的最佳方法( https://stackoverflow.com/a/9461386/5869805

Below is how you render a view in Application_Error and write it to http response. 下面是如何在Application_Error中呈现视图并将其写入http响应。 You do not need to use redirect. 您不需要使用重定向。 This will prevent a second request to server, so the link in browser's address bar will stay same. 这将阻止对服务器的第二次请求,因此浏览器地址栏中的链接将保持不变。 This may be good or bad, it depends on what you want. 这可能是好是坏,这取决于您想要什么。

Global.asax.cs 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.
}

View 视图

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

Brian,这种方法对非Ajax请求非常有效,但是正如Lion_cl所述,如果在Ajax调用过程中出错,则您的Share / Error.aspx视图(或自定义错误页面视图)将返回给Ajax调用者, -用户将不会被重定向到错误页面。

Use Following code for redirecting on route page. 使用以下代码在路线页面上进行重定向。 Use exception.Message instide of exception. 使用exception.Message实例化异常。 Coz exception query string gives error if it extends the querystring length. 如果Coz异常查询字符串扩展了查询字符串的长度,则会给出错误。

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

I have problem with this error handling approach: In case of web.config: 我对这种错误处理方法有疑问:对于web.config:

<customErrors mode="On"/>

The error handler is searching view Error.shtml and the control flow step in to Application_Error global.asax only after exception 错误处理程序正在搜索View Error.shtml,并且仅在异常发生后,控制流才进入Application_Error global.asax。

System.InvalidOperationException: The view 'Error' or its master was not found or no view engine supports the searched locations. System.InvalidOperationException:找不到视图“错误”或其主视图,或者没有视图引擎支持搜索到的位置。 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) .................... 搜索以下位置:〜/ 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位于System.Web.Mvc.ViewResult.FindView(ControllerContext上下文)........ ............

So 所以

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

httpException is always null then customErrors mode="On" :( It is misleading Then <customErrors mode="Off"/> or <customErrors mode="RemoteOnly"/> the users see customErrors html, Then customErrors mode="On" this code is wrong too httpException始终为null,然后customErrors mode =“ On” :(这具有误导性,然后<customErrors mode="Off"/><customErrors mode="RemoteOnly"/>用户会看到customErrors html,然后customErrors mode =“ On”这样代码也是错误的


Another problem of this code that 该代码的另一个问题是

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

Return page with code 302 instead real error code(402,403 etc) 返回页面,代码为302,而不是实际错误代码(402,403等)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM