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

432

Estoy usando RC2

Uso de enrutamiento de URL:

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

Lo anterior parece ocuparse de solicitudes como esta (suponiendo que las tablas de ruta predeterminadas estén configuradas por el proyecto MVC inicial): "/ blah / blah / blah / blah"

Anulación de HandleUnknownAction () en el controlador en sí:

// 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 manejan una solicitud a un controlador incorrecto / desconocido. Por ejemplo, no tengo un "/ IDoNotExist", si lo solicito, obtengo la página genérica 404 del servidor web y no mi 404 si uso enrutamiento + anulación.

Entonces, finalmente, mi pregunta es: ¿hay alguna forma de atrapar este tipo de solicitud utilizando una ruta u otra cosa en el marco MVC?

¿O debería simplemente usar Web.Config customErrors como mi controlador 404 y olvidar todo esto? Supongo que si voy con customErrors tendré que almacenar la página 404 genérica fuera de / Vistas debido a las restricciones de Web.Config en el acceso directo.

Brian
fuente
3
Es un error 404, simplemente no me molestaría. deje que muestre 404. como definitivamente el usuario escribió mal algo. o si es algo que se movió, entonces su aplicación debe tomar esa solicitud y redirigirla de manera permanente. 404 pertenece al servidor web, no a la aplicación. Siempre puede personalizar sus páginas por error.
mamu
puedes echar un vistazo a esta solución también blog.dantup.com/2009/04/…
Desarrollador el
ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages también tiene buena información
Chris S
44
Es una lástima que 4 lanzamientos estables más tarde y más de 5 años después, la situación para manejar 404 en asp.net MVC + IIS no haya mejorado realmente y este sigue siendo el Q&A para saber cómo manejarlo.
joelmdev

Respuestas:

271

El código está tomado de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx y funciona en ASP.net MVC 1.0 también

Así es como manejo las excepciones http:

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));
}
Shay Jacoby
fuente
23
actualización: definitivamente se requiere verificar un http 404, pero todavía no estoy seguro de cuándo obtendrías un 500. También debes configurar explícitamente Response.StatusCode = 404 o 500, de lo contrario, Google comenzará a indexar estas páginas si está devolviendo un código de estado 200 que este código hace actualmente
Simon_Weaver
66
@Simon_Weaver: de acuerdo! Esto necesita devolver códigos de estado 404. Esencialmente se rompe como una solución 404 hasta que lo hace. Mira esto: codinghorror.com/blog/2007/03/…
Matt Kocaj
1
Hay una falla subyacente con toda esta sugerencia: en el momento en que la ejecución ha aumentado hasta Global.asax, falta demasiado HttpContext. No puede volver a sus controladores como sugiere el ejemplo. Consulte los comentarios en el enlace del blog en la parte superior.
Matt Kocaj
3
Como algunos de los comentarios anteriores y la publicación vinculada mencionan, esto no parece funcionar. Se golpea el controlador de error, pero vuelve una pantalla en blanco. (usando mvc 3)
RyanW
44
algo no se siente bien, todo el propósito de MVC es eliminar toda esa abstracción y, sin embargo, aquí está de nuevo ...
Alex Nolasco
255

Requisitos para 404

Los siguientes son mis requisitos para una solución 404 y a continuación muestro cómo lo implemento:

  • Quiero manejar rutas coincidentes con malas acciones
  • Quiero manejar rutas coincidentes con malos controladores
  • Quiero manejar rutas no coincidentes (URL arbitrarias que mi aplicación no puede entender): no quiero que estas aumenten a Global.asax o IIS porque no puedo redirigir nuevamente a mi aplicación MVC correctamente
  • Quiero una forma de manejar de la misma manera que anteriormente, los 404 personalizados, como cuando se envía una ID para un objeto que no existe (tal vez eliminado)
  • Quiero que todos mis 404 para devolver una vista MVC (no es una página estática) a la 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 debería ahorrar Application_Erroren Global.asax para cosas más altas, como excepciones y registros no controlados (como muestra la respuesta de Shay Jacoby ) pero no manejo 404. Es por eso que mi sugerencia mantiene las cosas 404 fuera del archivo Global.asax.

