Si l'itinéraire est donné:
{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?
Si l'itinéraire est donné:
{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?
Réponses:
Tirant de la hanche (codage cowboy ;-)), je suggère 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
{
/// <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 :)
En utilisant cette approche, vous vous conformez aux normes du cadre. Il y a déjà un HttpUnauthorizedResult là-dedans, donc cela élargirait simplement le cadre aux yeux d'un autre développeur qui maintiendrait votre code plus tard (vous savez, le psychopathe qui sait où vous habitez).
Vous pouvez utiliser réflecteur pour jeter un œil dans l'assemblage pour voir comment le HttpUnauthorizedResult est atteint, car je ne sais pas si cette approche manque quelque chose (cela semble presque trop simple).
J'ai utilisé le réflecteur pour jeter un coup d'œil à 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, il semble que je n'obtienne qu'une page vierge dans Firefox. Internet Explorer affiche cependant un 404 par défaut (pas la version ASP.NET). En utilisant la barre d'outils du développeur Web, j'ai inspecté les en-têtes dans FF, qui affichent une réponse 404 Not Found. Peut-être simplement quelque chose que j'ai mal configuré dans FF.
Cela étant dit, je pense que l'approche de Jeff est un bel exemple de KISS. Si vous n'avez pas vraiment besoin de la verbosité de cet exemple, sa méthode fonctionne également très bien.
Nous le faisons comme ça; ce code se trouve dansBaseController
/// <summary>
/// returns our standard page not found view
/// </summary>
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();
Le HttpNotFoundResult est une excellente première étape vers ce que j'utilise. Renvoyer un HttpNotFoundResult est bon. Alors la question est, quelle est la prochaine étape?
J'ai créé un filtre d'action appelé HandleNotFoundAttribute qui affiche ensuite une page d'erreur 404. Puisqu'il renvoie une vue, vous pouvez créer une vue 404 spéciale par contrôleur, ou laisser est 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 présente, car le framework lève une HttpException avec un code d'état de 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
};
}
}
}
Notez qu'à partir de MVC3, vous pouvez simplement utiliser HttpStatusCodeResult
.
HttpNotFoundResult
L'utilisation d' ActionFilter est difficile à gérer car chaque fois que nous lançons une erreur, le filtre doit être défini dans l'attribut. Et si on oublie de le régler? Une façon consiste à dériver 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 deBaseController
. Il est recommandé d'avoir un contrôleur de base.
Notez que si Exception
le code d'état de réponse est 500, nous devons le changer en 404 pour Non trouvé et 401 pour Non autorisé. Comme je l'ai mentionné ci-dessus, utilisez les OnException
remplacements surBaseController
pour éviter d'utiliser l'attribut de filtre.
Le nouveau MVC 3 rend également plus gênant en renvoyant une vue vide au navigateur. La meilleure solution après quelques recherches est basée sur ma réponse ici Comment renvoyer une vue pour HttpNotFound () dans ASP.Net MVC 3?
Pour plus de commodité, je le colle ici:
Après quelques études. La solution de contournement pour MVC 3 ici est de tirer tous HttpNotFoundResult
, HttpUnauthorizedResult
, les HttpStatusCodeResult
classes et mettre en œuvre une nouvelle ( la redéfinir) HttpNotFound
méthode () dansBaseController
.
Il est recommandé d'utiliser le contrôleur de base pour avoir un «contrôle» sur tous les contrôleurs dérivés.
Je crée une nouvelle HttpStatusCodeResult
classe, non pas pour dériver ActionResult
mais ViewResult
pour rendre la vue ou celle que View
vous voulez en spécifiant la ViewName
propriété. Je suis l'original HttpStatusCodeResult
pour définir le HttpContext.Response.StatusCode
et HttpContext.Response.StatusDescription
puis base.ExecuteResult(context)
je rendrai la vue appropriée car encore une fois je dérive ViewResult
. Est-ce assez simple? J'espère que cela sera implémenté dans le noyau MVC.
Voir mon BaseController
ci - dessous:
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);
}
}
}
}
À 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)
<div class="content">
@if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>@ViewBag.Message</p></div>
}
@RenderBody()
</div>
De plus, vous pouvez utiliser une vue personnalisée comme Error.shtml
ou créer une nouvelle NotFound.cshtml
comme j'ai commenté dans le code et vous pouvez définir un modèle de vue pour la description du statut et d'autres explications.