Jeśli podano trasę:
{FeedName} / {ItemPermalink}
np .: / Blog / Hello-World
Jeśli element nie istnieje, chcę zwrócić 404. Jaki jest właściwy sposób zrobienia tego w ASP.NET MVC?
asp.net
asp.net-mvc
http-status-code-404
Daniel Schaffer
źródło
źródło
Odpowiedzi:
Strzelając z biodra (kodowanie kowbojskie ;-)), proponuję coś takiego:
Kontroler:
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 :)
Stosując takie podejście, przestrzegasz standardów ramowych. Jest tam już HttpUnauthorizedResult, więc to po prostu rozszerzyłoby strukturę w oczach innego dewelopera, który później utrzymuje twój kod (wiesz, psychol, który wie, gdzie mieszkasz).
Możesz użyć reflektora, aby przyjrzeć się asemblacji, aby zobaczyć, jak osiągnięto HttpUnauthorizedResult, ponieważ nie wiem, czy to podejście coś pomija (wydaje się prawie zbyt proste).
Właśnie użyłem reflektora, aby przyjrzeć się HttpUnauthorizedResult. Wygląda na to, że ustawiają StatusCode w odpowiedzi na 0x191 (401). Chociaż działa to dla 401, używając 404 jako nowej wartości, wydaje się, że w Firefoksie pojawia się tylko pusta strona. Internet Explorer pokazuje jednak domyślny 404 (nie wersję ASP.NET). Korzystając z paska narzędzi webdeveloper, przejrzałem nagłówki w FF, które DO pokazują odpowiedź 404 Not Found. Może to być po prostu coś, co źle skonfigurowałem w FF.
Biorąc to pod uwagę, myślę, że podejście Jeffa jest dobrym przykładem KISS. Jeśli tak naprawdę nie potrzebujesz szczegółowości w tym przykładzie, jego metoda również działa dobrze.
źródło
Robimy to w ten sposób; ten kod znajduje się w
BaseController
/// <summary> /// returns our standard page not found view /// </summary> protected ViewResult PageNotFound() { Response.StatusCode = 404; return View("PageNotFound"); }
tak nazywany
public ActionResult ShowUserDetails(int? id) { // make sure we have a valid ID if (!id.HasValue) return PageNotFound();
źródło
throw new HttpException(404, "Are you sure you're in the right place?");
źródło
web.config
.HttpNotFoundResult to świetny pierwszy krok do tego, czego używam. Zwracanie HttpNotFoundResult jest dobre. W takim razie pytanie brzmi: co dalej?
Utworzyłem filtr akcji o nazwie HandleNotFoundAttribute, który następnie wyświetla stronę błędu 404. Ponieważ zwraca widok, możesz utworzyć specjalny widok 404 na kontroler lub użyć domyślnego udostępnionego widoku 404. Będzie to nawet wywoływane, gdy kontroler nie ma określonej akcji, ponieważ struktura zgłasza HttpException z kodem stanu 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 }; } } }
źródło
Zwróć uwagę, że od MVC3 możesz po prostu użyć
HttpStatusCodeResult
.źródło
HttpNotFoundResult
Korzystanie z ActionFilter jest trudne do utrzymania, ponieważ za każdym razem, gdy zgłaszamy błąd, filtr musi być ustawiony w atrybucie. A co, jeśli zapomnimy go ustawić? Jednym ze sposobów jest wyprowadzenie
OnException
na podstawie kontrolera. Musisz zdefiniować elementBaseController
pochodnyController
i wszystkie kontrolery muszą pochodzić zBaseController
. Najlepszą praktyką jest posiadanie podstawowego kontrolera.Zwróć uwagę, jeśli używasz
Exception
kodu stanu odpowiedzi 500, więc musimy zmienić go na 404 dla Nie znaleziono i 401 dla Nieautoryzowany. Tak jak wspomniałem powyżej, użyjOnException
zastąpień,BaseController
aby uniknąć używania atrybutu filtra.Nowy MVC 3 jest również bardziej kłopotliwy, zwracając pusty widok do przeglądarki. Najlepsze rozwiązanie po niektórych badaniach opiera się na mojej odpowiedzi tutaj. Jak zwrócić widok dla HttpNotFound () w ASP.Net MVC 3?
Dla większej wygody wklejam go tutaj:
Po kilku badaniach. Rozwiązaniem dla MVC 3 jest tu czerpać wszystkie
HttpNotFoundResult
,HttpUnauthorizedResult
,HttpStatusCodeResult
zajęcia i wdrożyć nową (przesłanianie go)HttpNotFound
(metoda) wBaseController
.Najlepszą praktyką jest używanie kontrolera podstawowego, aby mieć „kontrolę” nad wszystkimi kontrolerami pochodnymi.
Tworzę nową
HttpStatusCodeResult
klasę, nie po to, aby pochodzić z,ActionResult
ale z,ViewResult
aby renderować widok lub dowolną innąView
, określającViewName
właściwość. Podążam za oryginałem,HttpStatusCodeResult
aby ustawić,HttpContext.Response.StatusCode
aHttpContext.Response.StatusDescription
następniebase.ExecuteResult(context)
wyrenderuję odpowiedni widok, ponieważ ponownie wywodzę się zViewResult
. Wystarczająco proste, prawda? Mam nadzieję, że zostanie to zaimplementowane w rdzeniu MVC.Zobacz mój
BaseController
poniżej: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); } } } }
Aby użyć w swoim działaniu w następujący sposób:
public ActionResult Index() { // Some processing if (...) return HttpNotFound(); // Other processing }
I w _Layout.cshtml (jak strona wzorcowa)
<div class="content"> @if (ViewBag.Message != null) { <div class="inlineMsg"><p>@ViewBag.Message</p></div> } @RenderBody() </div>
Dodatkowo możesz użyć niestandardowego widoku, takiego jak
Error.shtml
lub utworzyć nowy,NotFound.cshtml
jak skomentowałem w kodzie, a także możesz zdefiniować model widoku dla opisu stanu i innych wyjaśnień.źródło