Каков правильный способ отправки HTTP 404-ответа из действия ASP.NET MVC?

Если задан маршрут:

{FeedName}/{ItemPermalink}

ex:/Блог/Hello-World

Если элемент не существует, я хочу вернуть значение 404. Каков правильный способ сделать это в ASP.NET MVC?

Ответ 1

Съемка с бедра (ковбойское кодирование;-)), я бы предложил что-то вроде этого:

Контроллер:

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.


Говоря это, я думаю, что подход Джеффа - прекрасный пример KISS. Если вам действительно не нужна подробность в этом примере, его метод также отлично работает.

Ответ 2

Мы делаем это так; этот код находится в 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();

Ответ 3

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

Ответ 4

Обратите внимание, что с MVC3 вы можете просто использовать HttpStatusCodeResult.

Ответ 5

HttpNotFoundResult - отличный первый шаг к тому, что я использую. Возврат HttpNotFoundResult хорош. Тогда возникает вопрос: что дальше?

Я создал фильтр действий с именем HandleNotFoundAttribute, который затем показывает страницу ошибки 404. Поскольку он возвращает представление, вы можете создать специальный вид 404 для каждого контроллера или позволить использовать общий доступ 404 по умолчанию. Это будет даже вызвано, если контроллер не имеет указанного действия, потому что фреймворк генерирует HttpException с кодом состояния 404.

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 на базовый контроллер. Вам необходимо определить a BaseController, полученный из Controller, и все ваши контроллеры должны получить от BaseController. Лучше всего иметь базовый контроллер.

Обратите внимание, что при использовании Exception код состояния ответа - 500, поэтому нам нужно изменить его на 404 для Not Found и 401 для Unauthorized. Как и упоминалось выше, используйте OnException переопределения на BaseController, чтобы избежать использования атрибута фильтра.

Новый MVC 3 также становится более неприятным, возвращая пустое представление браузеру. Лучшее решение после некоторых исследований основано на моем ответе здесь Как вернуть представление для HttpNotFound() в 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, как я прокомментировал код, и вы можете определить модель представления для описания состояния и других объяснений.