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

Posted

技术标签:

【中文标题】如何使自定义错误页面在 ASP.NET MVC 4 中工作【英文标题】:How to make custom error pages work in ASP.NET MVC 4 【发布时间】:2012-12-04 00:25:44 【问题描述】:

我想要一个显示 500、404 和 403 的自定义错误页面。这是我所做的:

    在 web.config 中启用自定义错误如下:

    <customErrors mode="On" 
                  defaultRedirect="~/Views/Shared/Error.cshtml">
    
        <error statusCode="403" 
               redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
    
        <error statusCode="404" 
               redirect="~/Views/Shared/FileNotFound.cshtml" />
    
    </customErrors>
    

    HandleErrorAttribute注册为FilterConfig类中的全局操作过滤器,如下所示:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    
        filters.Add(new CustomHandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    
    

    为上述每条消息创建了一个自定义错误页面。 500 的默认值已开箱即用。

    在每个自定义错误页面视图中声明该页面的模型为System.Web.Mvc.HandleErrorInfo

对于 500,它显示自定义错误页面。对于其他人来说,它没有。

我有什么遗漏吗?

当我通读 HandleErrorAttribute 类的 OnException 方法中的代码时,看起来这并不是显示自定义错误的全部,它只处理 500 个错误。

如何处理其他错误?

【问题讨论】:

这个设置的奇怪之处在于您重定向到视图,而不是控制器操作。例如,谁应该渲染这些视图并传入模型?只是想。 这里的大多数答案要么不能处理所有情况,要么导致 Web 服务器以“不正确”的方式响应,即重定向到错误页面而不是返回错误响应。如果您关心服务器以 Web 服务器所期望的方式响应,那么这里有一篇非常详细的文章:benfoster.io/blog/aspnet-mvc-custom-error-pages。请注意,它不像这里的答案那么简单,所以如果您想要一个简单的答案,只需使用以下答案之一。 这是另一篇关于 asp.net 错误处理的各种技术的精彩文章dusted.codes/… 【参考方案1】:

我当前的设置(在 MVC3 上,但我认为它仍然适用)依赖于 ErrorController,所以我使用:

<system.web>
    <customErrors mode="On" defaultRedirect="~/Error">
      <error redirect="~/Error/NotFound" statusCode="404" />
    </customErrors>
</system.web>

控制器包含以下内容:

public class ErrorController : Controller

    public ViewResult Index()
    
        return View("Error");
    
    public ViewResult NotFound()
    
        Response.StatusCode = 404;  //you may want to set this to 200
        return View("NotFound");
    

以及您实现它们的方式的视图。不过,我倾向于添加一些逻辑,以在应用程序处于调试模式时显示堆栈跟踪和错误信息。所以 Error.cshtml 看起来像这样:

@model System.Web.Mvc.HandleErrorInfo
@
    Layout = "_Layout.cshtml";
    ViewBag.Title = "Error";

<div class="list-header clearfix">
    <span>Error</span>
</div>
<div class="list-sfs-holder">
    <div class="alert alert-error">
        An unexpected error has occurred. Please contact the system administrator.
    </div>
    @if (Model != null && HttpContext.Current.IsDebuggingEnabled)
    
        <div>
            <p>
                <b>Exception:</b> @Model.Exception.Message<br />
                <b>Controller:</b> @Model.ControllerName<br />
                <b>Action:</b> @Model.ActionName
            </p>
            <div style="overflow:scroll">
                <pre>
                    @Model.Exception.StackTrace
                </pre>
            </div>
        </div>
    
</div>

【讨论】:

您是否必须在 Global.asax 的 Application_Error 中为这个 Pablo 添加任何内容? 根据我的经验,控制器中的代码似乎没有执行。 MVC4 - 在不同的控制器中抛出 System.Exception 将使 Error.cshtml 文件呈现,但不会通过 ErrorController。还有其他人遇到这种情况吗? 对于其他任何认为这很有帮助但需要更多上下文的人; 标记位于 web.config 中的 内。 为其他人更新-显然我的问题正在发生,因为我在 CustomerErrors 元素上有 redirectMode="ResponseRewrite" 看在上帝的份上,请忽略代码中//you may want to set this to 200 的评论。不要那样做!【参考方案2】:

我已经完成了 pablo 解决方案,但我总是遇到错误 (MVC4)

未找到视图“错误”或其主视图,或者没有视图引擎支持搜索到的位置。

要摆脱这种情况,删除该行

 filters.Add(new HandleErrorAttribute());

在 FilterConfig.cs 中

【讨论】:

我到处寻找解决这个问题。这终于有了答案。我知道它为什么要这样做,但对于我来说,我不能,没有像其他人所说的那样彻底思考。当我说谢谢你指出这一点时,我想我和 360Airwalk 一样痛苦。传奇! 这是一个选项,错误控制器工作正常。但似乎当您在 FilterConfig.cs 中注册过滤器时,它会在共享和原始控制器的视图文件夹中查找 Error.cshtml。当您将 Error.cshtml 更改为我们的自定义 ErrorController 以外的任何内容时。但是有一个地方你可以添加这个注册,它是 global.asax.cs。如果您在 global.asax.cs 的 RegisterGlobalFilters(GlobalFilterCollection filters) 函数中添加提到的行并从 FilterConfig.cs 中删除,它可以工作。 我认为这与过滤器注册的顺序有关。保留错误控制器并将过滤器注册移动到 global.asax.cs。 public static void RegisterGlobalFilters(GlobalFilterCollection filters) filters.Add(new HandleErrorAttribute()); 【参考方案3】:

我做的事情比发布的其他解决方案需要更少的编码。

首先,在我的 web.config 中,我有以下内容:

<customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
   <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
   <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
</customErrors>

并且控制器(/Controllers/ErrorPageController.cs)包含以下内容:

public class ErrorPageController : Controller

    public ActionResult Oops(int id)
    
        Response.StatusCode = id;

        return View();
    

最后,视图包含以下内容(为简单起见,已删除,但它可以包含:

@ ViewBag.Title = "Oops! Error Encountered"; 

<section id="Page">
  <div class="col-xs-12 well">
    <table cellspacing="5" cellpadding="3" style="background-color:#fff;width:100%;" class="table-responsive">
      <tbody>
        <tr>
          <td valign="top" align="left" id="tableProps">
            <img   src="~/Images/PageError.gif" id="pagerrorImg">
          </td>
          <td  valign="middle" align="left" id="tableProps2">
            <h1 style="COLOR: black; FONT: 13pt/15pt verdana" id="errortype"><span id="errorText">@Response.Status</span></h1>
          </td>
        </tr>
        <tr>
          <td  colspan="2" id="tablePropsWidth"><font style="COLOR: black; FONT: 8pt/11pt verdana">Possible causes:</font>
          </td>
        </tr>
        <tr>
          <td  colspan="2" id="tablePropsWidth2">
            <font style="COLOR: black; FONT: 8pt/11pt verdana" id="LID1">
                            <hr>
                            <ul>
                                <li id="list1">
                                    <span class="infotext">
                                        <strong>Baptist explanation: </strong>There
                                        must be sin in your life. Everyone else opened it fine.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Presbyterian explanation: </strong>It's
                                        not God's will for you to open this link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong> Word of Faith explanation:</strong>
                                        You lack the faith to open this link. Your negative words have prevented
                                        you from realizing this link's fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Charismatic explanation: </strong>Thou
                                        art loosed! Be commanded to OPEN!<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Unitarian explanation:</strong> All
                                        links are equal, so if this link doesn't work for you, feel free to
                                        experiment with other links that might bring you joy and fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Buddhist explanation:</strong> .........................<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Episcopalian explanation:</strong>
                                        Are you saying you have something against homosexuals?<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Christian Science explanation: </strong>There
                                        really is no link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Atheist explanation: </strong>The only
                                        reason you think this link exists is because you needed to invent it.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Church counselor's explanation:</strong>
                                        And what did you feel when the link would not open?
                                    </span>
                                </li>
                            </ul>
                            <p>
                                <br>
                            </p>
                            <h2 style="font:8pt/11pt verdana; color:black" id="ietext">
                                <img   align="top" src="~/Images/Search.gif">
                                HTTP @Response.StatusCode - @Response.StatusDescription <br>
                            </h2>
                        </font>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

就这么简单。它可以很容易地扩展以提供更详细的错误信息,但ELMAH 为我处理了这个问题,并且 statusCode 和 statusDescription 是我通常需要的。

【讨论】:

我认为“~/ErrorPage/Oops/404”的.config文件中的重定向应该是“~/ErrorPage/Oops?404”吧?至少这对我有用。也许这只是取决于路由。 如何模拟 IIS 抛出的错误。无论是 500 还是 504。在 ASP.Net MVC 中做什么 - 5 代码来模拟来自 IIS 的异常,以便我可以测试我的自定义错误页面【参考方案4】:

这里似乎有许多步骤混杂在一起。我会提出我从头开始做的事情。

    创建ErrorPage 控制器

    public class ErrorPageController : Controller
    
        public ActionResult Index()
        
            return View();
        
    
        public ActionResult Oops(int id)
        
            Response.StatusCode = id;
            return View();
        
    
    

    为这两个操作添加视图(右键单击 -> 添加视图)。这些应该出现在名为 ErrorPage 的文件夹中。

    App_Start 内部打开FilterConfig.cs 并注释掉错误处理过滤器。

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    
        // Remove this filter because we want to handle errors ourselves via the ErrorPage controller
        //filters.Add(new HandleErrorAttribute());
    
    

    在 web.config 中,在 System.Web 下添加以下 &lt;customerErrors&gt; 条目

    <customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
        <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
        <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
    </customErrors>
    

    测试(当然)。在您的代码中抛出一个未处理的异常并看到它转到 id 为 500 的页面,然后使用指向不存在的页面的 URL 来查看 404。

【讨论】:

我收到此错误 An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated. 我从您的代码中获取的所有内容都在 web.config 文件中,我添加了 &lt;error redirect = "~/ControllerName/ActionName" statusCode="404"/&gt; 并且它运行良好:) 其余代码来自 @Pablo's回答。我正在使用 MVC 5 和实体框架 6。我没有从 FilterConfig.cs 中删除 filters.Add(new HandleErrorAttribute()) 如何模拟 IIS 抛出的错误。无论是 500 还是 504。在 ASP.Net MVC 中做什么 - 5 代码来模拟来自 IIS 的异常,以便我可以测试我的自定义错误页面 另外,如何抛出未处理的异常(步骤 5)。我是编码新手,请指导。 仍然对我不起作用?路由呢?我是否也需要为错误页面添加路由?如果我点击页面:localhost:84/Enforcer/blah 我会被重定向到:localhost:84/Enforcer/Enforcer/Error/NotFound?aspxerrorpath=/… 错误页面看起来像 Asp.NET 提供的标准错误页面。有什么想法吗? webconfig 中的 customerrors 元素应该可以解决这个问题。您的(项目创建的)默认路由代码应该可以正常工作。【参考方案5】:

我会推荐使用 Global.asax.cs 文件。

 protected void Application_Error(Object sender, EventArgs e)

    var exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    
        Server.Transfer("~/Error.aspx");
    
    if (exception != null)
    
        Server.Transfer("~/Error.aspx");
    
    try
    
        // This is to stop a problem where we were seeing "gibberish" in the
        // chrome and firefox browsers
        HttpApplication app = sender as HttpApplication;
        app.Response.Filter = null;
    
    catch
    
    

【讨论】:

我认为您不能在 MVC 中执行 Server.Transfer()。您是否认为 OP 有一个混合站点? 为什么要在mvc中使用Application_Error?我们有类似 [handleerror] 属性的选项和重定向 url 选项。 application_error 对此有什么特别的好处吗? 我们应该在 MVC 中使用 HandleErrorAttribute 并通过重写 OnException 方法,我们可以更好地处理它们【参考方案6】:

基于 maxspan 发布的答案,我整理了一个最小的 sample project on GitHub 显示所有工作部分。

基本上,我们只是在 global.asax.cs 中添加一个Application_Error 方法来拦截异常并给我们重定向的机会(或者更准确地说,传输请求) 到自定义错误页面。

    protected void Application_Error(Object sender, EventArgs e)
    
        // See http://***.com/questions/13905164/how-to-make-custom-error-pages-work-in-asp-net-mvc-4
        // for additional context on use of this technique

        var exception = Server.GetLastError();
        if (exception != null)
        
            // This would be a good place to log any relevant details about the exception.
            // Since we are going to pass exception information to our error page via querystring,
            // it will only be practical to issue a short message. Further detail would have to be logged somewhere.

            // This will invoke our error page, passing the exception message via querystring parameter
            // Note that we chose to use Server.TransferRequest, which is only supported in IIS 7 and above.
            // As an alternative, Response.Redirect could be used instead.
            // Server.Transfer does not work (see https://support.microsoft.com/en-us/kb/320439 )
            Server.TransferRequest("~/Error?Message=" + exception.Message);
        

    

错误控制器:

/// <summary>
/// This controller exists to provide the error page
/// </summary>
public class ErrorController : Controller

    /// <summary>
    /// This action represents the error page
    /// </summary>
    /// <param name="Message">Error message to be displayed (provided via querystring parameter - a design choice)</param>
    /// <returns></returns>
    public ActionResult Index(string Message)
    
        // We choose to use the ViewBag to communicate the error message to the view
        ViewBag.Message = Message;
        return View();
    


错误页面查看:

<!DOCTYPE html>

<html>
<head>
    <title>Error</title>
</head>
<body>

    <h2>My Error</h2>
    <p>@ViewBag.Message</p>
</body>
</html>

除了在 FilterConfig.cs

中禁用/删除 filters.Add(new HandleErrorAttribute()) 之外,不涉及任何其他内容
public class FilterConfig

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    
        //filters.Add(new HandleErrorAttribute()); // <== disable/remove
    

虽然实现起来非常简单,但我在这种方法中看到的一个缺点是使用查询字符串将异常信息传递到目标错误页面。

【讨论】:

【参考方案7】:

我已经完成了所有设置,但在我们的暂存服务器上仍然看不到状态代码 500 的正确错误页面,尽管事实上在本地开发服务器上一切正常。

我从 Rick Strahl 那里找到了对我有帮助的 this blog post。

我需要将Response.TrySkipIisCustomErrors = true; 添加到我的自定义错误处理代码中。

【讨论】:

@Shaun314 你的意思是你把代码放在哪里?在处理请求的操作中。您可以在该博客文章中查看示例。【参考方案8】:

这是我的解决方案。在我看来,使用 [ExportModelStateToTempData] / [ImportModelStateFromTempData] 不舒服。

~/Views/Home/Error.cshtml:

@
    ViewBag.Title = "Error";
    Layout = "~/Views/Shared/_Layout.cshtml";


<h2>Error</h2>
<hr/>

<div style="min-height: 400px;">

    @Html.ValidationMessage("Error")

    <br />
    <br />

    <button onclick="Error_goBack()" class="k-button">Go Back</button>
    <script>
        function Error_goBack() 
            window.history.back()
        
    </script>

</div>

~/Controllers/HomeController.sc:

public class HomeController : BaseController

    public ActionResult Index()
    
        return View();
    

    public ActionResult Error()
    
        return this.View();
    

    ...

~/Controllers/BaseController.sc:

public class BaseController : Controller

    public BaseController()  

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    
        if (filterContext.Result is ViewResult)
        
            if (filterContext.Controller.TempData.ContainsKey("Error"))
            
                var modelState = filterContext.Controller.TempData["Error"] as ModelState;
                filterContext.Controller.ViewData.ModelState.Merge(new ModelStateDictionary()  new KeyValuePair<string, ModelState>("Error", modelState) );
                filterContext.Controller.TempData.Remove("Error");
            
        
        if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
        
            if (filterContext.Controller.ViewData.ModelState.ContainsKey("Error"))
            
                filterContext.Controller.TempData["Error"] = filterContext.Controller.ViewData.ModelState["Error"];
            
        

        base.OnActionExecuted(filterContext);
    

~/Controllers/MyController.sc:

public class MyController : BaseController

    public ActionResult Index()
    
        return View();
    

    public ActionResult Details(int id)
    
        if (id != 5)
        
            ModelState.AddModelError("Error", "Specified row does not exist.");
            return RedirectToAction("Error", "Home");
        
        else
        
            return View("Specified row exists.");
        
    

祝你项目成功;-)

