如何在ASP.NET MVC中正确处理404?

时间:2009-03-06 18:21:39

标签: asp.net-mvc http-status-code-404

我正在使用RC2

使用网址路由:

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);
}  

但是之前的策略不处理对Bad / Unknown控制器的请求。例如,我没有“/ IDoNotExist”,如果我请求这个,我从Web服务器获取通用404页面而不是我的404,如果我使用路由+覆盖。

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

或者我应该默认使用Web.Config customErrors作为我的404处理程序并忘记所有这些?我假设如果我使用customErrors,由于Web.Config对直接访问的限制,我必须在/ Views之外存储通用404页面。

19 个答案:

答案 0 :(得分:262)

代码取自http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx,也适用于ASP.net MVC 1.0

以下是我处理http例外的方法:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }           

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}

答案 1 :(得分:252)

404

的要求

以下是我对404解决方案的要求,下面我将展示如何实现它:

  • 我想处理带有错误操作的匹配路线
  • 我想处理带有坏控制器的匹配路由
  • 我想处理不匹配的路由(我的应用无法理解的任意网址) - 我不希望这些冒泡到Global.asax或IIS,因为那时i can't redirect back into my MVC app properly
  • 我希望以与上面相同的方式处理自定义404s - 就像为不存在的对象(可能已删除)提交ID时
  • 我希望我的所有404都返回一个MVC视图(不是静态页面),如果有必要,我可以在以后提取更多数据(good 404 designs他们必须< / em>返回HTTP 404状态代码

解决方案

我认为您应该在Global.asax中保存Application_Error以获取更高级的内容,例如未处理的异常和日志记录(如Shay Jacoby's answer节目),但不能处理404。这就是为什么我的建议将404内容保留在Global.asax文件之外。

步骤1:404错误逻辑的共同位置

这是可维护性的好主意。使用ErrorController,以便您well designed 404 page的未来改进可以轻松适应。此外,确保您的回复包含404代码

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

步骤2:使用基本Controller类,以便您可以轻松调用自定义404操作并连接HandleUnknownAction

ASP.NET MVC中的404s需要在很多地方被捕获。第一个是HandleUnknownAction

InvokeHttp404方法为重新路由到ErrorController和新的Http404操作创建了一个公共位置。想想DRY

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

步骤3:在Controller Factory中使用依赖注入并连接404 HttpExceptions

像这样(它不一定是StructureMap):

MVC1.0示例:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

MVC2.0示例:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

我认为更好地捕捉更接近原点的错误。这就是为什么我更喜欢上面的Application_Error处理程序。

这是第二个捕获404的地方。

步骤4:向Global.asax添加NotFound路由,以获取无法解析到您的应用中的网址

此路线应指向我们的Http404行动。请注意url param将是一个相对URL,因为路由引擎正在剥离域部分?这就是我们在步骤1中拥有所有条件URL逻辑的原因。

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

这是在你自己没有调用的MVC应用程序中捕获404s的第三个也是最后一个。如果你没有在这里捕获不匹配的路由,那么MVC会将问题传递给ASP.NET(Global.asax),在这种情况下你真的不想要它。

步骤5:最后,当您的应用无法找到某些内容时调用404s

就像将错误的ID提交给我的贷款控制器(来自MyController)时一样:

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

如果所有这些都可以用更少的代码连接到更少的地方,那将是很好的,但我认为这个解决方案更易于维护,更可测试且相当实用。

感谢您的反馈。我希望得到更多。

注意:这已经从我原来的答案中进行了大量编辑,但目的/要求是相同的 - 这就是我没有添加新答案的原因

答案 2 :(得分:232)

ASP.NET MVC不能很好地支持自定义404页面。自定义控制器工厂,catch-all路由,基础控制器类HandleUnknownAction - argh!

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

的web.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();
    }
}

示例项目

答案 3 :(得分:153)

快速回答/ TL; DR

enter image description here

对于那里的懒人:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

然后从global.asax

中删除此行
GlobalFilters.Filters.Add(new HandleErrorAttribute());

这仅适用于IIS7 +和IIS Express。

