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

时间:2009-01-31 23:48:32

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

如果给出路线:

{FeedName} / {ItemPermalink}

例如:/ Blog / Hello-World

如果该项不存在,我想返回404.在ASP.NET MVC中执行此操作的正确方法是什么?

6 个答案:

答案 0 :(得分:68)

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

<强>控制器:

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。似乎他们将响应的StatusCode设置为0x191(401)。虽然这适用于401,但使用404作为新值我似乎只是在Firefox中获得一个空白页面。 Internet Explorer显示默认404(不是ASP.NET版本)。使用webdeveloper工具栏我检查了FF中的标题,其中DO显示404 Not Found响应。可能只是我在FF中配置错误的东西。


话虽如此,我认为Jeff的方法是KISS的一个很好的例子。如果你真的不需要这个样本中的详细程度,他的方法也可以正常工作。

答案 1 :(得分:46)

我们这样做;此代码位于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();

答案 2 :(得分:19)

throw new HttpException(404, "Are you sure you're in the right place?");

答案 3 :(得分:7)

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

答案 4 :(得分:7)

请注意,从MVC3开始,您只需使用HttpStatusCodeResult

答案 5 :(得分:5)

使用 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 的解决方法是派生所有HttpNotFoundResultHttpUnauthorizedResultHttpStatusCodeResult类并实施 new (覆盖它){ HttpNotFound中的{1}}()方法。

最佳做法是使用基本控制器,以便“控制”所有派生的控制器。

我创建了新的BaseController课程,不是来自HttpStatusCodeResult而是来自ActionResult,以通过指定ViewResult来呈现视图或任何View属性。我按原始ViewName设置HttpStatusCodeResultHttpContext.Response.StatusCode,但HttpContext.Response.StatusDescription将呈现合适的视图,因为我再次从base.ExecuteResult(context)派生。这很简单吗?希望这将在MVC核心中实现。

见下文ViewResult

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

在_Layout.cshtml(如母版页)

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

此外,您可以使用自定义视图,例如<div class="content"> @if (ViewBag.Message != null) { <div class="inlineMsg"><p>@ViewBag.Message</p></div> } @RenderBody() </div> 或创建新的Error.shtml,就像我在代码中注释一样,您可以为状态说明和其他说明定义视图模型。