【讨论】:

【参考方案9】:

您可以在不修改 global.cs、弄乱 HandleErrorAttribute、执行 Response.TrySkipIisCustomErrors、连接 Application_Error 或其他任何事情的情况下正常工作:

在 system.web 中(只是通常的,开/关)

<customErrors mode="On">
  <error redirect="/error/401" statusCode="401" />
  <error redirect="/error/500" statusCode="500" />
</customErrors>

在system.webServer中

<httpErrors existingResponse="PassThrough" />

现在一切都应该符合预期了,您可以使用 ErrorController 来显示您需要的任何内容。

【讨论】:

如何模拟 IIS 抛出的错误。无论是 500 还是 504。在 ASP.Net MVC 中做什么 - 5 代码来模拟来自 IIS 的异常,以便我可以测试我的自定义错误页面 @Unbreakable 临时更改您的代码以引发异常。 对我没有任何影响。出现异常或 404 not found 错误时,我不会转到自定义错误页面。【参考方案10】:

在 web.config 中添加如下 system.webserver 标签,

<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="404"/>
  <remove statusCode="500"/>
  <error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound"/>
  <error statusCode="500" responseMode="ExecuteURL"path="/Error/ErrorPage"/>
</httpErrors>

并添加一个控制器,

public class ErrorController : Controller

    //
    // GET: /Error/
    [GET("/Error/NotFound")]
    public ActionResult NotFound()
    
        Response.StatusCode = 404;

        return View();
    

    [GET("/Error/ErrorPage")]
    public ActionResult ErrorPage()
    
        Response.StatusCode = 500;

        return View();
    

