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

Posted

技术标签:

【中文标题】ASP.NET MVC 自定义错误处理 Application_Error Global.asax?【英文标题】:ASP.NET MVC Custom Error Handling Application_Error Global.asax? 【发布时间】:2010-11-13 08:24:46 【问题描述】:

我有一些基本代码来确定我的 MVC 应用程序中的错误。目前在我的项目中,我有一个名为Error 的控制器,其操作方法为HTTPError404()HTTPError500()General()。它们都接受字符串参数error。使用或修改下面的代码。 将数据传递给错误控制器进行处理的最佳/正确方法是什么?我希望有一个尽可能强大的解决方案。

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?
    

【问题讨论】:

【参考方案1】:

您可以重定向到您的控制器/操作并通过查询字符串传递信息,而不是为此创建新路由。例如:

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

然后你的控制器会收到你想要的任何东西:

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

您的方法需要权衡取舍。在这种错误处理中循环时要非常小心。另一件事是,由于您正在通过 asp.net 管道处理 404,因此您将为所有这些命中创建一个会话对象。对于频繁使用的系统,这可能是一个问题(性能)。

【讨论】:

当你说“小心循环”时,你到底是什么意思?有没有更好的方法来处理这种类型的错误重定向(假设它是一个频繁使用的系统)? 循环我的意思是当你的错误页面中有错误时,你会一次又一次地被重定向到你的错误页面......(例如,你想在数据库中记录你的错误并且它已关闭)。 错误重定向违背了网络架构。当服务器响应正确的 HTTP 状态代码时,URI 应该保持不变,以便客户端知道失败的确切上下文。实现 HandleErrorAttribute.OnException 或 Controller.OnException 是更好的解决方案。如果失败,请在 Global.asax 中执行 Server.Transfer("~/Error")。 @Chris,这是可以接受的,但不是最佳实践。尤其是因为它经常被重定向到一个带有 HTTP 200 状态代码的资源文件,这让客户端相信一切正常。 我必须将 添加到 web.config 以使其在服务器上工作。【参考方案2】:

回答最初的问题“如何正确地将路由数据传递给错误控制器?”:

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

然后在您的 ErrorController 类中,实现如下函数:

[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)

    return View("Error", exception);

这会将异常推送到视图中。视图页面应声明如下:

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

以及显示错误的代码:

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

这是从异常树中收集所有异常消息的函数:

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

【讨论】:

【参考方案3】:

我找到了 Lion_cl 指出的 ajax 问题的解决方案。

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

错误页面控制器

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 = "tryhistory.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");
    

【讨论】:

在实现这一点时,我收到以下错误:“System.Web.HttpRequest”不包含“IsAjaxRequest”的定义。本文有解决办法:***.com/questions/14629304/…【参考方案4】:

之前,我曾为在 MVC 应用程序中集中一个全局错误处理例程的想法而苦恼。我有一个post on the ASP.NET forums。

它基本上可以处理 global.asax 中的所有应用程序错误,而无需错误控制器、使用 [HandlerError] 属性进行装饰或在 web.config 中摆弄 customErrors 节点。

【讨论】:

【参考方案5】:

也许在 MVC 中处理错误的更好方法是将 HandleError 属性应用于您的控制器或操作并更新 Shared/Error.aspx 文件以执行您想要的操作。该页面上的 Model 对象包括一个 Exception 属性以及 ControllerName 和 ActionName。

【讨论】:

那么您将如何处理404 错误?因为没有为此指定控制器/操作? 接受的答案包括 404。这种方法只对 500 个错误有用。 也许您应该将其编辑到您的答案中。 Perhaps a better way of handling errors 听起来很像 All Errors 而不仅仅是 500。【参考方案6】:

这可能不是 MVC 的最佳方式 (https://***.com/a/9461386/5869805)

以下是如何在 Application_Error 中呈现视图并将其写入 http 响应。您不需要使用重定向。这将阻止对服务器的第二次请求,因此浏览器地址栏中的链接将保持不变。这可能是好是坏,这取决于你想要什么。

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.

查看

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

【讨论】:

这是 IMO 的最佳方式。正是我想要的。 @SteveHarris 很高兴它有帮助! :) 我正在使用这种方法,但是一些错误没有显示在错误页面中,但它也进入了错误页面。 我会将断点放在 Application_Error 方法的开头和视图代码中以“@if(”开头的行,然后检查 (i) 执行是否真的命中 Application_Error 方法,(ii)视图代码中的模型不为空,并且包含异常对象,并且 (iii) 启用了调试。顺便说一句,据我所知,视图上的断点仅在视图代码自上次编译后未更改时才有效。【参考方案7】:

Application_Error 与 Ajax 请求有关。如果在 Ajax 调用的 Action 中处理了错误 - 它会在结果容器中显示您的错误视图。

【讨论】:

【参考方案8】:

布赖恩, 这种方法适用于非 Ajax 请求,但正如 Lion_cl 所说,如果您在 Ajax 调用期间出现错误,您的 Share/Error.aspx 视图(或您的自定义错误页面视图)将返回给 Ajax 调用者——用户不会被重定向到错误页面。

【讨论】:

【参考方案9】:

使用以下代码在路由页面上进行重定向。 在异常中使用 exception.Message。 Coz 异常查询字符串如果扩展查询字符串长度会给出错误。

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

【讨论】:

【参考方案10】:

我对这种错误处理方法有疑问: 如果是 web.config:

<customErrors mode="On"/>

错误处理程序正在搜索视图 Error.shtml 只有在异常发生后,控制流才会进入 Application_Error global.asax

System.InvalidOperationException:视图“错误”或其主视图是 未找到或没有视图引擎支持搜索的位置。这 搜索了以下位置:~/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 上下文) .....................

所以

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

httpException 总是 null 然后 customErrors 模式=“开” :( 这是误导 然后&lt;customErrors mode="Off"/&gt;&lt;customErrors mode="RemoteOnly"/&gt; 用户看到customErrors html, 那么customErrors mode="On" 这个代码也是错误的


这段代码的另一个问题

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

返回代码为 302 的页面,而不是真正的错误代码(402,403 等)

【讨论】:

以上是关于ASP.NET MVC 自定义错误处理 Application_Error Global.asax?的主要内容,如果未能解决你的问题,请参考以下文章

asp.net MVC3 上的自定义错误页面

七天学会ASP.NET MVC ——线程问题异常处理自定义URL

ASP.NET MVC Ajax 错误处理

未记录 ELMAH 和 ASP.NET MVC 自定义错误

如何在 Asp.Net Mvc 3 中显示自定义错误页面?

如何使自定义错误页面在 ASP.NET MVC 4 中工作