433 votos

¿Cómo puedo manejar adecuadamente 404 en ASP.NET MVC?

Yo estoy empezando en ASP.NET MVC así que tengan paciencia conmigo. He buscado por este sitio, y otros varios y he visto un par de implementaciones de este.

EDIT: me olvidé de mencionar que estoy usando RC2

Utiliza la URL de Enrutamiento:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

El de arriba parece tener la atención de las solicitudes como esto (suponiendo que la ruta por defecto de tablas de configuración inicial proyecto MVC): "/bla/bla/bla/bla"

Primordial HandleUnknownAction() en el controlador de sí mismo:

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

Sin embargo las estrategias anteriores no tratar una solicitud a una Mala/controlador controlador Desconocido. Por ejemplo, yo no tengo un "/IDoNotExist", si puedo pedir este me sale el genérico 404 de página desde el servidor web y no a mi 404 si puedo usar enrutamiento + override.

Así que, finalmente, mi pregunta es: hay alguna forma de detectar este tipo de solicitud de uso de una ruta o algo más en el MVC framework?

O debo predeterminado para el uso de Web.Config customErrors como mi 404 controlador y olvidarse de todo esto? Supongo que si voy con customErrors voy a tener que almacenar los genéricos página de error 404 fuera de /Vistas debido a la Web.Config restricciones en el acceso directo. De todos modos cualquiera de las mejores prácticas o guías que se aprecia.

272voto

Shay Jacoby Puntos 1819

El código se toma de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx y trabaja en ASP.net MVC 1.0 así

He aquí cómo puedo manejar http excepciones:

protected void Application_Error(object sender, EventArgs e)
{
   Exception exception = Server.GetLastError();
   // Log the exception.

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }           

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}

255voto

cottsak Puntos 5490

Requisitos para 404

Las siguientes son mis requisitos para un 404 solución y a continuación le muestro cómo puedo implementar:

  • Quiero manejar las rutas de emparejado con malas acciones
  • Quiero manejar las rutas de emparejado con mala controladores
  • Quiero manejar onu-rutas de emparejado (arbitrario url que mi aplicación no puede entender) - no quiero que estas burbujas hasta el Global.asax o IIS, porque entonces yo no puedo redirigir de nuevo en mi aplicación MVC correctamente
  • Yo quiero una manera de manejar de la misma manera como en el anterior, personalizada 404 - como cuando un IDENTIFICADOR es enviado por un objeto que no existe (tal vez eliminados))
  • Quiero que todos mis 404 para volver a MVC la vista (no es una página estática) para que yo pueda bombear más datos más adelante si es necesario (bueno 404 diseños) y que debe devolver el código de estado HTTP 404

Solución

Creo que se debe guardar Application_Error en el Global.asax para cosas más altas, como las excepciones no controladas y de registro (como Shay Jacoby de la respuesta de la muestra), pero no 404 manejo. Esta es la razón por la que mi sugerencia mantiene el 404 cosas de el archivo Global.asax.

Paso 1: Tener un lugar común para los 404-error de lógica

Esta es una buena idea para el mantenimiento. El uso de un ErrorController para que las futuras mejoras para su bien diseñada página de error 404 se puede adaptar fácilmente. También, asegúrese de que su respuesta tiene el 404 del código!

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Paso 2: Utilice un Controlador de base de la clase por lo que fácilmente puede invocar su 404 personalizado de acción y alambre HandleUnknownAction

404 en ASP.NET MVC necesidad de ser capturado en un número de lugares. La primera es HandleUnknownAction.

La InvokeHttp404 método crea un lugar común para re-enrutamiento a las ErrorController y nuestro nuevo Http404 acción. Pienso SECO!

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Paso 3: Uso de la Inyección de Dependencia en el Controlador de la Fábrica y el alambre hasta 404 HttpExceptions

Como así (no tiene que ser StructureMap):

MVC1.0 ejemplo:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

MVC2.0 ejemplo:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

Creo que es mejor para detectar los errores más cerca de donde se originan. Esta es la razón por la que yo prefiero la de arriba a la Application_Error cuidador.

Este es el segundo lugar para tomar 404.