如果你正在使用卡西尼......嗯..嗯..呃......尴尬...... awkward


很长,解释了答案

我知道这已经回答了。但答案真的很简单(欢呼David FowlerDamian Edwards真正回答这个问题。)

无需进行任何自定义

对于ASP.NET MVC3,所有的零碎都在那里。

步骤1 - &gt;在两个位置更新您的web.config。

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

<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&lt; - 找不到404,错误页面。
  • /ServerError&lt; - 对于任何其他错误,请包含我的代码中发生的错误。这是500内部服务器错误

查看<system.web>中的第一部分如何只有一个自定义条目? statusCode="404"条目?我只列出了一个状态代码,因为所有其他错误,包括500 Server Error(即,当您的代码有错误并且崩溃用户的请求时发生的那些讨厌的错误)..所有其他错误都由设置defaultRedirect="/ServerError" ..如果您没有找到404页面,那么请转到路线/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}
        );
}

列出两条忽略路线 - &gt; axd'sfavicons(ooo!奖励忽略路线,为你!) 然后(并且订单是IMPERATIVE HERE),我有两个明确的错误处理路线..后跟任何其他路线。在这种情况下,默认值为1。当然,我有更多,但这对我的网站来说很特别。 只需确保错误路由位于列表顶部。订单势在必行

最后,当我们在global.asax文件中时,我们不会全局注册HandleError属性。不,不,不,先生。 Nadda。不。粘。负。 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");
    }
}

好的,我们来看看吧。首先,这里有 NO [HandleError]属性。为什么?因为内置的ASP.NET框架已经处理错误了,我们已经指定了处理错误所需要做的所有事情:)这就是这个方法!

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

Bonus WTF:是的,我采用了第三种操作方法来测试错误处理。

第4步 - 创建视图

最后,创建两个视图。将em放在普通视点中,对于此控制器。

enter image description here

奖金评论

  • 您不需要Application_Error(object sender, EventArgs e)
  • 上述步骤与Elmah完全一致。 Elmah fraking wroxs!

而且,我的朋友,应该是它。

现在,恭喜阅读这篇文章并获得独角兽作为奖品!

enter image description here

答案 4 :(得分:84)

我已经研究了如何在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));
        }
    }
}

ErrorsController:

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;
    }
}

(可选)

<强>解释

AFAIK,有6种不同的情况,ASP.NET MVC3应用程序可以生成404。

(由ASP.NET Framework自动生成:)

(1)网址在路由表中找不到匹配项。

(由ASP.NET MVC Framework自动生成:)

(2) URL在路由表中找到匹配项,但指定了一个不存在的控制器。

(3)网址在路由表中找到匹配项,但指定了不存在的操作。

(手动生成:)

(4)一个动作使用方法HttpNotFound()返回一个HttpNotFoundResult。

(5)操作会抛出状态代码为404的HttpException。

(6)操作会手动将Response.StatusCode属性修改为404。

通常,您希望实现3个目标:

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

(2)在客户端响应中维护404状态代码(对SEO特别重要)。

(3)直接发送回复,不涉及302重定向。

有多种方法可以尝试实现此目的:

<强>(1)

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

此解决方案存在问题:

  1. 不符合案例(1),(4),(6)中的目标(1)。
  2. 不自动符合目标(2)。必须手动编程。
  3. 不符合目标(3)。
  4. <强>(2)

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

    此解决方案存在问题:

    1. 仅适用于IIS 7 +。
    2. 不符合案例(2),(3),(5)中的目标(1)。
    3. 不自动符合目标(2)。必须手动编程。
    4. <强>(3)

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

      此解决方案存在问题:

      1. 仅适用于IIS 7 +。
      2. 不自动符合目标(2)。必须手动编程。
      3. 它模糊了应用程序级别的http异常。例如。不能使用customErrors部分,System.Web.Mvc.HandleErrorAttribute等。它不仅可以显示通用错误页面。
      4. <强>(4)

        <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>
        

        此解决方案存在问题:

        1. 仅适用于IIS 7 +。
        2. 不自动符合目标(2)。必须手动编程。
        3. 不符合案例(2),(3),(5)中的目标(3)。
        4. 在尝试创建自己的库之前,对此感到困扰的人(请参阅http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html)。但是之前的解决方案似乎涵盖了所有情况,而没有使用外部库的复杂性。

