如果给定路线:
{FeedName}/{ItemPermalink}
例如:/博客/Hello-World
如果该项目不存在,我想返回 404。在 ASP.NET MVC 中执行此操作的正确方法是什么?
如果给定路线:
{FeedName}/{ItemPermalink}
例如:/博客/Hello-World
如果该项目不存在,我想返回 404。在 ASP.NET MVC 中执行此操作的正确方法是什么?
从臀部射击(牛仔编码;-)),我建议这样的事情:
控制器:
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,所以这只会在另一个开发人员的眼中扩展框架,以便稍后维护您的代码(你知道,知道你住在哪里的精神病)。
您可以使用反射器查看程序集以查看 HttpUnauthorizedResult 是如何实现的,因为我不知道这种方法是否遗漏了什么(似乎太简单了)。
刚才确实用反射器看了一下HttpUnauthorizedResult。似乎他们在对 0x191 (401) 的响应中设置了 StatusCode。虽然这适用于 401,但使用 404 作为新值,我似乎在 Firefox 中得到了一个空白页。Internet Explorer 显示默认的 404(不是 ASP.NET 版本)。使用 webdeveloper 工具栏,我检查了 FF 中的标题,它确实显示了 404 Not Found 响应。可能只是我在FF中配置错误的东西。
话虽如此,我认为 Jeff 的方法是 KISS 的一个很好的例子。如果您真的不需要此示例中的冗长,那么他的方法也可以正常工作。
我们这样做;此代码位于BaseController
/// <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?");
HttpNotFoundResult 是我正在使用的一个很好的第一步。返回一个 HttpNotFoundResult 很好。那么问题来了,接下来呢?
我创建了一个名为 HandleNotFoundAttribute 的操作过滤器,然后显示 404 错误页面。由于它返回一个视图,您可以为每个控制器创建一个特殊的 404 视图,或者让我们使用默认的共享 404 视图。这甚至会在控制器不存在指定的操作时调用,因为框架会抛出状态码为 404 的 HttpException。
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
};
}
}
}
请注意,从 MVC3 开始,您可以只使用HttpStatusCodeResult
.
使用ActionFilter很难维护,因为每当我们抛出错误时,都需要在属性中设置过滤器。如果我们忘记设置它怎么办?一种方法是OnException
在基本控制器上派生。您需要定义一个BaseController
派生自Controller
,并且您的所有控制器都必须派生自BaseController
. 最好有一个基本控制器。
注意如果使用Exception
响应状态码是 500,那么我们需要将其更改为 404 表示未找到,401 表示未授权。就像我上面提到的,使用OnException
覆盖BaseController
来避免使用过滤器属性。
新的 MVC 3 还通过向浏览器返回空视图来增加麻烦。经过一些研究后的最佳解决方案是基于我在此处的回答How to return a view for HttpNotFound() in ASP.Net MVC 3?
为了更方便,我把它贴在这里:
经过一番学习。MVC 3的解决方法是派生所有HttpNotFoundResult
, HttpUnauthorizedResult
,HttpStatusCodeResult
类并在.HttpNotFound
BaseController
最佳实践是使用基本控制器,这样您就可以“控制”所有派生控制器。
我创建新HttpStatusCodeResult
类,不是派生ActionResult
而是通过指定属性ViewResult
来呈现视图或任何View
您想要的。ViewName
我按照原来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
或创建新视图NotFound.cshtml
,并且您可以为状态描述和其他解释定义视图模型。