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?
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?
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.
Robimy to w ten sposób; ten kod znajduje się wBaseController
/// <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();
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
};
}
}
}
Zwróć uwagę, że od MVC3 możesz po prostu użyć HttpStatusCodeResult
.
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ć element BaseController
pochodny Controller
i wszystkie kontrolery muszą pochodzić z BaseController
. 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żyj OnException
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) w BaseController
.
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ąc ViewName
właściwość. Podążam za oryginałem, HttpStatusCodeResult
aby ustawić, HttpContext.Response.StatusCode
a HttpContext.Response.StatusDescription
następnie base.ExecuteResult(context)
wyrenderuję odpowiedni widok, ponieważ ponownie wywodzę się z ViewResult
. 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ń.