Если указан маршрут:
{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. Кажется, они устанавливают StatusCode в ответе на 0x191 (401). Хотя это работает для 401, используя 404 в качестве нового значения, я, кажется, получаю просто пустую страницу в Firefox. Internet Explorer показывает по умолчанию 404 (не версию ASP.NET). Используя панель инструментов веб-разработчика, я проверил заголовки в FF, которые ДОЛЖНЫ отображать ответ 404 Not Found. Возможно, я просто неправильно настроил FF.
При этом я думаю, что подход Джеффа является прекрасным примером 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();
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
};
}
}
}
Обратите внимание, что с MVC3 вы можете просто использовать HttpStatusCodeResult
.
HttpNotFoundResult
Использование ActionFilter это трудно поддерживать , потому что всякий раз , когда мы бросаем об ошибке , необходимость фильтра устанавливается в атрибуте. Что, если мы забудем его установить? Один из способов - получение OnException
на базе контроллера. Вам необходимо определить 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
как я прокомментировал в коде, и вы можете определить модель представления для описания статуса и других объяснений.