我怎样才能正确处理ASP.NET MVC中的404?

我只是开始ASP.NET MVC所以忍受着我。 我search了这个网站和其他各种各样,并已经看到了这个几个实现。

编辑:我忘了提及我正在使用RC2

使用URL路由:

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

上面似乎照顾这样的请求(假设初始MVC项目设置的默认路由表):“/等等/等等/等等/等等”

在控制器中重写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如果我使用路由+重写。

所以最后,我的问题是: 是否有任何方法来捕获这种types的请求使用路由或在MVC框架本身的其他东西?

或者我应该只是默认使用Web.Config customErrors作为我的404处理程序,忘记所有这一切? 我假设,如果我去customErrors我将不得不存储通用404页面之外/视图由于Web.Config限制直接访问。 无论如何,任何最佳做法或指导表示赞赏。

代码取自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以及

以下是我处理httpexception的方法:

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

404的要求

以下是我对404解决scheme的要求,下面我将介绍如何实现它:

  • 我想处理匹配的行为不好的行为
  • 我想处理与坏的控制器匹配的路线
  • 我想处理不匹配的路线(我的应用程序无法理解的任意url) – 我不想让这些冒泡到Global.asax或IIS,因为那样我就无法正确地redirect到我的MVC应用程序
  • 我想要一种方式来处理与上述相同的方式,自定义404s – 就像提交一个ID不存在的对象(可能被删除)
  • 我想我所有的404s返回一个MVC视图(而不是一个静态页面),如果有必要,我可以稍后抽出更多的数据( 良好的404devise ) 他们必须返回HTTP 404状态码

我认为你应该将Application_Error保存在Global.asax中,以获取更高的内容,例如未处理的exception和日志logging(如Shay Jacoby的答案所示),但不处理404。 这就是为什么我的build议保持在Global.asax文件的404东西。

步骤1:有一个404错误逻辑的通用位置

这是一个可维护性的好主意。 使用ErrorController,以便将来对精心devise的404页面的改进可以轻松适应。 另外, 确保你的回应有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步:使用基本的控制器类,以便您可以轻松地调用您的自定义404操作,并连接HandleUnknownAction

ASP.NET MVC中的404s需要在许多地方被捕获。 首先是HandleUnknownAction

InvokeHttp404方法为重新路由到ErrorController和我们新的Http404操作创build了一个通用的地方。 认为干 !

 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中使用dependency injection,并连接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处理程序。

这是抓住404s的第二位。

第4步:添加一个NotFound路由到Global.asax的urls,无法parsing到您的应用程序

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

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

这是第三个也是最后一个在MVC应用程序中捕捉404的地方,您不会自行调用。 如果你在这里没有捕捉到无与伦比的路线,那么MVC将会把问题传递给ASP.NET(Global.asax),在这种情况下你并不需要这样做。

第5步:最后,当你的应用程序找不到东西时调用404s

就像当一个坏的ID被提交给我的Loans控制器(从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); } 

如果所有这些都可以用更less的代码连接到更less的地方,那将是非常好的,但我认为这个解决scheme更易于维护,更易于testing和更实用。

感谢迄今为止的反馈。 我很想得到更多。

注意:这已经从我原来的答案显着编辑,但目的/要求是相同的 – 这就是为什么我没有添加新的答案

ASP.NET MVC不支持自定义404页面。 自定义控制器工厂,捕获所有path,基本控制器类与HandleUnknownAction – argh!

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

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

示例项目

  • GitHub上的Test404
  • 现场网站

快速回答/ TL; DR

在这里输入图像描述

对于那些懒惰的人:

 Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0 

然后从global.asax删除这一行

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

而这只适用于IIS7 +和IIS Express。

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


长,解释的答案

我知道这已经回答了。 但是答案是非常简单的(向David Fowler和Damian Edwards欢呼,真的回答了这个问题)。

没有必要做任何习俗

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

第1步 – >更新你的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 < – 404找不到,错误页面。
  • /ServerError < – 对于任何其他错误,包括发生在我的代码中的错误。 这是一个500内部服务器错误

看看<system.web>的第一部分只有一个自定义条目吗? statusCode="404"条目? 我只列出了一个状态代码,因为所有其他的错误,包括500 Server Error (即,当你的代码有一个错误,并崩溃用户的请求时发生的那些麻烦的错误)..所有其他的错误是由设置defaultRedirect="/ServerError" ..这说,如果你不是一个404页面没有find,那么请gotopath/ServerError

好。 现在,我的路线列在global.asax

第2步 – 在Global.asax中创build路由

这是我的完整路线部分..

 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'sfavicons (ooo!bonus忽略路由,为你!)然后(和命令是IMPERATIVE HERE),我有两个明确的error handling路由..其次是任何其他路由。 在这种情况下,默认的一个。 当然,我有更多,但这是我的网站特别。 只要确保错误路线在列表的顶部。 订单势在必行

最后,当我们在我们的global.asax文件中时,我们不会全局注册HandleError属性。 不,不,先生。 Nadda。 不。 粘。 负。 Noooooooooo …

global.asax删除这一行

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

第3步 – 使用操作方法创build控制器