Paso 1: tenga un lugar común para la lógica de error 404

Esta es una buena idea para la mantenibilidad. Utilice un ErrorController para que las futuras mejoras a su página 404 bien diseñada puedan adaptarse fácilmente. Además, ¡ asegúrese de que su respuesta tenga el código 404 !

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 una clase de controlador base para que pueda invocar fácilmente su acción 404 personalizada y conectarse HandleUnknownAction

Los 404 en ASP.NET MVC deben capturarse en varios lugares. La primera es HandleUnknownAction.

El InvokeHttp404método crea un lugar común para redirigir a ErrorControllernuestra nueva Http404acción. Piensa 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: use la inyección de dependencia en su fábrica de controladores y conecte 404 HttpExceptions

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 detectar los errores más cerca de donde se originan. Es por eso que prefiero lo anterior al Application_Errorcontrolador.

Este es el segundo lugar para atrapar 404s.

Paso 4: agregue una ruta NotFound a Global.asax para las URL que no se analizan en su aplicación

Esta ruta debe apuntar a nuestra Http404acción. ¿Te urldas cuenta de que el parámetro será una URL relativa porque el motor de enrutamiento está quitando la parte del dominio aquí? Es por eso que tenemos toda esa lógica de URL condicional en el Paso 1.

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

Este es el tercer y último lugar para atrapar 404 en una aplicación MVC que no invocas tú mismo. Si no detecta rutas inigualables aquí, MVC pasará el problema a ASP.NET (Global.asax) y realmente no quiere eso en esta situación.

Paso 5: finalmente, invoque 404s cuando su aplicación no pueda encontrar algo

