Qual è il modo corretto di inviare una risposta HTTP 404 da un’azione MVC ASP.NET?

Se dato il percorso:

{FeedName} / {} ItemPermalink

ex: / Blog / Hello-World

Se l’articolo non esiste, voglio restituire un 404. Qual è il modo giusto per farlo in ASP.NET MVC?

Sparando dal fianco (codifica da cowboy ;-)), suggerirei qualcosa del genere:

controller:

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(String message) { this.Message = message; } /// Initializes a new instance of  with an empty message. public HttpNotFoundResult() : this(String.Empty) { } /// Gets or sets the message that will be passed to the thrown . public String 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 :) 

Usando questo approccio rispetti gli standard del framework. C’è già un HttpUnauthorizedResult, quindi questo semplicemente estenderebbe la struttura agli occhi di un altro sviluppatore mantenendo il tuo codice in seguito (lo sai, lo psico che sa dove vivi).

È ansible utilizzare Reflector per dare un’occhiata all’assemblaggio per vedere come viene realizzato HttpUnauthorizedResult, perché non so se questo approccio manchi qualcosa (sembra quasi troppo semplice).


Ho usato Reflector per dare un’occhiata a HttpUnauthorizedResult proprio ora. Sembra che stiano impostando lo StatusCode sulla risposta a 0x191 (401). Anche se questo funziona per il 401, usando 404 come nuovo valore mi sembra di avere solo una pagina vuota in Firefox. Internet Explorer mostra comunque un 404 predefinito (non la versione di ASP.NET). Utilizzando la barra degli strumenti del webdeveloper ho ispezionato le intestazioni in FF, che DO mostra una risposta 404 non trovata. Potrebbe essere semplicemente qualcosa che ho configurato male in FF.


Detto questo, penso che l’approccio di Jeff sia un bell’esempio di KISS. Se in questo esempio non hai davvero bisogno della verbosità, anche il suo metodo funziona bene.

Lo facciamo così; questo codice si trova in BaseController

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

chiamato così

 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?"); 

HttpNotFoundResult è un ottimo primo passo verso ciò che sto usando. Restituire un HttpNotFoundResult è buono. Quindi la domanda è, quali sono le prospettive?

Ho creato un filtro azione chiamato HandleNotFoundAttribute che mostra una pagina di errore 404. Poiché restituisce una vista, è ansible creare una vista 404 speciale per controller o utilizzare una vista 404 condivisa predefinita. Questo verrà anche chiamato quando un controller non ha l’azione specificata presente, perché il framework lancia una HttpException con un codice di stato 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 }; } } } 

Nota che da MVC3 puoi semplicemente usare HttpStatusCodeResult .

L’uso di ActionFilter è difficile da mantenere perché ogni volta che si genera un errore, è necessario impostare il filtro nell’attributo. Cosa succede se ci dimentichiamo di impostarlo? Un modo è derivare da OnException sul controller di base. È necessario definire un BaseController derivato dal Controller e tutti i controller devono derivare da BaseController . È consigliabile disporre di un controller di base.

Nota se si utilizza Exception il codice di stato della risposta è 500, quindi è necessario cambiarlo in 404 per Non trovato e 401 per Non autorizzato. Proprio come ho detto sopra, usa OnException override di BaseController su BaseController per evitare di usare l’attributo filter.

Anche il nuovo MVC 3 diventa più problematico restituendo una vista vuota al browser. La soluzione migliore dopo alcune ricerche si basa sulla mia risposta qui Come restituire una vista per HttpNotFound () in ASP.Net MVC 3?

Per rendere più convenienza lo incollo qui:


Dopo qualche studio. La soluzione alternativa per MVC 3 consiste nel derivare tutte le HttpNotFoundResult , HttpUnauthorizedResult , HttpStatusCodeResult e implementare il metodo HttpNotFound () (in HttpNotFound ) nuovo in BaseController .

È consigliabile utilizzare il controller di base in modo da avere il controllo su tutti i controller derivati.

Creo una nuova class HttpStatusCodeResult , non per derivare da ActionResult ma da ViewResult per ViewResult rendering della vista o di qualsiasi View desiderata specificando la proprietà ViewName . Seguo il HttpStatusCodeResult originale per impostare HttpContext.Response.StatusCode e HttpContext.Response.StatusDescription ma poi base.ExecuteResult(context) renderà la vista adatta perché ancora una volta deriva da ViewResult . È abbastanza semplice? Spero che questo sarà implementato nel nucleo MVC.

Vedi il mio BaseController sotto:

 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); } } } } 

Per usare nella tua azione in questo modo:

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

E in _Layout.cshtml (come nella pagina principale)

 
@if (ViewBag.Message != null) {

@ViewBag.Message

} @RenderBody()

Inoltre è ansible utilizzare una vista personalizzata come Error.shtml o creare un nuovo NotFound.cshtml come ho commentato nel codice e si può definire un modello di vista per la descrizione dello stato e altre spiegazioni.