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


Answers:


69

从臀部射击(牛仔编码;-)),我建议这样:

控制器:

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中的标头,这些标头确实显示404 Not Found响应。可能只是我在FF中配置错误而已。


话虽这么说,我认为Jeff的方法是KISS的一个很好的例子。如果您在此示例中确实不需要冗长,那么他的方法也可以正常工作。


是的,我也注意到了Enum。正如我所说,这只是一个粗糙的例子,可以随时进行改进。毕竟,这应该是一个知识库;-)
Erik van Brakel,

我想我有点过分了……享受:D
Daniel Schaffer

FWIW,Jeff的示例还要求您具有自定义的404页面。
Daniel Schaffer

2
抛出HttpException而不是仅设置HttpContext.Response.StatusCode = 404的一个问题是,如果您使用OnException Controller处理程序(如我一样),它也会捕获HttpExceptions。因此,我认为仅设置StatusCode是更好的方法。
伊戈尔·布赖克

4
MVC3中的HttpException或HttpNotFoundResult在许多方面都很有用。对于@Igor Brejc,只需在OnException中使用if语句来滤除未找到的错误。
CallMeLaNN 2011年

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

然后将此操作连接到默认路由吗?无法看到如何执行它。
Christian Dalager

2
可以像这样运行它:受保护的重写void HandleUnknownAction(string actionName){PageNotFound()。ExecuteResult(this.ControllerContext); }
Tristan Warner-Smith

我曾经这样做,但是发现将结果和显示的视图分开是一种更好的方法。在下面查看我的答案。
布莱恩·瓦莱伦加

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

我喜欢这样做,因为它遵循中设置的自定义错误页面web.config
Mike Cole

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


6

使用ActionFilter很难维持的,因为每当我们抛出一个错误的过滤器需要的属性进行设置。如果我们忘记设置该怎么办?一种方法是OnException基于基本控制器。您需要定义一个BaseControllerfrom 的派生,Controller并且您的所有控制器都必须从派生BaseController。最好有一个基本控制器。

请注意,如果使用Exception的响应状态代码是500,那么对于未找到,我们需要将其更改为404,对于未授权,则需要将其更改为401。就像我上面提到的,OnExceptionBaseController以避免使用filter属性。

通过向浏览器返回空视图,新的MVC 3也会带来更多麻烦。经过研究后的最佳解决方案基于我的回答如何在ASP.Net MVC 3中返回HttpNotFound()的视图?

为了更加方便起见,我将其粘贴在此处:


经过研究。对于解决办法MVC 3这里是推导出所有的HttpNotFoundResultHttpUnauthorizedResultHttpStatusCodeResult类和实现新的(重写它)HttpNotFound在()方法BaseController

最佳实践是使用基本控制器,以便您可以“控制”所有派生的控制器。

我创建新HttpStatusCodeResult类,而不是从中派生ActionResult而是通过指定属性ViewResult来呈现视图或所需的任何类。我按照原图设置,然后再渲染合适的视图,因为我还是从派生的。很简单吗?希望这将在MVC核心中实现。ViewViewNameHttpStatusCodeResultHttpContext.Response.StatusCodeHttpContext.Response.StatusDescriptionbase.ExecuteResult(context)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);
            }
        }
    }
}

在这样的动作中使用:

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,并且可以为状态描述和其他说明定义视图模型。


您始终可以注册一个击败基本控制器的全局过滤器,因为您必须记住要使用基本控制器!
John Culviner

:)不知道这还是MVC4中的问题。当时我的意思是其他人回答的HandleNotFoundAttribute过滤器。不必对每个动作都应用。例如,它仅适用于具有id参数的操作,但不适用于Index()操作。我同意全局过滤器,不是针对HandleNotFoundAttribute,而是针对自定义HandleErrorAttribute。
CallMeLaNN

我以为MVC3也有,不确定。无论别人
怎么
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.