Si se le da la ruta:
{FeedName} / {ItemPermalink}
ej .: / Blog / Hello-World
Si el artículo no existe, quiero devolver un 404. ¿Cuál es la forma correcta de hacer esto en ASP.NET MVC?
Si se le da la ruta:
{FeedName} / {ItemPermalink}
ej .: / Blog / Hello-World
Si el artículo no existe, quiero devolver un 404. ¿Cuál es la forma correcta de hacer esto en ASP.NET MVC?
Respuestas:
Disparando desde la cadera (codificación de vaquero ;-)), sugeriría algo como esto:
Controlador:
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 :)
Con este enfoque, cumple con los estándares del marco. Ya hay un HttpUnauthorizedResult allí, por lo que esto simplemente ampliaría el marco a los ojos de otro desarrollador que mantendrá su código más adelante (ya sabe, el psicópata que sabe dónde vive).
Podría usar reflector para echar un vistazo al ensamblaje y ver cómo se logra HttpUnauthorizedResult, porque no sé si este enfoque pierde algo (casi parece demasiado simple).
Usé reflector para echar un vistazo al HttpUnauthorizedResult en este momento. Parece que están configurando el StatusCode en la respuesta a 0x191 (401). Aunque esto funciona para 401, usando 404 como el nuevo valor, parece que obtengo solo una página en blanco en Firefox. Sin embargo, Internet Explorer muestra un 404 predeterminado (no la versión ASP.NET). Usando la barra de herramientas del desarrollador web, inspeccioné los encabezados en FF, que SÍ muestran una respuesta 404 No encontrado. Podría ser simplemente algo que configuré mal en FF.
Dicho esto, creo que el enfoque de Jeff es un buen ejemplo de KISS. Si realmente no necesita la verbosidad en esta muestra, su método también funciona bien.
Lo hacemos así; este código se encuentra enBaseController
/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
Response.StatusCode = 404;
return View("PageNotFound");
}
llamado así
public ActionResult ShowUserDetails(int? id)
{
// make sure we have a valid ID
if (!id.HasValue) return PageNotFound();
HttpNotFoundResult es un gran primer paso para lo que estoy usando. Devolver un HttpNotFoundResult es bueno. Entonces la pregunta es, ¿qué sigue?
Creé un filtro de acción llamado HandleNotFoundAttribute que luego muestra una página de error 404. Dado que devuelve una vista, puede crear una vista 404 especial por controlador, o dejar que use una vista 404 compartida predeterminada. Esto incluso se llamará cuando un controlador no tenga la acción especificada presente, porque el marco arroja una HttpException con un código de estado 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
};
}
}
}
Tenga en cuenta que a partir de MVC3, solo puede usar HttpStatusCodeResult
.
HttpNotFoundResult
El uso de ActionFilter es difícil de mantener porque cada vez que arrojamos un error, el filtro debe establecerse en el atributo. ¿Qué pasa si nos olvidamos de configurarlo? Una forma es derivar OnException
en el controlador base. Debe definir un BaseController
derivado de Controller
y todos los controladores deben derivar de BaseController
. Es una buena práctica tener un controlador base.
Tenga en cuenta que si Exception
el código de estado de respuesta es 500, debemos cambiarlo a 404 para No encontrado y 401 para No autorizado. Como mencioné anteriormente, use OnException
anulaciones BaseController
para evitar usar el atributo de filtro.
El nuevo MVC 3 también hace más problemático al devolver una vista vacía al navegador. La mejor solución después de algunas investigaciones se basa en mi respuesta aquí. ¿Cómo devolver una vista para HttpNotFound () en ASP.Net MVC 3?
Para hacer más conveniencia lo pego aquí:
Después de un poco de estudio. La solución para MVC 3 aquí es derivar todos HttpNotFoundResult
, HttpUnauthorizedResult
, HttpStatusCodeResult
clases e implementar nueva (anulando ella) HttpNotFound
método () en BaseController
.
Es una buena práctica utilizar el controlador base para que tenga "control" sobre todos los controladores derivados.
Creo una nueva HttpStatusCodeResult
clase, no para derivar ActionResult
sino desde ViewResult
para representar la vista o cualquiera View
que desee especificando la ViewName
propiedad. Sigo el original HttpStatusCodeResult
para establecer el HttpContext.Response.StatusCode
y, HttpContext.Response.StatusDescription
pero luego base.ExecuteResult(context)
renderizaré la vista adecuada porque de nuevo derivaré de ViewResult
. ¿Es bastante simple? Espero que esto se implemente en el núcleo MVC.
Mira mi BaseController
bramido:
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);
}
}
}
}
Para usar en su acción de esta manera:
public ActionResult Index()
{
// Some processing
if (...)
return HttpNotFound();
// Other processing
}
Y en _Layout.cshtml (como página maestra)
<div class="content">
@if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>@ViewBag.Message</p></div>
}
@RenderBody()
</div>
Además, puede usar una vista personalizada como Error.shtml
o crear una nueva NotFound.cshtml
como comenté en el código y puede definir un modelo de vista para la descripción del estado y otras explicaciones.