Если указан маршрут:
{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как я прокомментировал в коде, и вы можете определить модель представления для описания статуса и других объяснений.