ASP.NET MVC 404错误处理

139

1
这是一个关于此主题的好文章 @ 如何有效处理ASP.NET MVC 4中的404 Not Found错误 - Yasser Shaikh
6个回答

381

我在MVC(特别是MVC3)中调查了很多关于如何正确处理404的内容,我认为以下方法是最好的:

在global.asax中:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

错误控制器:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

编辑:

如果您正在使用IoC(例如AutoFac),则应使用以下方式创建您的控制器:

var rc = new RequestContext(new HttpContextWrapper(Context), rd);
var c = ControllerBuilder.Current.GetControllerFactory().CreateController(rc, "Errors");
c.Execute(rc);

与其

IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));

(可选)

说明:

我能想到6种情况,可以导致ASP.NET MVC3应用程序生成404错误。

由ASP.NET自动生成:

  • 情况1:URL与路由表中的路由不匹配。

由ASP.NET MVC自动生成:

  • 情况2:URL匹配一个路由,但指定的控制器不存在。

  • 情况3:URL匹配一个路由,但指定的操作不存在。

手动生成:

  • 情况4:操作使用方法HttpNotFound()返回一个HttpNotFoundResult。

  • 情况5:操作抛出带有状态码404的HttpException。

  • 情况6:操作手动修改Response.StatusCode属性为404。

目标

  • (A) 向用户显示自定义的404错误页面。

  • (B) 在客户端响应上保持404状态代码(对于SEO非常重要)。

  • (C) 直接发送响应,不涉及302重定向。

解决尝试:自定义错误

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

该解决方案存在问题:

  • 在情景(1)、(4)、(6)中不符合目标(A)。
  • 无法自动符合目标(B),必须手动编程。
  • 不符合目标(C)。

尝试的解决方案:HTTP错误

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

这个解决方案存在问题:

  • 只适用于 IIS 7或更高版本。
  • 在场景 (2), (3), (5) 中不符合目标 (A)。
  • 不能自动符合目标 (B),必须手动编程。

尝试的解决方案: 使用HTTP错误替换

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

该解决方案存在以下问题:

  • 仅适用于IIS 7+。
  • 不能自动遵守(B)目标,必须手动编程。
  • 它掩盖了应用程序级别的http异常。例如,无法使用customErrors部分、System.Web.Mvc.HandleErrorAttribute等,它只能显示一般性错误页面。

尝试的解决方案:customErrors和HTTP错误

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

并且

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

这个解决方案存在以下问题:

  • 只适用于IIS 7+。
  • 无法自动遵守目标(B)。必须手动编程。
  • 在情景(2)、(3)、(5)中无法遵守目标(C)。

之前的人甚至尝试创建自己的库来处理此问题(请参见http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html),但是先前的解决方案似乎覆盖了所有情况,而不需要使用外部库的复杂性。


9
我喜欢您的分析,但不喜欢您的解决方案 :) 等到请求结束的问题在于一些请求上下文已经被丢弃,例如会话状态。 - Dale K
6
那篇文章没有提到Response.Clear,只提到了Response.Redirect。 - Tristan Warner-Smith
10
@RickAndMSFT: Response.Clear存在什么问题?在博客文章中发表如此神秘的评论并得出“不要使用这种方法”的结论,然后在被要求澄清时保持沉默,这样做并没有什么帮助。 - Slauma
3
嗨 @PussInBoots。在MVC中,如果您的网站变得太大(有很多控制器,模型和视图),您可以在区域中对它们进行逻辑分组。有关更多信息,请参见http://msdn.microsoft.com/en-us/library/ee671793(v=vs.100).aspx。 - Marco
4
@Marco,你总结得真的很棒,可能帮助了很多人!但我认为使用框架(ASP.NET/ASP.NET MVC)仅为处理404/500错误而造成的混乱和复杂性是无法容忍的!与其他网络框架(如Yii、Rails、Django等)进行比较,这部分在ASP.NET中确实非常糟糕! - Wayne Ye
显示剩余21条评论

146

另一种解决方案。

添加ErrorControllers或静态页面以显示404错误信息。

修改你的web.config文件(如果使用控制器)。

<system.web>
    <customErrors mode="On" >
       <error statusCode="404" redirect="~/Errors/Error404" />
    </customErrors>
</system.web>

或者在静态页面的情况下

<system.web>
    <customErrors mode="On" >
        <error statusCode="404" redirect="~/Static404.html" />
    </customErrors>
</system.web>

这将处理未匹配到的路由和未匹配到的操作。