Como cuando se envía una identificación incorrecta a mi controlador de préstamos (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 se pudiera conectar en menos lugares con menos código, pero creo que esta solución es más fácil de mantener, más comprobable y bastante pragmática.

Gracias por los comentarios hasta ahora. Me encantaría tener más.

NOTA: Esto ha sido editado significativamente de mi respuesta original pero el propósito / requisitos son los mismos, es por eso que no he agregado una nueva respuesta

Matt Kocaj
fuente
12
Gracias por el artículo completo. Una adición es que cuando se ejecuta bajo IIS7 necesita agregar establecer la propiedad "TrySkipIisCustomErrors" a verdadero. De lo contrario, IIS seguirá devolviendo la página 404 predeterminada. Agregamos Response. TrySkipIiisCustomErrors = true; después de la línea en el Paso 5 que establece el código de estado. msdn.microsoft.com/en-us/library/…
Rick
1
@Ryan La customErrorssección de web.config define páginas de redireccionamiento estático que se manejan a un alto nivel en aspnet, si no en IIS. Esto no es lo que quería, ya que necesitaba que el resultado fuera MVC Views (para que pueda tener datos en ellos, etc.). No diría categóricamente que " customErrorsestá obsoleto en MVC", pero para mí y para esta solución 404 ciertamente lo son.
Matt Kocaj
1
Además, ¿alguien puede actualizar el Paso 3 para que no se use StructureMap? Tal vez solo un ControllerFactory genérico que será fácil de implementar si aún no está usando un ControllerFactory.
David Murdoch
77
Esto funciona bien para MVC3. En cambio, ObjectFactory.GetInstancecambié a MVC3, DependencyResolver.Current.GetServiceasí que es más genérico. Estoy usando Ninject.
kamranicus
122
¿Alguien más encuentra patentemente loco que algo tan común como los 404 en un marco web sea tan sangrientamente complicado?
quentin-starin
235

ASP.NET MVC no admite páginas 404 personalizadas muy bien. Fábrica de controladores personalizados, ruta general, clase de controlador base con HandleUnknownAction- ¡argh!

Las páginas de error personalizadas de IIS son una mejor alternativa hasta ahora:

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 muestra

Pavel Chuchuva
fuente
38
¡ESTO DEBE HABER SIDO LA RESPUESTA ACEPTADA! Funciona excelente en ASP.NET MVC 3 con IIS Express.
Andrei Rînea
77
Si está utilizando IIS7 +, este es definitivamente el camino a seguir. +1!
elo80ka
3
¿Es posible devolver solo un estado 404 cuando está trabajando en JSON dentro del mismo proyecto?
VinnyG
66
Esto funciona muy bien en iis express, pero tan pronto como implemento el sitio en producción IIS 7.5, todo lo que obtengo es una página en blanco en lugar de la vista de error.
Moulde
2
Según mis pruebas (con MVC3), esto se rompe customErrors mode="On"junto con el HandleErrorAttributeser funcional. Las páginas de error personalizadas para excepciones no controladas en acciones del controlador ya no se sirven.
Slauma
153

Respuesta rápida / TL; DR

ingrese la descripción de la imagen aquí

Para la gente perezosa por ahí:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Luego elimine esta línea de global.asax

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

Y esto es solo para IIS7 + e IIS Express.

Si estás usando Cassini ... bueno ... um ... er ... incómodo ... torpe


Respuesta larga y explicada

Sé que esto ha sido respondido. Pero la respuesta es REALMENTE SIMPLE (aplausos para David Fowler y Damian Edwards por responder realmente esto).

No hay necesidad de hacer nada personalizado .

Para ASP.NET MVC3, todos los pedazos están ahí.

Paso 1 -> Actualiza tu web.config en DOS lugares.

<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 nota de las RUTAS que he decidido utilizar. Puedes usar cualquier cosa, pero mis rutas son

  • /NotFound <- para un 404 no encontrado, página de error.
  • /ServerError<- para cualquier otro error, incluya los errores que suceden en mi código. este es un error interno del servidor 500

¿Ves cómo la primera sección <system.web>solo tiene una entrada personalizada? La statusCode="404"entrada? Solo he enumerado un código de estado porque todos los demás errores, incluido el 500 Server Error(es decir, esos molestos errores que ocurren cuando su código tiene un error y bloquea la solicitud del usuario) ... todos los otros errores son manejados por la configuración defaultRedirect="/ServerError"... que dice , si no se encuentra una página 404, vaya a la ruta /ServerError.

Okay. eso está fuera del camino ... ahora a mis rutas enumeradas englobal.asax

Paso 2: crear las rutas en Global.asax

Aquí está mi sección de ruta completa ...

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

Eso enumera dos rutas de ignorar -> axd'sy favicons(¡ooo! Bonus ignora la ruta, ¡para ti!) Luego (y el pedido es IMPERATIVO AQUÍ), tengo mis dos rutas explícitas de manejo de errores ... seguidas de cualquier otra ruta. En este caso, el predeterminado. Por supuesto, tengo más, pero eso es especial para mi sitio web. Solo asegúrese de que las rutas de error estén en la parte superior de la lista. El orden es imperativo .

Finalmente, mientras estamos dentro de nuestro global.asaxarchivo, NO registramos globalmente el atributo HandleError. No, no, no señor. Nadda No Nien Negativo. Noooooooooo ...

Eliminar esta línea de global.asax

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

Paso 3: crea el controlador con los métodos de acción

Ahora ... agregamos 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, veamos esto. En primer lugar, NO hay [HandleError] atributo aquí. ¿Por qué? Debido a que el ASP.NETmarco integrado ya está manejando errores Y hemos especificado toda la mierda que debemos hacer para manejar un error :) ¡Está en este método!

A continuación, tengo los dos métodos de acción. Nada duro allí. Si desea mostrar alguna información de excepción, puede usarla Server.GetLastError()para obtener esa información.

Bonificación WTF: Sí, hice un tercer método de acción, para probar el manejo de errores.

Paso 4: crea las vistas

Y finalmente, crea dos vistas. Póngalos en el punto de vista normal, para este controlador.

