Se indicato il percorso:
{FeedName} / {ItemPermalink}
es: / Blog / Hello-World
Se l'elemento non esiste, voglio restituire un 404. Qual è il modo giusto per farlo in ASP.NET MVC?
Se indicato il percorso:
{FeedName} / {ItemPermalink}
es: / Blog / Hello-World
Se l'elemento non esiste, voglio restituire un 404. Qual è il modo giusto per farlo in ASP.NET MVC?
Risposte:
Scattando dall'anca (codifica 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
{
/// <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 :)
Usando questo approccio sei conforme agli standard del framework. C'è già un HttpUnauthorizedResult lì dentro, quindi questo estenderebbe semplicemente il framework agli occhi di un altro sviluppatore che manterrà il tuo codice in seguito (sai, lo psicopatico che sa dove vivi).
Potresti usare reflector per dare un'occhiata all'assembly per vedere come si ottiene HttpUnauthorizedResult, perché non so se questo approccio manchi qualcosa (sembra quasi troppo semplice).
Ho usato riflettore per dare un'occhiata a HttpUnauthorizedResult proprio ora. Sembra che stiano impostando lo StatusCode sulla risposta a 0x191 (401). Sebbene questo funzioni per 401, utilizzando 404 come nuovo valore mi sembra di ottenere solo una pagina vuota in Firefox. Internet Explorer mostra un 404 predefinito (non la versione ASP.NET). Utilizzando la barra degli strumenti del webdeveloper ho ispezionato le intestazioni in FF, che mostrano una risposta 404 Not Found. Potrebbe essere semplicemente qualcosa che ho configurato male in FF.
Detto questo, penso che l'approccio di Jeff sia un ottimo esempio di KISS. Se non hai davvero bisogno della verbosità in questo esempio, anche il suo metodo funziona bene.
Lo facciamo così; questo codice si trova inBaseController
/// <summary>
/// returns our standard page not found view
/// </summary>
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();
HttpNotFoundResult è un ottimo primo passo per quello che sto usando. La restituzione di un HttpNotFoundResult è buona. Allora la domanda è: qual è il prossimo?
Ho creato un filtro di azione chiamato HandleNotFoundAttribute che poi mostra una pagina di errore 404. Poiché restituisce una visualizzazione, è possibile creare una visualizzazione 404 speciale per controller o utilizzare una visualizzazione 404 condivisa predefinita. Questo verrà anche chiamato quando un controller non ha l'azione specificata presente, perché il framework genera 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 a partire da MVC3, puoi semplicemente usare HttpStatusCodeResult
.
HttpNotFoundResult
L'uso di ActionFilter è difficile da mantenere perché ogni volta che viene generato un errore il filtro deve essere impostato nell'attributo. E se ci dimentichiamo di impostarlo? Un modo è derivare OnException
dal controller di base. È necessario definire un BaseController
derivato da Controller
e tutti i controller devono derivare BaseController
. È consigliabile disporre di un controller di base.
Nota se l'utilizzo Exception
del codice di stato della risposta è 500, quindi è necessario modificarlo in 404 per Non trovato e 401 per Non autorizzato. Proprio come ho detto sopra, usa le OnException
sostituzioni BaseController
per evitare di usare l'attributo di filtro.
Il nuovo MVC 3 rende anche 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ù comoda la incollo qui:
Dopo un po 'di studio. La soluzione per MVC 3 è quello di trarre tutti HttpNotFoundResult
, HttpUnauthorizedResult
, HttpStatusCodeResult
le classi e implementare nuove (l'override di esso) HttpNotFound
il metodo () in BaseController
.
È buona norma utilizzare il controller di base in modo da avere il "controllo" su tutti i controller derivati.
Creo una nuova HttpStatusCodeResult
classe, non da cui derivare ActionResult
ma da cui ViewResult
renderizzare la vista o qualsiasi altra View
tu voglia specificando la ViewName
proprietà. Seguo l'originale HttpStatusCodeResult
per impostare HttpContext.Response.StatusCode
e HttpContext.Response.StatusDescription
ma poi base.ExecuteResult(context)
renderò la vista adatta perché di nuovo derivo da ViewResult
. Abbastanza semplice è? Spero che questo venga implementato nel core MVC.
Vedi il mio BaseController
muggito:
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);
}
}
}
}
Da utilizzare nella tua azione in questo modo:
public ActionResult Index()
{
// Some processing
if (...)
return HttpNotFound();
// Other processing
}
E in _Layout.cshtml (come la pagina principale)
<div class="content">
@if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>@ViewBag.Message</p></div>
}
@RenderBody()
</div>
Inoltre puoi usare una vista personalizzata come Error.shtml
o crearne di nuove NotFound.cshtml
come ho commentato nel codice e puoi definire un modello di vista per la descrizione dello stato e altre spiegazioni.