2
不错! :) ErrorsController 可以继承与所有其他控制器相同的基类,从而可以访问某些功能。此外,Error404视图可以包装在主控制器中,为用户提供整个站点的整体外观和感觉,而无需进行任何额外的工作。 - Dimskiy
7
在开发过程中,使用 <customErrors mode="RemoteOnly"> 可以查看实际的错误页面。 - Jakub Konecki
6
如果从控制器操作返回return HttpNotFound();作为ActionResult使用,则无法正常工作。 - Slauma
2
这个解决方案不起作用。对于“On”和“RemoteOnly”模式,我收到了默认的404错误。 - Gleno
4
这将导致302重定向,因此您无法保留原始的HTTP代码。ASP.NET确实使得控制HTTP堆栈变得比必要的更加困难。 - Justin Helgerson
显示剩余6条评论

5

Marco的回答是最好的解决方案。我需要控制我的错误处理,而且我是真的要全面控制它。当然,我稍微扩展了这个解决方案并创建了一个完整的错误管理系统来管理一切。我还在其他博客上阅读了这个解决方案,大多数高级开发人员似乎都认为它非常可行。

以下是我正在使用的最终代码:

protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            var exception = Server.GetLastError();
            var httpException = exception as HttpException;
            Response.Clear();
            Server.ClearError();
            var routeData = new RouteData();
            routeData.Values["controller"] = "ErrorManager";
            routeData.Values["action"] = "Fire404Error";
            routeData.Values["exception"] = exception;
            Response.StatusCode = 500;

            if (httpException != null)
            {
                Response.StatusCode = httpException.GetHttpCode();
                switch (Response.StatusCode)
                {
                    case 404:
                        routeData.Values["action"] = "Fire404Error";
                        break;
                }
            }
            // Avoid IIS7 getting in the middle
            Response.TrySkipIisCustomErrors = true;
            IController errormanagerController = new ErrorManagerController();
            HttpContextWrapper wrapper = new HttpContextWrapper(Context);
            var rc = new RequestContext(wrapper, routeData);
            errormanagerController.Execute(rc);
        }
    }

在我的ErrorManagerController内部:
        public void Fire404Error(HttpException exception)
    {
        //you can place any other error handling code here
        throw new PageNotFoundException("page or resource");
    }

现在,在我的Action中,我正在抛出一个我创建的自定义异常。我的控制器继承自我创建的自定义控制器基类。自定义基础控制器被创建用于覆盖错误处理。以下是我的自定义基础控制器类:

public class MyBasePageController : Controller
{
    protected override void OnException(ExceptionContext filterContext)
    {
        filterContext.GetType();
        filterContext.ExceptionHandled = true;
        this.View("ErrorManager", filterContext).ExecuteResult(this.ControllerContext);
        base.OnException(filterContext);
    }
}

以上代码中的"ErrorManager"只是一个视图,它使用基于ExceptionContext的模型。
我的解决方案完美地运作,我能够处理网站上的任何错误并根据任何异常类型显示不同的消息。

3
我不同意你的看法,认为这并不是最好的解决方案。像这样的常见任务不应该如此复杂才能设置好。Marcos的回答很棒,但对于简单的事情来说,你真的不需要那么多的代码。 - PussInBoots

4

1
在IIS中,您可以根据错误代码指定重定向到“特定”页面。例如,您可以配置404 -> 您自定义的404错误页面。

1
我可以推荐的是看一下FilterAttribute。例如,MVC已经有HandleErrorAttribute。可以自定义它来处理仅404错误。如果您感兴趣,请回复我,我将提供示例。
顺便说一句,在以前的问题中接受的解决方案(带最后路由)在许多情况下都不起作用。第二个使用HandleUnknownAction的解决方案将起作用,但需要在每个控制器中进行更改或只需拥有单个基础控制器即可。
我的选择是使用HandleUnknownAction的解决方案。

看起来问题在于标准默认路由“{controller}/{action}/{id}”捕获了所有内容,因此它无法到达最后一个路由。我认为如果找不到控制器,则会评估下一个路由。 - Clearly
HandleUnknownAction仅适用于找不到的操作。如果路由匹配但无法找到相应的控制器,该怎么办?最好的处理方式是什么? - Clearly
是的,这是正确的,只有在找不到操作时才能使用。您可以尝试将两种解决方案结合起来。HandleUnknownAction 用于处理丢失的操作,而路由用于处理丢失的控制器。其他可能的解决方案是自定义 RouteHandler。 - Mike Chaliy
哎呀,RouteHandler超出了范围。很抱歉,您将无法使用自定义的RouteHandler来完成这个任务。 - Mike Chaliy

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