ingrese la descripción de la imagen aquí

Comentarios adicionales

  • No necesitas un Application_Error(object sender, EventArgs e)
  • Todos los pasos anteriores funcionan 100% perfectamente con Elmah . Elmah fraking wroxs!

Y eso, mis amigos, debería ser.

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

ingrese la descripción de la imagen aquí

Pure.Krome
fuente
Así que traté de implementar esto, pero un par de problemas ... primero, necesitas un ~ antes de la ruta en weeb.config o no funciona para directorios virtuales. 2-Si se activan los errores personalizados de IIS y la vista está utilizando un diseño que no representa en absoluto, solo una página en blanco. Lo resolví agregando esta línea en el controlador "Response. TrySkipIisCustomErrors = true;" . Sin embargo, aún no funciona si va a una url que es un archivo pero 404 .. como mysite / whatever / fake.html obtiene una página en blanco.
Robert Noack
3
-1, lo siento, para mí cualquier solución que cambie la URL para 404 está mal. y con webconfig no hay forma en MVC de que pueda manejarlo sin cambiar la url, o necesita crear archivos html estáticos o aspx (sí, archivos aspx viejos) para poder hacerlo. su solución está bien si le gusta ?aspxerrorpath=/er/not/foundtener en URL.
Gutek
77
Esto podría suena muy raro - pero no se proporcionó mi respuesta hace años y estoy de acuerdo con su @Gutek, no me gusta hacer una redirección a una página de error más . Solía ​​(ref. A mi respuesta: P). Si el error ocurrió en / some / resource ... entonces ESE recurso debería devolver un 404 o 500, etc. Implicaciones masivas de SEO de lo contrario. Ahh ... cómo cambian los tiempos :)
Pure.Krome
@Gutek ¿Conoces customErrors redirectMode = "ResponseRewrite"? Y devolver 404 no es ideal desde una perspectiva de seguridad
Jowen,
1
@ Chris <inserta tu deidad favorita aquí> maldita sea. Ni siquiera puedo recordar lo que era ahora. Bueno, mi colección de memes al rescate ... y ... arreglada.
Pure.Krome
86

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

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

Errores Controlador:

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 casos diferentes que una aplicación ASP.NET MVC3 puede generar 404.

(Generado automáticamente por ASP.NET Framework :)

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

(Generado automáticamente por ASP.NET MVC Framework :)

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

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

(Generado manualmente :)

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

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

(6) Una acción modifica manualmente la propiedad Response.StatusCode a 404.

Normalmente, quieres lograr 3 objetivos:

(1) Mostrar una página de error 404 personalizada al usuario.

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

(3) Enviar la respuesta directamente, sin involucrar una redirección 302.

Hay varias formas de tratar 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 (1) en los casos (1), (4), (6).
  2. No cumple con el objetivo (2) automáticamente. Debe ser programado manualmente.
  3. No cumple 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. Solo funciona en IIS 7+.
  2. No cumple con el objetivo (1) en los casos (2), (3), (5).
  3. No cumple con el objetivo (2) automáticamente. Debe ser programado 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. Solo funciona en IIS 7+.
  2. No cumple con el objetivo (2) automáticamente. Debe ser programado manualmente.
  3. Oculta las excepciones http de nivel de aplicación. Por ejemplo, no puede usar la sección customErrors, System.Web.Mvc.HandleErrorAttribute, etc. No solo puede mostrar páginas de error genéricas.

