2011-02-06 47 views
37

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

在这一点上,我会很乐意妥善处理404和500错误 - 403(需要授权)也会很好,其次是各种其他特定的响应。现在,我要么得到所有404,500S所有,404之前的所有302S,或全部302S前500

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

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

  • 如果发生内部服务器错误,请抛出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:

  • 发表错误值: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代表一个URL(控制器/操作),其中使用 Web服务无法找到。 我也得到了请求的URL为(N不必要的)查询字符串PARAM:/Error/NotFound?aspxerrorpath=/Home/non-existent-action

    这两种技术都来自http://www.niksmit.com/wp/?p=17(如何获得正常的404(使用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" />,我得到相应的响应代码,但是我的Error控制器永远不会被调用。取出redirectMode属性得到我的MVC错误的观点,但有一个中间的302和改变URL - 总是相同的控制器(Unknown = 500;如果我将其更改为NotFound一切看起来像404)。

    下面是一些我读过的和其他的东西试图执行:

    ..以及一堆StackOverflow帖子。

    对我来说,这种错误处理对于Web应用程序来说是非常基本的,而MVC框架应该有默认的功能,并且让其他人可以扩展它。也许他们会在未来的版本中做到这一点。同时,有人可以给我详细的介绍如何实现正确的HTTP响应吗?

    回答

    46

    以下是您可以使用的一种技术。定义一个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 2011-02-06 17:11:05

    +2

    @val,当控制器没有发现与代码404的HttpException自动由ASP抛出。NET MVC所以它会进入第一种情况。 – 2011-02-06 22:21:34

    0

    这不回答你的问题,但要注意的是HTTP状态500是非常重要的指示出现了错误的服务器上,所以你的例子:

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

    是无效的理由抛出500 ,它是一个数据验证问题,应该由MVC数据注释或者jQuery验证框架等来处理。只是在TextBox旁边显示一条错误消息,说“User Name too long”好得多。

    +1

    是的,我应该* *验证和抛出一个验证错误。但是,如果我无法验证,然后从数据库得到一个约束的错误时,它未能插入或更新行,我不为正确检查,我想抛出一个内部服务器错误。事实上,这就是我发现我们错过了一些验证! – Val 2011-02-06 17:07:59