加上他们尊重的观点,我想这肯定对所有人都有效。

我从以下地址找到了这个解决方案:Neptune Century

【讨论】:

波斯字符编码混乱【参考方案11】:

看来我来晚了,但你最好也看看这个。

所以在 system.web 中用于缓存应用程序中的异常,例如 return HttpNotFound()

  <system.web>
    <customErrors mode="RemoteOnly">
      <error statusCode="404" redirect="/page-not-found" />
      <error statusCode="500" redirect="/internal-server-error" />
    </customErrors>
  </system.web>

并在system.webServer 中用于追赶被 IIS 捕获但没有进入 asp.net 框架的错误

 <system.webServer>
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/page-not-found" responseMode="Redirect"/>
      <remove statusCode="500"/>
      <error statusCode="500" path="/internal-server-error" responseMode="Redirect"/>
  </system.webServer>

如果您担心客户端响应,请在最后一个中将 responseMode="Redirect" 更改为 responseMode="File" 并提供静态 html 文件,因为这将显示一个带有 200 响应代码的友好页面。

【讨论】:

波斯字符编码混乱

以上是关于如何使自定义错误页面在 ASP.NET MVC 4 中工作的主要内容,如果未能解决你的问题,请参考以下文章

自定义 ASP.NET MVC 404 错误页面的路由

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

将自定义属性添加到 asp.net-mvc 中的页面指令

asp.net MVC 4 0x80070005 - Javascript 运行时错误:访问被拒绝

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

ASP.NET MVC 4 - 上传图片到数据库