(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. Solo funciona en IIS 7+.
  2. No cumple con el objetivo (2) automáticamente. Debe ser programado manualmente.
  3. No cumple con el objetivo (3) en los casos (2), (3), (5).

Las personas que han tenido problemas con esto antes incluso intentaron crear sus propias bibliotecas (consulte http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Pero la solución anterior parece cubrir todos los casos sin la complejidad de usar una biblioteca externa.

Marco
fuente
Gran respuesta. Digno de muchos más votos a favor. ¿Por qué su código global.asax no funciona / pertenece en Application_Error?
NinjaNye
77
¡Gracias! No se puede hacer bajo Application_Error, porque los 404 explícitos lanzados desde un controlador no se consideran errores en ASP.NET. Si devuelve un HttpNotFound () desde un controlador, el evento Application_Error nunca se activará.
Marco
1
Creo que te olvidaste public ActionResult NotFound() {}en tu ErrorController. Además, ¿puede explicar cómo _NotFoundse vería su parcial para las solicitudes AJAX?
d4n3
2
Con MVC 4 siempre estoy MissingMethodException: Cannot create an abstract classen línea ¿ c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Alguna idea?
µBio
1
Si la URL "no encontrada" incluye un punto en la ruta (por ejemplo, example.com/hi.bob ), entonces Application_EndRequest no se activa en absoluto, y obtengo la página 404 genérica de IE.
Bob.at.Indigo.Health
13

Realmente me gusta la solución cottsaks y creo que se explica muy claramente. mi única adición fue alterar el paso 2 de la siguiente manera

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
}

Básicamente, esto evita que las URL que contienen acciones no válidas Y los controladores activen la rutina de excepción dos veces. por ejemplo, para URL como asdfsdf / dfgdfgd

Dave Lowe
fuente
44
Esto es excelente. Esos "dos" casos comenzaron a molestarme. actualicé mi respuesta
Matt Kocaj el
¿funciona la solución anterior si el usuario ingresa un controlador y nombre de acción incorrectos?
Monojit Sarkar
6

La única forma en que podía hacer que el método de @ cottsak funcionara para controladores no válidos era modificar la solicitud de ruta existente en CustomControllerFactory, de esta manera:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

Debo mencionar que estoy usando MVC 2.0.

Dave K
fuente
¿Sabes por qué? (¿MVC2 específico?)
Matt Kocaj
Creo que la clave era modificar la solicitud existente en lugar de hacer una nueva, pero lo hice hace un tiempo, así que no estoy seguro de que fuera así. El "InvokeHttp404" no funcionaba desde la fábrica de controladores.
Dave K
Actualicé mi respuesta hoy con algunos detalles de MVC2. ¿Puede decirme si mi solución como se detalla anteriormente aún no funciona para usted?
Matt Kocaj
4

Aquí hay otro método que utiliza herramientas MVC que puede manejar solicitudes de nombres de controlador incorrectos, nombres de ruta incorrectos y cualquier otro criterio que considere adecuado dentro de un método de Acción. Personalmente, prefiero evitar tantas configuraciones web.config como sea posible, ya que hacen la redirección 302/200 y no admiten ResponseRewrite (Server.Transfer ) usando las vistas Razor. Prefiero devolver un 404 con una página de error personalizada por razones de SEO.

Algo de esto es una nueva versión de la técnica de Cottsak anterior.

Esta solución también utiliza una configuración mínima de web.config que favorece los filtros de error MVC 3.

Uso

Simplemente arroje una HttpException desde una acción o ActionFilterAttribute personalizado.

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

Paso 1

Agregue la siguiente configuración a su web.config. Esto es necesario para usar HandleErrorAttribute de MVC.

<customErrors mode="On" redirectMode="ResponseRedirect" />

Paso 2

Agregue un HandleHttpErrorAttribute personalizado similar al HandleErrorAttribute del marco MVC, a excepción de los errores HTTP:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class

Paso 3

Agregue filtros a GlobalFilterCollection ( GlobalFilters.Filters) en Global.asax. Este ejemplo enrutará todos los errores de InternalServerError (500) a la vista compartida Error ( Views/Shared/Error.vbhtml). Los errores NotFound (404) se enviarán también a ErrorHttp404.vbhtml en las vistas compartidas. He agregado un error 401 aquí para mostrarle cómo se puede extender esto para códigos de error HTTP adicionales. Tenga en cuenta que estas deben ser vistas compartidas, y todas usan el System.Web.Mvc.HandleErrorInfoobjeto como modelo.

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})

Paso 4

