111

我有一些基本代码来确定我的 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?
    }
}
4

10 回答 10

109

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

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,因此您将为所有这些命中创建一个会话对象。对于频繁使用的系统,这可能是一个问题(性能)。

于 2009-07-23T13:40:28.483 回答
30

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

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);
            }
        }
    }
于 2010-01-12T13:55:10.783 回答
11

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

全球.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);
    }

Ajax重定向结果

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");
    }
}
于 2011-05-10T15:45:39.120 回答
9

之前,我一直在纠结于在 MVC 应用程序中集中全局错误处理例程的想法。我在 ASP.NET 论坛上有一个帖子

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

于 2010-01-22T13:47:26.197 回答
7

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

于 2009-08-12T21:30:54.610 回答
6

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

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

全球.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>
}
于 2016-09-28T13:58:23.747 回答
5

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

于 2009-08-18T21:35:57.563 回答
4

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

于 2011-12-13T00:07:25.637 回答
1

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

routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
于 2016-05-11T06:53:05.327 回答
-1

我对这种错误处理方法有疑问:如果是 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 at System.Web.Mvc.ViewResult.FindView(ControllerContext context) ...... …………

所以

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

httpException 总是 null 然后 customErrors mode="On" :( 这是误导然后<customErrors mode="Off"/>或者<customErrors mode="RemoteOnly"/>用户看到 customErrors html,然后 customErrors mode="On" 这个代码也是错误的


这段代码的另一个问题是

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

返回带有代码 302 的页面,而不是实际错误代码(402,403 等)

于 2017-08-24T09:18:09.317 回答