Quelle est la bonne façon d’envoyer une réponse HTTP 404 à partir d’une action ASP.NET MVC?

Si donné l’itinéraire:

{FeedName} / {ItemPermalink}

ex: / Blog / Hello-World

Si l’élément n’existe pas, je souhaite renvoyer un 404. Quelle est la bonne façon de procéder dans ASP.NET MVC?

Tir de la hanche (codage cowboy ;-)), je suggérerais quelque chose comme ceci:

Manette:

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 { /// An implementation of  that throws an . public class HttpNotFoundResult : ActionResult { /// Initializes a new instance of  with the specified . ///  public HttpNotFoundResult(Ssortingng message) { this.Message = message; } /// Initializes a new instance of  with an empty message. public HttpNotFoundResult() : this(Ssortingng.Empty) { } /// Gets or sets the message that will be passed to the thrown . public Ssortingng Message { get; set; } /// Overrides the base  functionality to throw an . public override void ExecuteResult(ControllerContext context) { throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message); } } } // By Erik van Brakel, with edits from Daniel Schaffer :) 

En utilisant cette approche, vous vous conformez aux normes-frameworks. Il y a déjà un HttpUnauthorizedResult, donc cela étendrait simplement le framework aux yeux d’un autre développeur en maintenant votre code plus tard (vous savez, le psycho qui sait où vous vivez).

Vous pouvez utiliser le réflecteur pour jeter un oeil à l’assemblage pour voir comment HttpUnauthorizedResult est atteint, car je ne sais pas si cette approche manque quelque chose (cela semble trop simple presque).


J’ai utilisé le réflecteur pour examiner le HttpUnauthorizedResult tout à l’heure. Il semble qu’ils définissent le StatusCode sur la réponse à 0x191 (401). Bien que cela fonctionne pour 401, en utilisant 404 comme nouvelle valeur, je semble obtenir une page vierge dans Firefox. Internet Explorer affiche un 404 par défaut (pas la version ASP.NET). En utilisant la barre d’outils webdeveloper, j’ai inspecté les en-têtes dans FF, qui affiche une réponse 404 Not Found. Pourrait être simplement quelque chose que j’ai mal configuré dans FF.


Cela étant dit, je pense que l’approche de Jeff est un bon exemple de KISS. Si vous n’avez pas vraiment besoin de la verbosité dans cet exemple, sa méthode fonctionne bien aussi.

Nous le faisons comme ça; ce code se trouve dans BaseController

 ///  /// returns our standard page not found view ///  protected ViewResult PageNotFound() { Response.StatusCode = 404; return View("PageNotFound"); } 

appelé comme ça

 public ActionResult ShowUserDetails(int? id) { // make sure we have a valid ID if (!id.HasValue) return PageNotFound(); 
 throw new HttpException(404, "Are you sure you're in the right place?"); 

Le HttpNotFoundResult est un excellent premier pas vers ce que j’utilise. Renvoyer un HttpNotFoundResult est bon. Alors la question est: quelle est la suite?

J’ai créé un filtre d’action appelé HandleNotFoundAtsortingbute qui affiche ensuite une page d’erreur 404. Comme il renvoie une vue, vous pouvez créer une vue 404 spéciale par contrôleur ou utiliser une vue 404 partagée par défaut. Cela sera même appelé lorsqu’un contrôleur n’a pas l’action spécifiée, car la structure lance une exception HttpException avec un code d’état de 404.

 public class HandleNotFoundAtsortingbute : ActionFilterAtsortingbute, 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 }; } } } 

Notez qu’à partir de MVC3, vous pouvez simplement utiliser HttpStatusCodeResult .

Utiliser ActionFilter est difficile à gérer, car chaque fois que nous lançons une erreur, le filtre doit être défini dans l’atsortingbut. Et si on oublie de le définir? Une manière dérive OnException sur le contrôleur de base. Vous devez définir un BaseController dérivé de Controller et tous vos contrôleurs doivent dériver de BaseController . Il est recommandé d’avoir un contrôleur de base.

Notez que si vous utilisez Exception le code d’état de la réponse est 500, nous devons donc le remplacer par 404 pour Non trouvé et 401 pour Non autorisé. Tout comme je l’ai mentionné ci-dessus, utilisez les substitutions BaseController sur BaseController pour éviter d’utiliser des atsortingbuts de filtre.

Le nouveau MVC 3 rend également plus compliqué le retour d’une vue vide au navigateur. La meilleure solution après certaines recherches est basée sur ma réponse ici Comment retourner une vue pour HttpNotFound () dans ASP.Net MVC 3?

Pour faire plus de convenance je le colle ici:


Après quelques études. La solution de contournement pour MVC 3 consiste à dériver toutes les HttpNotFoundResult , HttpUnauthorizedResult , HttpStatusCodeResult et à implémenter la nouvelle méthode HttpNotFound () dans BaseController .

Il est recommandé d’utiliser le contrôleur de base afin de contrôler tous les contrôleurs dérivés.

Je crée une nouvelle classe HttpStatusCodeResult , non pour dériver d’ ActionResult mais de ViewResult pour afficher la vue ou toute View souhaitée en spécifiant la propriété ViewName . Je suis le HttpStatusCodeResult origine pour définir le HttpContext.Response.StatusCode et le HttpContext.Response.StatusDescription mais base.ExecuteResult(context) affichera alors la vue appropriée car je dérive à nouveau de ViewResult . Assez simple est-ce? J’espère que cela sera implémenté dans le kernel MVC.

Voir ci-dessous mon 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(ssortingng statusDescription = null) { return new HttpNotFoundResult(statusDescription); } protected HttpUnauthorizedResult HttpUnauthorized(ssortingng statusDescription = null) { return new HttpUnauthorizedResult(statusDescription); } protected class HttpNotFoundResult : HttpStatusCodeResult { public HttpNotFoundResult() : this(null) { } public HttpNotFoundResult(ssortingng statusDescription) : base(404, statusDescription) { } } protected class HttpUnauthorizedResult : HttpStatusCodeResult { public HttpUnauthorizedResult(ssortingng statusDescription) : base(401, statusDescription) { } } protected class HttpStatusCodeResult : ViewResult { public int StatusCode { get; private set; } public ssortingng StatusDescription { get; private set; } public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { } public HttpStatusCodeResult(int statusCode, ssortingng 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); } } } } 

Pour utiliser dans votre action comme ceci:

 public ActionResult Index() { // Some processing if (...) return HttpNotFound(); // Other processing } 

Et dans _Layout.cshtml (comme la page maître)

 
@if (ViewBag.Message != null) {

@ViewBag.Message

} @RenderBody()

De plus, vous pouvez utiliser une vue personnalisée comme Error.shtml ou créer un nouveau NotFound.cshtml comme je l’ai NotFound.cshtml dans le code et définir un modèle de vue pour la description du statut et d’autres explications.