Cree una clase de controlador base y herede de ella en sus controladores. Este paso nos permite manejar nombres de acciones desconocidos y elevar el error HTTP 404 a nuestro HandleHttpErrorAttribute.

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class

Paso 5

Cree una anulación ControllerFactory y anúlela en su archivo Global.asax en Application_Start. Este paso nos permite generar la excepción HTTP 404 cuando se ha especificado un nombre de controlador no válido.

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)

Paso 6

Incluya una ruta especial en su RoutTable.Routes para la acción Desconocida de BaseController. Esto nos ayudará a generar un 404 en el caso de que un usuario acceda a un controlador desconocido o acción desconocida.

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)

Resumen

Este ejemplo demostró cómo se puede usar el marco MVC para devolver códigos de error 404 Http al navegador sin una redirección utilizando atributos de filtro y vistas de error compartidas. También demuestra que muestra la misma página de error personalizada cuando se especifican nombres de controlador y nombres de acción no válidos.

Agregaré una captura de pantalla de un nombre de controlador no válido, un nombre de acción y un 404 personalizado generado desde la acción Inicio / TriggerNotFound si obtengo suficientes votos para publicar uno =). Fiddler devuelve un mensaje 404 cuando accedo a las siguientes URL con esta solución:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

La publicación de Cottsak anterior y estos artículos fueron buenas referencias.

sky-dev
fuente
Hmm, no pude hacer que esto funcione: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.¿alguna idea de por qué iba a conseguir eso?
enashnash
redirectMode = "ResponseRedirect". ¡Esto devolverá un 302 encontrado + un 200 OK que no es bueno para SEO!
PussInBoots
4

Mi solución abreviada que funciona con áreas no controladas, controladores y acciones:

  1. Cree una vista 404.cshtml.

  2. Cree una clase base para sus controladores:

    public class Controller : System.Web.Mvc.Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            Http404().ExecuteResult(ControllerContext);
        }
    
        protected virtual ViewResult Http404()
        {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("404");
        }
    }
  3. Cree una fábrica de controladores personalizados que devuelva su controlador base como respaldo:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Agregue a Application_Start()la siguiente línea:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
Herman Kan
fuente
3

En MVC4 WebAPI 404 se puede manejar de la siguiente manera,

CURSOS APICONTROLADOR

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

CONTROLADOR DEL HOGAR

public ActionResult Course(int id)
{
    return View(id);
}

VER

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

GLOBAL

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

RESULTADOS

ingrese la descripción de la imagen aquí

Diganta Kumar
fuente
2

Prueba NotFoundMVC en nuget. Funciona, sin configuración.

Darth Vader
fuente
http://localhost/Views/Shared/NotFound.cshtmlno da como resultado una página 404 personalizada.
Dan Friedman
Es muy fácil de personalizar. Tiene acceso a la URL solicitada y al referente, para que pueda hacer lo que quiera. Yo uso este paquete, y funciona muy bien.
Avrohom Yisroel
Este es un gran paquete, siempre que no utilice acciones asíncronas de la tarea <ActionResult> (u otras acciones asíncronas similares). En MVC 5 este es un escenario roto. Hay un tenedor en GitHub para evitar eso, pero para mí es un no, no.
Stargazer
2

Mi solución, en caso de que alguien lo encuentre útil.

En Web.config:

<system.web>
    <customErrors mode="On" defaultRedirect="Error" >
      <error statusCode="404" redirect="~/Error/PageNotFound"/>
    </customErrors>
    ...
</system.web>

En Controllers/ErrorController.cs:

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

Agregue un PageNotFound.cshtmlen la Sharedcarpeta, y eso es todo.

Konamiman
fuente
2
¿No emite esto un redireccionamiento 302 y luego un estado 200 (OK) al cliente? ¿No deberían seguir obteniendo un estado 404?
Sam
@Konamiman ¿Está seguro de que la línea en su código debería leer model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;y no model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(y en lugar de &&)?
Jean-François Beauchamp
2

