从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的一个很好的例子。 如果你真的不需要这个样本中的冗长,他的方法也可以正常工作。
我们这样做; 这个代码在BaseController
find
/// <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的解决方法是派生所有HttpNotFoundResult
, HttpNotFoundResult
, HttpUnauthorizedResult
类并在BaseController
实现新的 (覆盖它) HttpNotFound
()方法。
使用基本控制器是最好的做法,因此您可以对所有派生的控制器进行“控制”。
我创build了新的HttpStatusCodeResult
类,不是从ActionResult
派生,而是从ViewResult
派生来通过指定ViewName
属性来呈现视图或任何View
。 我按照原来的HttpStatusCodeResult
来设置HttpContext.Response.StatusCode
和HttpContext.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
,并且可以为状态描述和其他解释定义视图模型。