Якщо вказано маршрут:
{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 Не знайдено. Це може бути просто те, що я неправильно налаштував у 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 для Не знайдено та 401 для Несанкціонованого. Як я вже згадував вище, використовуйте OnException
перевизначення, BaseController
щоб уникнути використання атрибута filter.
Новий 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
як я коментував у коді, і ви можете визначити модель перегляду для опису стану та інших пояснень.