答案 5 :(得分:13)

我非常喜欢cottsaks解决方案,并认为它非常清楚地解释了。我唯一的补充是改变步骤2如下

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

基本上,这会阻止包含无效操作和控制器的URL触发异常例程两次。例如,对于诸如asdfsdf / dfgdfgd

之类的网址

答案 6 :(得分:6)

我能让@ 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。

答案 7 :(得分:4)

这是使用MVC工具的另一种方法,您可以处理对不良控制器名称,错误路由名称以及您认为适合Action方法的任何其他条件的请求。就个人而言,我更喜欢避免尽可能多的web.config设置,因为他们使用Razor视图执行302/200重定向并且不支持ResponseRewrite(Server.Transfer)。出于搜索引擎优化的原因,我宁愿返回带有自定义错误页面的404.

其中一些是对上述cottsak技术的新观点。

此解决方案还使用了最少的web.config设置,而不是MVC 3错误过滤器。

用法

从动作或自定义ActionFilterAttribute抛出HttpException。

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

第1步

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

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

第2步

添加类似于MVC框架的HandleErrorAttribute的自定义HandleHttpErrorAttribute,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步

将过滤器添加到GlobalFilters.Filters中的GlobalFilterCollection(Global.asax)。此示例将所有InternalServerError(500)错误路由到错误共享视图(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步

创建一个基本控制器类,并在控制器中继承它。此步骤允许我们处理未知的操作名称并将HTTP 404错误引发到HandleHttpErrorAttribute。

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覆盖,并在Application_Start中的Global.asax文件中覆盖它。此步骤允许我们在指定了无效的控制器名称时引发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步

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

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

摘要

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

我将添加一个无效控制器名称,操作名称和从Home / TriggerNotFound操作引发的自定义404的屏幕截图,如果我有足够的投票来发布一个=)。当我使用此解决方案访问以下URL时,Fiddler会返回404消息:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound
cottsak的帖子上面和这些文章是很好的参考。

答案 8 :(得分:4)

我的缩短解决方案适用于未处理的区域,控制器和操作:

  1. 创建视图404.cshtml。

  2. 为您的控制器创建基类:

    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");
        }
    }
    
  3. 创建一个自定义控制器工厂,将基本控制器作为后备返回:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
  4. Application_Start()添加到以下行:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    

答案 9 :(得分:3)

在MVC4中,WebAPI 404可以通过以下方式处理,

COURSES APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

家庭控制器

public ActionResult Course(int id)
{
    return View(id);
}

查看

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

GLOBAL

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

结果

enter image description here

答案 10 :(得分:2)

在nuget上尝试NotFoundMVC。它有效,没有设置。

答案 11 :(得分:2)

处理ASP.NET MVC中的错误只是一个痛苦的屁股。我在这个页面以及其他问题和网站上尝试了很多建议,没有什么效果好。一个建议是处理 system.webserver web.config 的错误,但只有返回空白页

我提出这个解决方案时的目标是:

  • NOT REDIRECT
  • 返回正确的状态代码而不是200 /好像默认的错误处理

这是我的解决方案。

1 。将以下内容添加到 system.web 部分

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

以上处理 routes.config 未处理的任何URL和未处理的异常,尤其是在视图上遇到的异常。注意我使用 aspx 而不是 html 。这样我就可以在后面的代码上添加响应代码

<强> 2 即可。在项目的根目录中创建一个名为错误(或任何您喜欢的)的文件夹,并添加两个webforms。以下是我的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级错误。

答案 12 :(得分:2)

在我看来,标准CustomErrors配置应该正常工作但是,由于依赖于Server.Transfer,似乎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开发服务器,卡西尼)
  • 经典模式下的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>

对于那些关注你的人,你会发现在集成管道模式下,由于Server.TransferRequest的工作方式,它总是会响应HTTP 200。要返回正确的错误代码,请使用以下错误控制器。

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());
    }

}

