如何在 .NET MVC 2 中实现正确的 HTTP 错误处理?

Posted

技术标签:

【中文标题】如何在 .NET MVC 2 中实现正确的 HTTP 错误处理?【英文标题】:How to implement proper HTTP error handling in .NET MVC 2? 【发布时间】:2011-06-22 02:59:49 【问题描述】:

我整天都在努力在我的 ASP.NET MVC 2 应用程序中实现错误处理。我看过各种技术,但没有一个能正常工作。我正在使用 MVC2 和 .NET 4.0(在 MVC3 发布之前启动项目;我们将在发布初始版本后升级)。

此时,我很乐意正确处理 404 和 500 错误 - 403(需要授权)也很好,然后是各种其他具体响应。现在,我要么得到所有 404,所有 500,所有 404 之前的 302,或者 500 之前的所有 302。

这是我的要求(应该非常接近 HTTP 的基本要求):

如果找不到资源,则抛出 404,并显示带有请求 URL 的 404 特定页面。不要返回像 302 这样的中间响应代码。理想情况下,保留请求的 URL,而不是显示像 /Error/NotFound 这样的新 URL——但如果显示后者,请确保我们没有返回重定向响应来获取它。

如果发生内部服务器错误,则抛出 500,并显示特定于 500 的错误,并指出出现了什么问题。同样,不要返回中间响应代码,最好不要更改 URL。

这是我认为的 404:

    找不到静态文件:/Content/non-existent-dir/non-existent-file.txt 找不到控制器:/non-existent-controller/Foo/666 找到控制器,但未找到操作:/Home/non-existent-action/666 找到控制器和动作,但动作找不到请求的对象:/Home/Login/non-existent-id

这是我认为的 500:

    发布错误值:POST /User/New/new-user-name-too-long-for-db-column-constraint 与数据无关的问题,例如 Web 服务端点没有响应

其中一些问题需要由特定的控制器或模型来识别,然后控制器应该抛出相应的 HttpException。其余的应该更通用地处理。

对于 404 案例 #2,如果找不到控制器,我尝试使用自定义 ControllerFactory 抛出 404。 对于 404 案例 #3,我尝试使用自定义基本控制器来覆盖 HandleUnknownAction 并抛出 404。

在这两种情况下,我都会在 404 之前得到 302。而且,我从来没有得到 500 错误;如果我修改 Web.config 以在我的 Web 服务端点中输入一个错字,我仍然得到一个 302,然后是一个 404,表示找不到 Web 服务的 URL(控制器/操作)使用 . 我还将请求的 URL 作为(n 不需要的)查询字符串参数:/Error/NotFound?aspxerrorpath=/Home/non-existent-action

这两种技术都来自http://www.niksmit.com/wp/?p=17(How to get normal 404 (Page not found) error pages using ASP.Net MVC),指向http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/

如果在 Web.config 中我有 <customErrors mode="On" defaultRedirect="~/Error/Unknown" redirectMode="ResponseRedirect" />,我会得到相应的响应代码,但我的错误控制器永远不会被调用。取出 redirectMode 属性可以让我看到 MVC 错误视图,但中间有 302 和更改的 URL - 并且始终是相同的控制器(Unknown = 500;如果我将其更改为 NotFound 一切看起来像 404 )。

以下是我阅读并尝试实现的其他一些内容:

http://www.davidjuth.com/asp-net-mvc-error-handler.aspx http://sanjayuttam.com/wordpress/index.php/c-sharp/c-sharp-code-examples/error-handling-in-asp-net-mvc-1-part-2-of-2/ http://blog.hebbink.com/post/2010/12/14/NET-custom-404-error-page-returns-302-for-http-status.aspx http://blog.dantup.com/2009/04/aspnet-mvc-handleerror-attribute-custom.html http://weblogs.asp.net/scottgu/archive/2008/07/14/asp-net-mvc-preview-4-release-part-1.aspx

.. 以及一堆 *** 帖子。

在我看来,这种错误处理对于 Web 应用程序来说是非常基本的,MVC 框架应该有默认的开箱即用的方法,并让人们扩展它以使其工作。也许他们会在未来的版本中这样做。同时,有人可以给我详细介绍如何实现正确的 HTTP 响应吗?

【问题讨论】:

【参考方案1】:

这是一个非常古老的问题。但我认为如果我向您介绍一种更简洁的方法来处理我在亲爱的"Jesse Webb's answer" 中看到的 Http 异常,这是值得的。

解决方案是使用system.webServer 部分的httpErrors 元素:

<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="404" subStatusCode="-1" />
  <remove statusCode="500" subStatusCode="-1" />
  <error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
  <error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>

您也可以通过这种方式记录所有异常。 “阅读"Jesse Webb's answer"”。

这确实感觉更干净,并且与其他所有解决方案一样有效(没有重定向)。

注意:这仅适用于 IIS 7 及更高版本。 (因为最近添加了httpErrors 元素。

【讨论】:

【参考方案2】:

对于 HTTP 404 错误(没有重定向),请查看我关于该主题的博客文章。这可能会给你一些好主意:

http://hectorcorrea.com/blog/returning-http-404-in-asp-net-mvc/16

【讨论】:

很好——你正在按照 Darin 的建议去做,尽管他有更多的基础设施来处理多种异常类型。我现在正在努力尝试将他的逻辑从 Application_Error 中移到 ErrorController 本身。 内容不再可用 @Darrren 链接再次可用。【参考方案3】:

这是您可以使用的一种技术。定义一个ErrorsController,它将为错误页面提供服务:

public class ErrorsController : Controller

    public ActionResult Http404()
    
        Response.StatusCode = 404;
        return Content("404", "text/plain");
    

    public ActionResult Http500()
    
        Response.StatusCode = 500;
        return Content("500", "text/plain");
    

    public ActionResult Http403()
    
        Response.StatusCode = 403;
        return Content("403", "text/plain");
    

然后在Global.asax 中订阅Application_Error 事件,您可以在其中记录异常并执行ErrorsController 的相应操作:

protected void Application_Error(object sender, EventArgs e)

    var app = (MvcApplication)sender;
    var context = app.Context;
    var ex = app.Server.GetLastError();
    context.Response.Clear();
    context.ClearError();
    var httpException = ex as HttpException;

    var routeData = new RouteData();
    routeData.Values["controller"] = "errors";
    routeData.Values["exception"] = ex;
    routeData.Values["action"] = "http500";
    if (httpException != null)
    
        switch (httpException.GetHttpCode())
        
            case 404:
                routeData.Values["action"] = "http404";
                break;
            case 403:
                routeData.Values["action"] = "http403";
                break;
            case 500:
                routeData.Values["action"] = "http500";
                break;
        
    
    IController controller = new ErrorsController();
    controller.Execute(new RequestContext(new HttpContextWrapper(context), routeData));

现在剩下的就是开始抛出适当的异常了:

public class HomeController : Controller

    public ActionResult Index()
    
        throw new HttpException(404, "NotFound");
    

【讨论】:

看起来不错——我会在几个小时内试一试,然后告诉你它是如何工作的。我没有看到的一件事:您在哪里处理找不到 Controller 的情况? @Val,当找不到控制器时,ASP.NET MVC 会自动抛出代码为 404 的 HttpException,因此它将进入第一种情况。 好的,我实现了基础知识,现在我要回过头来为各种 HTTP 错误实现自定义错误视图,并传递相关信息以显示在每个错误中。我有更多的工作来连接它——构建一个错误视图模型,将它填充到 ErrorController 等,但基本功能很好用。没有 302,而且,正如您所评论的,丢失的动作和控制器得到了正确处理。谢谢 - 这是我见过的最简单、最干净的正常工作示例! 一如既往@DarinDimitrov 的回答非常出色!现在我在 Firebug 中得到 404 响应而不是 302。 @Darin:这也可以在 MVC3 中完成,还是有其他方法可以做到?如果有,怎么做?【参考方案4】:

这并不能回答您的问题,但重要的是要注意 HTTP 状态 500 表示服务器出现问题,因此您的示例:

POST /User/New/new-user-name-too-long-for-db-column-constraint

不是抛出 500 的有效理由,它是一个数据验证问题,应该由 MVC 数据注释或 jQuery 验证框架等处理。只是在文本框旁边显示一条错误消息,说“用户名太长”是好多了。

【讨论】:

是的,我应该验证并抛出验证错误。但是,如果我在无法插入或更新行时无法验证并从数据库获得约束错误,并且我没有正确检查,我想抛出内部服务器错误。事实上,这就是我发现我们缺少一些验证的原因!

以上是关于如何在 .NET MVC 2 中实现正确的 HTTP 错误处理?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.Net MVC 中实现视频文件流式传输?

如何在 ASP.NET MVC 5 中实现自定义身份验证

您将如何在 asp.net mvc 中实现面包屑助手?

如何在.net core mvc的搜索框中实现自动完成功能?

如何在 ASP.NET MVC 中实现自定义主体和身份?

在 ASP.NET MVC 2 中实现 DropDownList 的最佳方式?