Sin embargo, me parece que la CustomErrorsconfiguración estándar debería funcionar , debido a la dependencia de Server.Transferque parece que la implementación interna deResponseRewrite no es compatible con MVC.

Esto se siente como un agujero de funcionalidad deslumbrante para mí, así que decidí volver a implementar esta característica usando un módulo HTTP. La solución a continuación le permite manejar cualquier código de estado HTTP (incluido 404) al redirigir a cualquier ruta MVC válida como lo haría normalmente.

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

Esto ha sido probado en las siguientes plataformas;

  • MVC4 en modo de canalización integrado (IIS Express 8)
  • MVC4 en modo clásico (VS Development Server, Cassini)
  • MVC4 en modo clásico (IIS6)

Beneficios

  • Solución genérica que se puede colocar en cualquier proyecto MVC
  • Permite el soporte para la configuración tradicional de errores personalizados
  • Funciona tanto en modo de tubería integrada como en modo clásico.

La solución

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Uso

Incluya esto como el último módulo HTTP en su web.config

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

Para aquellos de ustedes que estén prestando atención, notarán que en el modo Integrated Pipeline esto siempre responderá con HTTP 200 debido a la forma en que Server.TransferRequestfunciona. Para devolver el código de error adecuado, uso el siguiente controlador de error.

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}
Taz rojo
fuente
2

Tratar los errores en ASP.NET MVC es solo una molestia. Intenté muchas sugerencias en esta página y en otras preguntas y sitios, y nada funciona bien. Una sugerencia fue manejar errores en web.config dentro de system.webserver pero eso solo devuelve páginas en blanco .

Mi objetivo al encontrar esta solución era;

  • NO REDIRECER
  • Devuelve CÓDIGOS DE ESTADO APROPIADOS no 200 / Ok, como el manejo de errores predeterminado

Aquí está mi solución.

1. Agregue lo siguiente a la sección system.web

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

Lo anterior maneja cualquier URL no manejada por routes.config y excepciones no manejadas, especialmente aquellas encontradas en las vistas. Tenga en cuenta que usé aspx, no html . Esto es para que pueda agregar un código de respuesta en el código detrás.

2 . Cree una carpeta llamada Error (o lo que prefiera) en la raíz de su proyecto y agregue los dos formularios web. Debajo está mi página 404;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

Y en el código detrás establecí el código de respuesta

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Haz lo mismo para la página 500

3. Para manejar errores dentro de los controladores. Hay muchas formas de hacerlo. Esto es lo que funcionó para mí. Todos mis controladores heredan de un controlador base. En el controlador base, tengo los siguientes métodos

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4. Agregue CustomError.cshtml a su carpeta Vistas compartidas . Debajo está el mío;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Ahora en su controlador de aplicación puede hacer algo como esto;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Ahora para la advertencia . No manejará errores de archivos estáticos. Entonces, si tiene una ruta como example.com/widgets y el usuario la cambia a example.com/widgets.html , obtendrá la página de error predeterminada de IIS, por lo que debe manejar los errores de nivel de IIS de otra manera.

Moses Machua
fuente
1

Publicando una respuesta ya que mi comentario fue demasiado largo ...

Es tanto un comentario como preguntas para la publicación / respuesta del unicornio:

https://stackoverflow.com/a/7499406/687549

Prefiero esta respuesta sobre las otras por su simplicidad y el hecho de que aparentemente se consultó a algunas personas en Microsoft. Sin embargo, obtuve tres preguntas y si pueden responderse, llamaré a esta respuesta el santo grial de todas las respuestas de error 404/500 en las interwebs para una aplicación ASP.NET MVC (x).

@ Pure.Krome

  1. ¿Puede actualizar su respuesta con el material de SEO de los comentarios señalados por GWB (nunca se mencionó esto en su respuesta) <customErrors mode="On" redirectMode="ResponseRewrite">y <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. ¿Pueden preguntarle a sus amigos del equipo ASP.NET si está bien hacerlo así? Sería bueno tener alguna confirmación. ¿Tal vez sea un gran no-no cambiar redirectModey existingResponsede esta manera poder jugar bien con SEO?

  3. Se puede añadir algunas aclaraciones que rodea todo eso ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", ELIMINAR customErrorsTOTALMENTE como alguien sugirió) después de hablar con sus amigos en Microsoft?