Paso 4: Añadir NotFound ruta al archivo Global.asax para las urls que no se analiza en su aplicación

Esta ruta debe apuntar a nuestro Http404 acción. Aviso de la url param será una dirección url relativa debido a que el motor de enrutamiento está privando a la parte de dominio aquí? Es por eso que tenemos todos los que condicional url de lógica en el Paso 1.

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

Este es el tercer y último lugar para tomar el 404 en una aplicación MVC que no se invoca a sí mismo. Si no coges inigualable rutas de aquí, a continuación, MVC pasará el problema ASP.NET (Global.asax) y usted realmente no quiere que en esta situación.

Paso 5: por último, invocar 404 cuando la aplicación no puede encontrar algo

Como cuando una mala IDENTIFICACIÓN se presenta a mi Préstamos controlador (deriva de MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

Sería bueno si todo esto podría ser conectado en menos lugares con menos código, pero creo que esta solución es más fácil de mantener, más comprobables y bastante pragmático.

Gracias por los comentarios hasta el momento. Me gustaría obtener más.

NOTA: Esto ha sido editado de manera significativa a partir de mi respuesta original, pero el propósito y los requisitos son los mismos, es por esta razón no he añadido una nueva respuesta

236voto

Pavel Chuchuva Puntos 12220

ASP.NET MVC no admite 404 personalizado de páginas muy bien. Personalizadas del controlador de la fábrica, catch-all de la ruta, la base de la clase de controlador con HandleUnknownAction - argh!

IIS páginas de error personalizadas son la mejor alternativa hasta el momento:

web.config

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
  </httpErrors>
</system.webServer>

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

Proyecto De Ejemplo

Test404.zip

153voto

Pure.Krome Puntos 28473

Respuesta rápida / TL;DR

enter image description here

Para los perezosos chicos por ahí:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

A continuación, retire esta línea de global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Y esto es sólo para IIS7+ e IIS Express.

Si estás usando Cassini .. bueno .. um .. er.. torpe ...


Larga, explicó respuesta

Sé que esto ha sido contestado. Pero la respuesta es MUY SIMPLE (saludos a David Fowler y Damian Edwards para realmente responder a esta).

No hay ninguna necesidad de hacer nada personalizado.

Para ASP.NET MVC3, todas las piezas están ahí.

Paso 1 -> Actualización de su web.config en DOS puntos.

<system.web>
    <customErrors mode="On" defaultRedirect="/ServerError">
      <error statusCode="404" redirect="/NotFound" />
    </customErrors>

y

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

Ahora tome atenta nota de las RUTAS que he decidido usar. Usted puede usar cualquier cosa, pero mis rutas son

  • /NotFound <- para un 404 no encontrado, la página de error.
  • /ServerError <- para cualquier otro tipo de error, incluir errores que suceden en mi código. este es un Error Interno del Servidor 500

Ver cómo la primera sección en <system.web> sólo tiene una entrada personalizada? La statusCode="404" entrada? Yo sólo he enumerado un código de estado porque todos los demás errores, incluyendo el 500 Server Error (es decir. esos molestos de error que se produce cuando el código tiene un error y se bloquea la petición del usuario) .. todos los otros errores son manejados por el valor defaultRedirect="/ServerError" .. que dice, si usted no es un error 404 de página no encontrada, haga el favor de ir a la ruta /ServerError.

Aceptar. eso está fuera de la manera.. ahora a mis rutas incluidas en global.asax

Paso 2 - Creación de las rutas en el archivo Global.asax

Aquí está mi recorrido completo de la sección..

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

Que muestra dos ignorar rutas -> axd's y favicons (ooo! bono de ignorar la ruta, para ti!) A continuación (el orden es IMPERATIVO AQUÍ), tengo a mis dos explícita el manejo de errores rutas .. seguido por las otras rutas. En este caso, la de por defecto. Por supuesto, yo tengo más, pero eso es especial para mi sitio web. Sólo asegúrese de que el error rutas están en la parte superior de la lista. El orden es imperativo.

Finalmente, mientras estamos dentro de nuestro global.asax archivo, NO a nivel mundial registrar el atributo HandleError. No, No, no señor. Nadda. Nope. Nien. Negativo. Noooooooooo...

Eliminar esta línea de global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Paso 3 - Crear el controlador con los métodos de acción

Ahora .. le agregue un controlador con dos métodos de acción ...

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

Ok, vamos a comprobar esto. Primero de todo, no hay NINGUNA [HandleError] atributo aquí. ¿Por qué? Debido a que el construido en ASP.NET marco ya es el manejo de errores Y hemos especificado toda la mierda que tenemos que hacer para controlar un error :) en este método!