现在..我们添加一个控制器与两个行动方法…

 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框架已经处理错误,我们已经指定了所有我们需要做的事情来处理错误:)这是在这种方法!

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

Bonus WTF:是的,我做了第三个动作方法,来testingerror handling。

第4步 – 创build视图

最后,创build两个视图。 把这个控制器放在正常的视点中。

在这里输入图像描述

奖金评论

  • 您不需要Application_Error(object sender, EventArgs e)
  • 以上所有步骤都与Elmah完美配合。 Elmah擦肩而过!

而且,我的朋友们应该是这样的。

现在,恭喜您阅读这篇文章,并获得独angular兽奖!

在这里输入图像描述

我已经调查了如何在MVC (特别是MVC3)中正确pipe理404的很多 ,而这,恕我直言,是我想出的最好的解决scheme:

在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应用程序可以生成404s。

(由ASP.NET框架自动生成:)

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

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

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

(3) URL在路由表中find一个匹配项,但指定一个不存在的操作。

(手动生成:)

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

(5)一个动作抛出一个带有状态码404的HttpException。

(6)一个动作手动修改Response.StatusCode属性为404。

通常情况下,你想完成3个目标:

(1)给用户显示一个自定义的404错误页面。

(2)维护客户端响应中的404状态码(对于search引擎优化尤为重要)。

(3)直接发送响应,而不涉及302redirect。

有很多方法可以实现这一点:

(1)

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

这个解决scheme的问题:

  1. (1),(4),(6)的情况下不符合目标(1)。
  2. 不符合目标(2)自动。 必须手动编程。
  3. 不符合目标(3)。

(2)

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

这个解决scheme的问题:

  1. 只适用于IIS 7+。
  2. (2),(3),(5)的情况下不符合目标(1)。
  3. 不符合目标(2)自动。 必须手动编程。

(3)

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

这个解决scheme的问题:

  1. 只适用于IIS 7+。
  2. 不符合目标(2)自动。 必须手动编程。
  3. 它掩盖了应用程序级别的httpexception。 例如不能使用customErrors部分,System.Web.Mvc.HandleErrorAttribute等,它不能只显示通用的错误页面。

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

这个解决scheme的问题:

  1. 只适用于IIS 7+。
  2. 不符合目标(2)自动。 必须手动编程。
  3. (2),(3),(5)的情况下不符合目标(3)。

那些曾经困扰过这个问题的人甚至还想创build自己的库(参见http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html )。 但是以前的解决scheme似乎覆盖了所有的情况,没有使用外部库的复杂性。

我真的很喜欢cottsaks解决scheme,并认为它非常清楚地解释。 我唯一的补充是改变步骤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的URL

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

下面是使用MVC工具的另一种方法,您可以处理对错误的控制器名称,不良路由名称以及您在Action方法中看到的任何其他标准的请求。 就个人而言,我宁愿避免尽可能多的web.config设置,因为他们做Server.Transferredirect,并不支持使用Razor视图的ResponseRewrite( Server.Transfer )。 我宁愿返回404与自定义错误页面的search引擎优化的原因。

上面的一些是新的cottsak的技术。

此解决scheme也使用最低限度的web.config设置来支持MVC 3错误filter。

用法

只要从动作或自定义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步

将filter添加到Global.asax的GlobalFilterCollection( GlobalFilters.Filters )。 此示例将将所有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

创build一个基础控制器类,并在控制器中inheritance它。 这一步允许我们处理未知的操作名称,并将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步

创build一个ControllerFactory覆盖,并在Application_Start的Global.asax文件中覆盖它。 这个步骤允许我们在指定了无效的控制器名称时引发HTTP 404exception。

 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错误代码返回给浏览器,而无需使用筛选器属性和共享错误视图进行redirect。 It also demonstrates showing the same custom error page when invalid controller names and action names are specified.

I'll add a screenshot of an invalid controller name, action name, and a custom 404 raised from the Home/TriggerNotFound action if I get enough votes to post one =). Fiddler returns a 404 message when I access the following URLs using this solution:

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

cottsak's post above and these articles were good references.

  • Problems using CustomErrors redirectMode=ResponseRewrite
  • Elmah + MVC HandleErrorAttribute

In MVC4 WebAPI 404 can be handle in the following way,

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

HOME CONTROLLER

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

VIEW

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

RESULTS

在这里输入图像描述

Try NotFoundMVC on nuget. It works , no setup.

My shortened solution that works with unhandled areas, controllers and actions:

  1. Create a view 404.cshtml.

  2. 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"); } } 
  3. 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(); } } 
  4. Add to Application_Start() the following line:

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

Dealing with errors in ASP.NET MVC is just a pain in the butt. I tried a whole lot of suggestions on this page and on other questions and sites and nothing works good. One suggestion was to handle errors on web.config inside system.webserver but that just returns blank pages .

My goal when coming up with this solution was to;

  • NOT REDIRECT
  • Return PROPER STATUS CODES not 200/Ok like the default error handling

这是我的解决scheme。

1 .Add the following to system.web section

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

The above handles any urls not handled by routes.config and unhandled exceptions especially those encountered on the views. Notice I used aspx not html . This is so I can add a response code on the code behind.