Como decía; Sería estupendo si pudiéramos hacer que su respuesta sea más completa, ya que esta parece ser una pregunta bastante popular con más de 54 000 visitas.

Actualización : La respuesta de Unicornio hace un 302 encontrado y un 200 OK y no se puede cambiar para que solo devuelva 404 usando una ruta. Tiene que ser un archivo físico que no sea muy MVC: ish. Entonces, pasar a otra solución. Lástima porque este parecía ser el MVC definitivo: esta es la respuesta hasta ahora.

El gato con botas
fuente
1

Agregué mi solución, que es casi idéntica a la de Herman Kan, con una pequeña arruga para que funcione para mi proyecto.

Cree un controlador de error personalizado:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Luego cree una fábrica de controladores personalizados:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Finalmente, agregue una anulación al controlador de error personalizado:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

Y eso es. No es necesario realizar cambios en Web.config.

Rob Lyndon
fuente
1

1) Hacer una clase abstracta de controlador.

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2) Haga la herencia de esta clase abstracta en todos sus controladores

public class HomeController : MyController
{}  

3) Y agregue una vista llamada "NotFound" en su carpeta View-Shared.

Mehmet
fuente
0

Revisé la mayoría de las soluciones publicadas en este hilo. Si bien esta pregunta puede ser antigua, aún es muy aplicable a nuevos proyectos, incluso ahora, por lo que pasé mucho tiempo leyendo las respuestas presentadas aquí y en otros lugares.

Como @Marco señaló los diferentes casos bajo los cuales puede ocurrir un 404, verifiqué la solución que compilé en esa lista. Además de su lista de requisitos, también agregué uno más.

  • La solución debería poder manejar las llamadas MVC y AJAX / WebAPI de la manera más adecuada. (es decir, si 404 ocurre en MVC, debería mostrar la página No encontrado y si 404 ocurre en WebAPI, no debería secuestrar la respuesta XML / JSON para que el Javascript consumidor pueda analizarla fácilmente).

Esta solución es doble:

La primera parte proviene de @Guillaume en https://stackoverflow.com/a/27354140/2310818 . Su solución se encarga de cualquier 404 causado por una ruta no válida, un controlador no válido y una acción no válida.

La idea es crear un formulario web y luego hacer que llame a la acción NotFound de su controlador de errores MVC. Hace todo esto sin ningún redireccionamiento, por lo que no verá un solo 302 en Fiddler. La URL original también se conserva, lo que hace que esta solución sea fantástica.


La segunda parte proviene de @ Germán en https://stackoverflow.com/a/5536676/2310818 . ¡Su solución se encarga de cualquier 404 devuelto por sus acciones en forma de HttpNotFoundResult () o arroja una nueva HttpException ()!

La idea es tener un filtro que mire la respuesta, así como la excepción lanzada por sus controladores MVC y llamar a la acción apropiada en su controlador de errores. De nuevo, esta solución funciona sin redireccionamiento y se conserva la URL original.


Como puede ver, ambas soluciones juntas ofrecen un mecanismo de manejo de errores muy robusto y cumplen todos los requisitos enumerados por @Marco, así como mis requisitos. Si desea ver una muestra de trabajo o una demostración de esta solución, por favor deje en los comentarios y me complacería reunirla.

Parth Shah
fuente
0

He revisado todos los artículos, pero nada funciona para mí: mi usuario de requisito debe escribir algo en su página 404 personalizada de URL debe mostrar. Pensé que es muy sencillo. Pero debe comprender el manejo del 404 correctamente:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Este artículo me pareció muy útil. Debería leerlo de inmediato. Página de error de Custome-Ben Foster

Roca Thakur
fuente