Siguiente, tengo los dos métodos de acción. Nada dura allí. Si u desea mostrar excepción alguna, información, entonces u puede utilizar Server.GetLastError() para obtener esa información.

Bono WTF: Sí, he hecho un tercer método de acción, a prueba de manejo de errores.

Paso 4 - Crear las Vistas

Y por último, crear dos puntos de vista. Poner em en la vista normal, de punto, para este controlador.

enter image description here

Bono comentarios

  • Usted no necesita un Application_Error(object sender, EventArgs e)
  • Los pasos anteriores todos funcionan en un 100% a la perfección con Elmah. Elmah fraking wroxs!

Y eso, mis amigos, debería ser éste.

Ahora, felicidades por leer mucho y tener un Unicornio como un premio!

enter image description here

86voto

Marco Puntos 2652

He investigado MUCHO sobre cómo gestionar adecuadamente 404 en MVC (específicamente MVC3), y esto, en mi humilde opinión es la mejor solución que he encontrado:

En el archivo global.asax:

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErrorsController:

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

(Opcional)

Explicación:

AFAIK, hay 6 diferentes casos que una ASP.NET MVC3 de aplicaciones puede generar 404.

(Generado automáticamente por el ASP.NET Marco:)

(1) Una URL que no encuentra una coincidencia en la tabla de rutas.

(Generado automáticamente por el ASP.NET Marco de MVC:)

(2) Una URL encuentra una coincidencia en la tabla de rutas, pero especifica un inexistente controlador.

(3) Una URL encuentra una coincidencia en la tabla de rutas, pero especifica un inexistente acción.

(Generado manualmente:)

(4) Una acción devuelve un HttpNotFoundResult mediante el método HttpNotFound().

(5) Una acción produce una HttpException con el código de estado 404.

(6) acciones manualmente, se modifica el Response.StatusCode propiedad 404.

Normalmente, usted quiere lograr 3 objetivos:

(1) Mostrar una página persnoalizada de error 404 para el usuario.

(2) Mantener el código de estado 404 en la respuesta de los clientes (especialmente importante para el SEO).

(3) Mandar la respuesta directamente, sin la intervención de una redirección 302.

Hay varias maneras de lograr esto:

(1)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

Problemas con esta solución:

  1. No cumple con el objetivo de (1) en los casos(1), (4), (6).
  2. No cumplir con el objetivo (2) de forma automática. Debe ser programada manualmente.
  3. No cumplir con el objetivo (3).

(2)

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problemas con esta solución:

  1. Sólo funciona en IIS 7+.
  2. No cumple con el objetivo de (1) en los casos(2), (3), (5).
  3. No cumplir con el objetivo (2) de forma automática. Debe ser programada manualmente.

(3)

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problemas con esta solución:

  1. Sólo funciona en IIS 7+.
  2. No cumplir con el objetivo (2) de forma automática. Debe ser programada manualmente.
  3. Se oscurece el nivel de la aplicación http excepciones. Por ejemplo. no se puede utilizar customErrors sección, System.Web.Mvc.HandleErrorAttribute, etc. No puede mostrar sólo los genéricos páginas de error.

(4)

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

y

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problemas con esta solución:

  1. Sólo funciona en IIS 7+.
  2. No cumplir con el objetivo (2) de forma automática. Debe ser programada manualmente.
  3. No cumplir con el objetivo (3) en los casos(2), (3), (5).

Las personas que han inquietado con esto antes de que incluso trató de crear sus propias bibliotecas (ver http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). Pero la solución anterior, en la que parece abarcar todos los casos, sin la complejidad de uso de una biblioteca externa.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X