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

39

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

目前,我希望能够正确处理404和500错误--403(需要授权)也很好,然后是其他特定响应。目前,我要么得到全部404、全部500、在404之前的所有302或在500之前的所有302。

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

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

  • 如果发生内部服务器错误,则抛出500,并显示一个具有某些出错信息的500特定错误。同样,不要返回中间响应代码,并且理想情况下不要更改URL。

以下是我认为是404的内容:

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

以下是我认为是500的内容:

  1. 发布错误的值:POST /User/New/new-user-name-too-long-for-db-column-constraint
  2. 非数据相关问题,比如Web服务端点不响应

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

对于404情况#2,我试图使用自定义的ControllerFactory在找不到控制器时抛出404错误。 对于404情况#3,我尝试使用自定义基础控制器来覆盖HandleUnknownAction并抛出404错误。

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

这两种技术都来自http://www.niksmit.com/wp/?p=17(如何使用ASP.Net MVC获得正常的404(页面未找到)错误页面),它指向了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" />,我会得到适当的响应代码,但我的Error控制器永远不会被调用。去掉redirectMode属性会让我获得MVC错误视图,但会出现一个介入的302和一个更改后的URL——而且始终是同一个控制器(Unknown= 500;如果我将它更改为NotFound,则所有都像404)。

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

4个回答

50

这里有一种技巧可以使用。定义一个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");
    }
}

1
看起来不错--我会在几个小时内试一下,然后告诉你它的工作情况。有一件事我没有看到:你在哪里处理控制器未找到的情况? - Val
2
@Val,当 ASP.NET MVC 找不到控制器时,会自动抛出一个代码为404的 HttpException,因此它将进入第一种情况。 - Darin Dimitrov
1
好的,我已经实现了基础功能,现在我要回去为各种HTTP错误实现自定义错误视图,并传递相关信息以在每个视图中显示。我还有更多的工作要做--构建一个错误视图模型,在ErrorController中填充它等等,但基本功能运行良好。没有302,正如你所评论的,缺失的操作和控制器被正确处理。谢谢--这是我见过的最简单、最干净、最有效的例子! - Val
它在本地运行得非常好,但是当我将它部署在IIS 7.5 ASP.NET 4.5和MVC4上时,会出现IIS 404页面。我该如何解决?谢谢。 - Barbaros Alp
在我的5.2.3.0版本中可用。谢谢Darin,您真的掌握了MVC-fu技巧。 - Martin Hansen Lennox
显示剩余3条评论

4

不错 - 你正在做Darin建议的事情,尽管他有更多的基础设施来处理多个异常类型。我现在正在努力将他的逻辑从Application_Error移动到ErrorController本身。 - Val
内容不再可用。 - user755404
@Darrren 链接已经恢复。 - Hector Correa

0

这并不回答你的问题,但需要注意的是,HTTP状态码500表示服务器出现了错误,因此你的例子:

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

不是抛出500的有效理由,这是数据验证问题,应该由MVC数据注释或jQuery验证框架处理等。只需在文本框旁边显示一个错误消息,例如“用户名过长”,效果会更好。


1
是的,我应该进行验证并抛出验证错误。但如果我没有进行验证,并且在插入或更新行失败时从DB得到约束错误,并且我没有适当地进行检查,我想抛出内部服务器错误。实际上,那就是我发现我们缺少一些验证的方式! - Val

0

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

解决方案是使用 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的回答"".

这种方法感觉更加简洁,而且和其他解决方案一样有效(无需重定向)。

注意:这种方法只适用于IIS 7及以上版本。(因为最近添加了httpErrors元素)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接