2 。 Create a folder called Error (or whatever you prefer) at the root of your project and add the two webforms. Below is my 404 page;

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

And on the code behind I set the response code

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

Do the same for the 500 page

3 .To handle errors within the controllers. There's many ways to do it. 这是为我工作。 All my controllers inherit from a base controller. In the base controller, I have the following methods

 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 .Add the CustomError.cshtml to your Shared views folder. Below is mine;

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

Now in your application controller you can do something like this;

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

Now for the caveat . It won't handle static file errors. So if you have a route such as example.com/widgets and the user changes it to example.com/widgets.html , they will get the IIS default error page so you have to handle IIS level errors some other way.

My solution, in case someone finds it useful.

In Web.config:

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

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

Add a PageNotFound.cshtml in the Shared folder, and that's it.

Posting an answer since my comment was too long…

It's both a comment and questions to the unicorn post/answer:

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

I prefer this answer over the others for it's simplicity and the fact that apparently some folks at Microsoft were consulted. I got three questions however and if they can be answered then I will call this answer the holy grail of all 404/500 error answers on the interwebs for an ASP.NET MVC (x) app.

@Pure.Krome

  1. 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"> ?

  2. 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?!

  3. 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?

As I was saying; it would be supernice if we could make your answer more complete as this seem to be a fairly popular question with 54 000+ views.

Update : Unicorn answer does a 302 Found and a 200 OK and cannot be changed to only return 404 using a route. It has to be a physical file which is not very MVC:ish. So moving on to another solution. Too bad because this seemed to be the ultimate MVC:ish answer this far.

It seems to me that the standard CustomErrors configuration should just work however, due to the reliance on Server.Transfer it seems that the internal implementation of ResponseRewrite isn't compatible with MVC.

This feels like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module. The solution below allows you to handle any HTTP status code (including 404) by redirecting to any valid MVC route just as you would do normally.

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

This has been tested on the following platforms;

  • MVC4 in Integrated Pipeline Mode (IIS Express 8)
  • MVC4 in Classic Mode (VS Development Server, Cassini)
  • MVC4 in Classic Mode (IIS6)

优点

  • Generic solution which can be dropped into any MVC project
  • Enables support for traditional custom errors configuration
  • Works in both Integrated Pipeline and Classic modes

解决scheme

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

用法

Include this as the final HTTP module in your web.config

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

For those of you paying attention you will notice that in Integrated Pipeline mode this will always respond with HTTP 200 due to the way Server.TransferRequest works. To return the proper error code I use the following error controller.

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

Adding my solution, which is almost identical to Herman Kan's, with a small wrinkle to allow it to work for my project.

Create a custom error controller:

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

Then create a custom controller factory:

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

Finally, add an override to the custom error controller:

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

就是这样。 No need for Web.config changes.

1) Make abstract Controller class.

 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) Make inheritence from this abstract class in your all controllers

 public class HomeController : MyController {} 

3) And add a view named "NotFound" in you View-Shared folder.

I went through most of the solutions posted on this thread. While this question might be old, it is still very applicable to new projects even now, so I spent quite a lot of time reading up on the answers presented here as well as else where.

As @Marco pointed out the different cases under which a 404 can happen, I checked the solution I compiled together against that list. In addition to his list of requirements, I also added one more.

  • The solution should be able to handle MVC as well as AJAX/WebAPI calls in the most appropriate manner. (ie if 404 happens in MVC, it should show the Not Found page and if 404 happens in WebAPI, it should not hijack the XML/JSON response so that the consuming Javascript can parse it easily).

This solution is 2 fold:

First part of it comes from @Guillaume at https://stackoverflow.com/a/27354140/2310818 . Their solution takes care of any 404 that were caused due to invalid route, invalid controller and invalid action.

The idea is to create a WebForm and then make it call the NotFound action of your MVC Errors Controller. It does all of this without any redirect so you will not see a single 302 in Fiddler. The original URL is also preserved, which makes this solution fantastic!


Second part of it comes from @Germán at https://stackoverflow.com/a/5536676/2310818 . Their solution takes care of any 404 returned by your actions in the form of HttpNotFoundResult() or throw new HttpException()!

The idea is to have a filter look at the response as well as the exception thrown by your MVC controllers and to call the appropriate action in your Errors Controller. Again this solution works without any redirect and the original url is preserved!


As you can see, both of these solutions together offer a very robust error handling mechanism and they achieve all the requirements listed by @Marco as well as my requirements. If you would like to see a working sample or a demo of this solution, please leave in the comments and I would be happy to put it together.

I have gone through all articles but nothing works for me: My requirement user type anything in your url custom 404 page should show.I thought it is very straight forward.But you should understand handling of 404 properly:

  <system.web> <customErrors mode="On" redirectMode="ResponseRewrite"> <error statusCode="404" redirect="~/PageNotFound.aspx"/> </customErrors> </system.web> <system.webServer> <httpErrors errorMode="Custom"> <remove statusCode="404"/> <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/> </httpErrors> </system.webServer> 

I found this article very helpfull.should be read at once. Custome error page-Ben Foster