我用的是RC2

使用URL路由:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

上面似乎照顾到这样的请求(假设默认路由表由最初的MVC项目设置):"/blah/blah/blah/blah"

重写控制器本身的HandleUnknownAction():

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

但是,前面的策略不处理对坏/未知控制器的请求。例如,我没有“/IDoNotExist”,如果我请求这个,我从web服务器得到通用404页面,而不是我的404,如果我使用路由+覆盖。

最后,我的问题是:有没有办法在MVC框架中使用路由或其他东西来捕获这种类型的请求?

或者我应该默认使用Web。配置customErrors作为我的404处理程序,忘记这一切?我假设如果我使用customErrors,由于Web的原因,我将不得不在/Views之外存储通用404页面。配置直接访问限制。


当前回答

我对未处理区域、控制器和动作的简化解决方案:

Create a view 404.cshtml. Create a base class for your controllers: public class Controller : System.Web.Mvc.Controller { protected override void HandleUnknownAction(string actionName) { Http404().ExecuteResult(ControllerContext); } protected virtual ViewResult Http404() { Response.StatusCode = (int)HttpStatusCode.NotFound; return View("404"); } } Create a custom controller factory returning your base controller as a fallback: public class ControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType != null) return base.GetControllerInstance(requestContext, controllerType); return new Controller(); } } Add to Application_Start() the following line: ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));

其他回答

ASP。NET MVC不太支持自定义404页面。自定义控制器工厂,捕获所有路由,带有HandleUnknownAction的基控制器类-啊!

到目前为止,IIS自定义错误页面是更好的选择:

. config

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

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

示例项目

GitHub上的Test404 生活的网站

加上我的解决方案,这几乎是相同的Herman Kan的,有一个小皱纹,让它工作在我的项目。

创建一个自定义错误控制器:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

然后创建一个自定义控制器工厂:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

最后,向自定义错误控制器添加一个覆盖:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

就是这样。不需要网络。配置更改。

我浏览了这个帖子上的大部分解决方案。虽然这个问题可能很老了,但即使是现在,它仍然非常适用于新项目,所以我花了很多时间阅读这里和其他地方给出的答案。

正如@Marco指出的404错误可能发生的不同情况,我根据这个列表检查了我一起编译的解决方案。除了他的要求之外,我还增加了一项。

解决方案应该能够以最合适的方式处理MVC以及AJAX/WebAPI调用。(例如,如果在MVC中发生404,它应该显示Not Found页面,如果在WebAPI中发生404,它不应该劫持XML/JSON响应,以便消费Javascript可以很容易地解析它)。


这个解是2倍的:

第一部分来自https://stackoverflow.com/a/27354140/2310818的@Guillaume。他们的解决方案处理了由于无效路由、无效控制器和无效动作而导致的任何404。

这个想法是创建一个WebForm,然后让它调用MVC错误控制器的NotFound动作。它做所有这些没有任何重定向,所以你不会看到一个302在小提琴手。原始URL也被保留,这使得这个解决方案非常棒!


第二部分来自@Germán在https://stackoverflow.com/a/5536676/2310818。他们的解决方案照顾任何404返回你的行为在HttpNotFoundResult()或抛出新的HttpException()的形式!

其思想是让一个过滤器查看由MVC控制器抛出的响应和异常,并在错误控制器中调用适当的操作。再次,这个解决方案的工作没有任何重定向和原始url被保留!


正如您所看到的,这两个解决方案一起提供了一个非常健壮的错误处理机制,它们实现了@Marco列出的所有需求以及我的需求。如果你想看这个解决方案的工作示例或演示,请在评论中留下,我很乐意把它放在一起。

快速回答/ TL

对于那些懒惰的人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

然后从global.asax中删除这一行

GlobalFilters.Filters.Add(new HandleErrorAttribute());

这仅适用于IIS7+和IIS Express。

如果你用卡西尼号…嗯. .嗯. .呃. .尴尬……


冗长的解释

我知道这个问题已经得到了回答。但答案真的很简单(为大卫·福勒和达米安·爱德华兹真正回答了这个问题而欢呼)。

没有必要做任何定制。

ASP。NET MVC3,所有的片段都在那里。

更新你的网页。在两个点配置。

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

and

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

现在仔细记下我决定使用的路线。你可以用任何方法,但我的路线

/NotFound <-表示404未找到,错误页面。 /ServerError <-对于任何其他错误,包括在我的代码中发生的错误。这是一个500内部服务器错误

请参阅<system. >中的第一部分。Web >只有一个自定义条目?statusCode="404"条目?我只列出了一个状态码,因为所有其他错误,包括500服务器错误(即。当你的代码有bug并使用户的请求崩溃时发生的那些讨厌的错误)。所有其他错误都通过设置defaultRedirect="/ServerError"来处理。上面说,如果你没有404页面没有找到,那么请转到route /ServerError。

好的。那太离谱了。现在到global.asax中列出的路由

步骤2 -在Global.asax中创建路由

这是我的完整路线部分。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

它列出了两个忽略路由-> axd's和favicons(哦!奖励忽略路线,为你!) 然后(这里的顺序是IMPERATIVE),我有两个显式的错误处理路由..然后是其他路线。在本例中,是默认值。当然,我还有更多,但那是我网站的特色。只要确保错误路由位于列表的顶部即可。秩序是必须的。

最后,当我们在全局变量中。asax文件,我们不全局注册HandleError属性。不,不,不,先生。没有。年兽。负的。Noooooooooo……

从global.asax中删除这一行

GlobalFilters.Filters.Add(new HandleErrorAttribute());

步骤3 -用动作方法创建控制器

现在. .我们添加了一个带有两个动作方法的控制器…

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

好的,我们来看看这个。首先,这里没有[HandleError]属性。为什么?因为内置的ASP。NET框架已经在处理错误,并且我们已经指定了处理错误所需的所有屁事:)它就在这个方法中!

接下来,我有两个动作方法。没什么难的。如果你想显示任何异常信息,那么你可以使用Server.GetLastError()来获取该信息。

奖励WTF:是的,我做了第三个动作方法,以测试错误处理。

步骤4 -创建视图

最后,创建两个视图。把它们放到这个控制器的正常视图点。

奖金的评论

你不需要一个Application_Error(对象发送器,EventArgs e) 以上步骤对Elmah来说都是100%完美的。该死的埃尔玛!

朋友们,就这样吧。

现在,祝贺你读了这么多,并有一个独角兽作为奖品!

在我看来,由于依赖于服务器,标准的CustomErrors配置应该可以工作。似乎ResponseRewrite的内部实现与MVC不兼容。

我觉得这是一个明显的功能漏洞,所以我决定使用HTTP模块重新实现这个功能。下面的解决方案允许您通过重定向到任何有效的MVC路由来处理任何HTTP状态代码(包括404),就像您通常所做的那样。

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

这已经在以下平台上进行了测试;

集成管道模式下的MVC4 (IIS Express 8) 经典模式下的MVC4 (VS Development Server, Cassini) MVC4经典模式(IIS6)

好处

通用的解决方案,可以放入任何MVC项目 启用对传统自定义错误配置的支持 工作在集成管道和经典模式


解决方案

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

使用

将此作为web.config中的最后一个HTTP模块

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

注意的人会注意到,在集成管道模式下,由于服务器的方式,它总是以HTTP 200响应。TransferRequest作品。为了返回正确的错误代码,我使用以下错误控制器。

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}