【问题标题】:ASP.NET MVC Custom Error Handling Application_Error Global.asax?ASP.NET MVC 自定义错误处理 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?
    }
}

【问题讨论】:

    标签: c# asp.net asp.net-mvc error-handling user-experience


    【解决方案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 = "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");
          }
      }
      

      【讨论】:

      【解决方案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://stackoverflow.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 等)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2010-12-24
                • 1970-01-01
                • 1970-01-01
                • 2016-04-10
                • 1970-01-01
                • 1970-01-01
                • 2011-01-29
                相关资源
                最近更新 更多