我用的是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页面。配置直接访问限制。


当前回答

在我看来,由于依赖于服务器,标准的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());
    }

}

其他回答

下面是另一个使用MVC工具的方法,你可以处理对坏控制器名、坏路由名的请求,以及任何其他你认为在Action方法中合适的条件。就我个人而言,我倾向于尽量避免使用网络。配置设置,因为他们做302 / 200重定向,不支持ResponseRewrite (Server.Transfer)使用Razor视图。出于搜索引擎优化的原因,我更喜欢返回带有自定义错误页面的404。

其中一些是对cottsak上面的技术的新采取。

这个解决方案也使用最少的网络。配置设置偏向MVC 3错误过滤器。

使用

只需从动作或自定义ActionFilterAttribute抛出一个HttpException。

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

步骤1

将以下设置添加到您的web.config。这是使用MVC的HandleErrorAttribute所必需的。

<customErrors mode="On" redirectMode="ResponseRedirect" />

步骤2

添加一个自定义的HandleHttpErrorAttribute,类似于MVC框架的HandleErrorAttribute,除了HTTP错误:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class

步骤3

在Global.asax中的GlobalFilterCollection (GlobalFilters.Filters)中添加过滤器。这个例子将所有InternalServerError(500)错误路由到Error共享视图(Views/ shared /Error.vbhtml)。NotFound(404)错误将被发送到ErrorHttp404。共享视图中的VBHTML。我在这里添加了一个401错误,以向您展示如何将其扩展为其他HTTP错误代码。注意,这些必须是共享视图,并且它们都使用System.Web.Mvc.HandleErrorInfo对象作为模型。

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})

步骤4

创建一个基控制器类,并在控制器中继承它。这一步允许我们处理未知的动作名称,并向我们的HandleHttpErrorAttribute抛出HTTP 404错误。

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class

步骤5

创建一个ControllerFactory重写,并在全局中重写它。asax文件在Application_Start。这个步骤允许我们在指定了无效的控制器名时引发HTTP 404异常。

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)

步骤6

在你的路由表中包含一个特殊的路由。BaseController Unknown动作的路由。这将帮助我们在用户访问未知控制器或未知操作的情况下引发404。

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)

总结

这个例子演示了如何使用MVC框架向浏览器返回404 Http错误代码,而不需要使用过滤器属性和共享错误视图进行重定向。它还演示了当指定无效的控制器名和动作名时显示相同的自定义错误页面。

我将添加一个无效的控制器名,动作名的截图,以及一个自定义404从Home/TriggerNotFound动作引发,如果我得到足够的投票,张贴一个=)。提琴手返回404消息时,我访问以下url使用这个解决方案:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

Cottsak上面的文章和这些文章都是很好的参考。

使用CustomErrors redirectMode=ResponseRewrite的问题 Elmah + MVC HandleErrorAttribute

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

正如@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列出的所有需求以及我的需求。如果你想看这个解决方案的工作示例或演示,请在评论中留下,我很乐意把它放在一起。

我能让@cottsak的方法为无效控制器工作的唯一方法是修改CustomControllerFactory中现有的路由请求,如下所示:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

我应该提到我使用的是MVC 2.0。

处理ASP中的错误。NET MVC就是个麻烦。我在这个页面和其他问题和网站上尝试了很多建议,都没有什么效果。一个建议是处理网络上的错误。系统内部配置。web服务器,但只返回空白页。

当我想出这个解决方案时,我的目标是;

不重定向 返回正确的STATUS CODES,而不是像默认的错误处理那样返回200/Ok

这是我的解决方案。

1.将以下内容添加到系统中。网络部分

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

上面的处理没有路由处理的任何url。配置和未处理的异常,特别是在视图上遇到的异常。注意我用的是aspx而不是html。这样我就可以在后面的代码上添加响应代码。

2. 在项目的根目录下创建一个名为Error的文件夹(或者其他你喜欢的文件夹),并添加这两个webform。下面是我的404页面;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

在后面的代码上,我设置了响应代码

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

对500页做同样的处理吗

3.来处理控制器内的错误。有很多方法可以做到。这对我来说很管用。我所有的控制器都继承自一个基本控制器。在基本控制器中,我有以下方法

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4.添加CustomError。cshtml到共享视图文件夹。下面是我的;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

在你的应用控制器中,你可以这样做;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

现在是警告。 它不会处理静态文件错误。因此,如果您有一个路由,例如example.com/widgets,用户将其更改为example.com/widgets.html,他们将获得IIS默认错误页面,因此您必须以其他方式处理IIS级别的错误。

因为我的评论太长了,所以发表了一个答案…

这既是对独角兽帖子/答案的评论,也是对它的问题:

https://stackoverflow.com/a/7499406/687549

比起其他答案,我更喜欢这个答案,因为它很简单,而且事实是,显然微软的一些人被咨询了。然而,我有三个问题,如果他们可以回答,那么我将把这个答案称为互联网上所有404/500个错误答案的圣杯。NET MVC (x)应用程序。

@Pure。Krome

Can you update your answer with the SEO stuff from the comments pointed out by GWB (there was never any mentioning of this in your answer) - <customErrors mode="On" redirectMode="ResponseRewrite"> and <httpErrors errorMode="Custom" existingResponse="Replace">? Can you ask your ASP.NET team friends if it is okay to do it like that - would be nice to have some confirmation - maybe it's a big no-no to change redirectMode and existingResponse in this way to be able to play nicely with SEO?! Can you add some clarification surrounding all that stuff (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrors COMPLETELY as someone suggested) after talking to your friends at Microsoft?

就像我说的;如果我们能让你的回答更完整就太好了,因为这似乎是一个相当受欢迎的问题,有54000 +的浏览量。

更新:Unicorn的答案是302 Found和200 OK,不能使用路由更改为只返回404。它必须是一个物理文件,不是很MVC:ish。我们来看另一个解。太糟糕了,因为这似乎是到目前为止的终极MVC:ish答案。