答案 13 :(得分:2)

我的解决方案,万一有人发现它有用。

在Web.config中:

<system.web>
    <customErrors mode="On" defaultRedirect="Error" >
      <error statusCode="404" redirect="~/Error/PageNotFound"/>
    </customErrors>
    ...
</system.web>

Controllers/ErrorController.cs

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

PageNotFound.cshtml文件夹中添加Shared,就是这样。

答案 14 :(得分:1)

因为我的评论太长了而发布了答案......

这是对独角兽帖子/答案的评论和问题:

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

我更喜欢这个答案而不是其他答案,因为它的简单性以及显然有些人在微软咨询过。然而,我有三个问题,如果可以回答,那么我将把这个答案称为ASP.NET MVC(x)应用程序的互联网上所有404/500错误答案的圣杯。

@ Pure.Krome

  1. 您能否根据GWB指出的评论中的SEO内容更新您的答案(答案中从未提及此问题) - <customErrors mode="On" redirectMode="ResponseRewrite"><httpErrors errorMode="Custom" existingResponse="Replace">

  2. 你能问问你的ASP.NET团队的朋友是否可以这样做 - 很高兴得到一些确认 - 也许这是一个很大的禁忌来改变redirectMode和{{ 1}}以这种方式能够很好地与SEO玩耍?!

  3. 在与朋友交谈后,您是否可以添加一些有关所有内容的澄清(existingResponsecustomErrors redirectMode="ResponseRewrite"customErrors redirectMode="ResponseRedirect",完全按照某人的建议删除httpErrors errorMode="Custom" existingResponse="Replace"在微软?

  4. 正如我所说;如果我们能够让你的答案更加完整,这将是超级的,因为这似乎是一个相当受欢迎的问题,有54 000多个观点。

    更新:Unicorn答案执行302 Found和200 OK并且无法更改为仅使用路线返回404。它必须是一个不是很MVC的物理文件:ish。所以转向另一个解决方案。太糟糕了,因为这似乎是最终的MVC:到目前为止,我已经回答了。

答案 15 :(得分:1)

添加我的解决方案,这个解决方案与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));
}

就是这样。无需更改Web.config。

答案 16 :(得分:1)

1)制作抽象的Controller类。

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2)在所有控制器中使用此抽象类继承

public class HomeController : MyController
{}  

3)并添加名为&#34; NotFound&#34;的视图在你的View-Shared文件夹中。

答案 17 :(得分:0)

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

由于@Marco指出404可能发生的不同情况,我检查了我在该列表中编译的解决方案。除了他的要求清单外,我还增加了一个。

  • 解决方案应该能够以最恰当的方式处理MVC以及AJAX / WebAPI调用。 (即如果404发生在MVC中,它应该显示Not Found页面,如果404发生在WebAPI中,它不应该劫持XML / JSON响应,以便消费Javascript可以轻松解析它)。

此解决方案有两倍:

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

这个想法是创建一个WebForm,然后让它调用MVC错误控制器的NotFound操作。它完成所有这一切,没有任何重定向,所以你不会在Fiddler中看到一个302。原始网址也会保留,这使得此解决方案非常棒!

第二部分来自{Germánhttps://stackoverflow.com/a/5536676/2310818。他们的解决方案负责处理您的操作以HttpNotFoundResult()形式返回的任何404,或抛出新的HttpException()!

我们的想法是让过滤器查看响应以及MVC控制器抛出的异常,并在错误控制器中调用相应的操作。同样,此解决方案可以在没有任何重定向的情况下工作,并保留原始URL!

正如您所看到的,这两种解决方案共同提供了非常强大的错误处理机制,并且它们满足了@Marco列出的所有要求以及我的要求。如果您想查看此解决方案的工作示例或演示,请留在评论中,我很乐意将其整合在一起。

答案 18 :(得分:0)

我已阅读所有文章,但对我没有任何作用: 我的要求用户在网址自定义404页面中输入任何内容都应该显示。我认为这是非常直接的。但是你应该理解404的正确处理:

TIMESTAMP

我发现这篇文章很有帮助。应立即阅读。Custome error page-Ben Foster