从ASP.NET MVC操作发送HTTP 404响应的正确方法是什么?

如果给定路线:

{FeedName} / {ItemPermalink}

例如:/ Blog / Hello-World

如果项目不存在,我想返回一个404。在ASP.NET MVC中做这件事的正确方法是什么?

从臀部拍摄(牛仔编码;-)),我会build议这样的事情:

控制器:

public class HomeController : Controller { public ActionResult Index() { return new HttpNotFoundResult("This doesn't exist"); } } 

HttpNotFoundResult:

 using System; using System.Net; using System.Web; using System.Web.Mvc; namespace YourNamespaceHere { /// <summary>An implementation of <see cref="ActionResult" /> that throws an <see cref="HttpException" />.</summary> public class HttpNotFoundResult : ActionResult { /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with the specified <paramref name="message"/>.</summary> /// <param name="message"></param> public HttpNotFoundResult(String message) { this.Message = message; } /// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with an empty message.</summary> public HttpNotFoundResult() : this(String.Empty) { } /// <summary>Gets or sets the message that will be passed to the thrown <see cref="HttpException" />.</summary> public String Message { get; set; } /// <summary>Overrides the base <see cref="ActionResult.ExecuteResult" /> functionality to throw an <see cref="HttpException" />.</summary> public override void ExecuteResult(ControllerContext context) { throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message); } } } // By Erik van Brakel, with edits from Daniel Schaffer :) 

使用这种方法你遵守框架标准。 那里已经有了一个HttpUnauthorizedResult,所以这只会延伸到另一个维护你的代码的开发者(你知道,那个知道你住在哪里的心理学家)眼里的框架。

您可以使用reflection器来查看程序集,以查看HttpUnauthorizedResult是如何实现的,因为我不知道这种方法是否会漏掉任何东西(这似乎太简单了)。


我刚刚使用了reflection器来看看HttpUnauthorizedResult。 似乎他们正在设置状态码0x191(401)的响应。 虽然这适用于401,使用404作为新的价值,我似乎只是在Firefox的空白页面。 Internet Explorer显示了默认的404(不是ASP.NET版本)。 使用webdeveloper工具条,我检查了FF中的标题,这些标题显示404 Not Found响应。 可能只是我在FF中configuration错误。


这就是说,我认为杰夫的做法是KISS的一个很好的例子。 如果你真的不需要这个样本中的冗长,他的方法也可以正常工作。

我们这样做; 这个代码在BaseControllerfind

 /// <summary> /// returns our standard page not found view /// </summary> protected ViewResult PageNotFound() { Response.StatusCode = 404; return View("PageNotFound"); } 

这样叫

 public ActionResult ShowUserDetails(int? id) { // make sure we have a valid ID if (!id.HasValue) return PageNotFound(); 
 throw new HttpException(404, "Are you sure you're in the right place?"); 

请注意,从MVC3开始,您可以使用HttpStatusCodeResult

HttpNotFoundResult是我使用的第一步。 返回一个HttpNotFoundResult是很好的。 那么问题是,接下来呢?

我创build了一个名为HandleNotFoundAttribute的动作filter,然后显示一个404错误页面。 既然它返回一个视图,你可以为每个控制器创build一个特殊的404视图,或者让我们使用默认的共享404视图。 当控制器没有指定的动作时,甚至会被调用,因为框架抛出一个状态码为404的HttpExceptionexception。

 public class HandleNotFoundAttribute : ActionFilterAttribute, IExceptionFilter { public void OnException(ExceptionContext filterContext) { var httpException = filterContext.Exception.GetBaseException() as HttpException; if (httpException != null && httpException.GetHttpCode() == (int)HttpStatusCode.NotFound) { filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; // Prevents IIS from intercepting the error and displaying its own content. filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound; filterContext.Result = new ViewResult { ViewName = "404", ViewData = filterContext.Controller.ViewData, TempData = filterContext.Controller.TempData }; } } } 

使用ActionFilter 很难维护,因为每当我们抛出一个错误,filter需要在属性中设置。 如果我们忘记设置呢? 一种方法是在基础控制器上派生OnException 。 您需要定义从Controller派生的BaseController ,并且您的所有控制器都必须从BaseController派生。 拥有一个基础控制器是一个最佳实践。

请注意,如果使用Exception ,响应状态码是500,所以我们需要将其更改为404为未find和401为未经授权。 就像我上面提到的,使用OnException覆盖BaseController以避免使用filter属性。

新的MVC 3通过向浏览器返回一个空的视图也使麻烦更多。 一些研究后,最好的解决scheme是基于我的答案在这里如何在ASP.Net MVC 3中返回HttpNotFound()的视图?

为了更方便我把它粘贴在这里:


经过一番研究。 MVC 3的解决方法是派生所有HttpNotFoundResultHttpNotFoundResultHttpUnauthorizedResult类并在BaseController实现新的 (覆盖它) HttpNotFound ()方法。

使用基本控制器是最好的做法,因此您可以对所有派生的控制器进行“控制”。

我创build了新的HttpStatusCodeResult类,不是从ActionResult派生,而是从ViewResult派生来通过指定ViewName属性来呈现视图或任何View 。 我按照原来的HttpStatusCodeResult来设置HttpContext.Response.StatusCodeHttpContext.Response.StatusDescription但是然后base.ExecuteResult(context)将呈现合适的视图,因为我再次从ViewResult派生。 够简单了吗? 希望这将在MVC内核中实现。

看到我的BaseController

 using System.Web; using System.Web.Mvc; namespace YourNamespace.Controllers { public class BaseController : Controller { public BaseController() { ViewBag.MetaDescription = Settings.metaDescription; ViewBag.MetaKeywords = Settings.metaKeywords; } protected new HttpNotFoundResult HttpNotFound(string statusDescription = null) { return new HttpNotFoundResult(statusDescription); } protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null) { return new HttpUnauthorizedResult(statusDescription); } protected class HttpNotFoundResult : HttpStatusCodeResult { public HttpNotFoundResult() : this(null) { } public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { } } protected class HttpUnauthorizedResult : HttpStatusCodeResult { public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { } } protected class HttpStatusCodeResult : ViewResult { public int StatusCode { get; private set; } public string StatusDescription { get; private set; } public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { } public HttpStatusCodeResult(int statusCode, string statusDescription) { this.StatusCode = statusCode; this.StatusDescription = statusDescription; } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } context.HttpContext.Response.StatusCode = this.StatusCode; if (this.StatusDescription != null) { context.HttpContext.Response.StatusDescription = this.StatusDescription; } // 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or // 2. Uncomment this and change to any custom view and set the name here or simply // 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the @ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized //this.ViewName = "Error"; this.ViewBag.Message = context.HttpContext.Response.StatusDescription; base.ExecuteResult(context); } } } } 

要在你的行动中使用这样的:

 public ActionResult Index() { // Some processing if (...) return HttpNotFound(); // Other processing } 

并在_Layout.cshtml(如母版页)

 <div class="content"> @if (ViewBag.Message != null) { <div class="inlineMsg"><p>@ViewBag.Message</p></div> } @RenderBody() </div> 

另外,您可以使用像Error.shtml这样的自定义视图,或者像我在代码中评论的那样创build新的NotFound.cshtml ,并且可以为状态描述和其他解释定义视图模型。