Pregunta

estoy usando RC2

Usando enrutamiento 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):"/bla bla bla bla"

Anulando HandleUnknownAction() en el propio controlador:

// 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 solicito esto obtengo la página 404 genérica del servidor web y no mi 404 si uso enrutamiento + anulación.

Finalmente mi pregunta es: ¿Hay alguna forma de detectar este tipo de solicitud utilizando una ruta u otra cosa en el propio marco MVC?

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

¿Fue útil?

Solución

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 también funciona en ASP.net MVC 1.0

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

Otros consejos

Requisitos para 404

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

  • Quiero manejar rutas coincidentes con malas acciones.
  • Quiero manejar rutas coincidentes con controladores incorrectos
  • Quiero manejar rutas no coincidentes (URL arbitrarias que mi aplicación no puede entender); no quiero que aparezcan en Global.asax o IIS porque entonces No puedo redirigir correctamente a mi aplicación MVC
  • Quiero una forma de manejar los 404 personalizados de la misma manera que antes, como cuando se envía una identificación para un objeto que no existe (tal vez eliminado).
  • Quiero que todos mis 404 devuelvan una vista MVC (no una página estática) a la que puedo enviar más datos más adelante si es necesario (buenos diseños 404) y ellos debe devolver el código de estado HTTP 404

Solución

creo que deberías ahorrar Application_Error en Global.asax para cosas superiores, como excepciones no controladas y registros (como La respuesta de Shay Jacoby muestra) pero no manejo 404.Es por eso que mi sugerencia mantiene el material 404 fuera del archivo Global.asax.

Paso 1:Tenga un lugar común para la lógica del error 404

Esta es una buena idea para la mantenibilidad.Utilice un Controlador de errores para que futuras mejoras en tu página 404 bien diseñada puede adaptarse fácilmente.También, 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 detectarse en varios lugares.El primero es HandleUnknownAction.

El InvokeHttp404 El método crea un lugar común para redirigir al ErrorController y nuestro nuevo Http404 acción.Pensar 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:Utilice la inyección de dependencia en su fábrica de controladores y conecte 404 HttpExceptions

Así (no tiene que ser StructureMap):

Ejemplo de MVC1.0:

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

Ejemplo de MVC2.0:

    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.Por eso prefiero lo anterior a lo anterior. Application_Error manipulador.

Este es el segundo lugar para capturar 404.

Etapa 4:Agregue una ruta NotFound a Global.asax para las URL que no se pueden analizar en su aplicación

Esta ruta debe apuntar a nuestro Http404 acción.Observe la url ¿El parámetro será una URL relativa porque el motor de enrutamiento está eliminando 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 detectar 404 en una aplicación MVC que no invoca usted mismo.Si no detecta rutas incomparables aquí, MVC pasará el problema a ASP.NET (Global.asax) y realmente no desea eso en esta situación.

Paso 5:Finalmente, invoca los 404 cuando tu aplicación no pueda encontrar algo.

Como cuando se envía una identificación incorrecta a mi controlador de préstamos (se 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 pudiera conectarse 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 conseguir más.

NOTA:Esto se ha editado significativamente con respecto a mi respuesta original, pero el propósito/requisitos son los mismos; es por eso que no agregué una nueva respuesta.

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 alternativa mejor 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

Respuesta rápida / TL;DR

enter image description here

Para los perezosos:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Luego elimine 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 ..mmm..ejem..extraño ...awkward


Respuesta larga y explicada

Sé que esto ha sido respondido.Pero la respuesta es REALMENTE SIMPLE (salud a David Fowler y Damián Edwards por realmente responder esto).

Hay no es necesario hacer nada personalizado.

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

Paso 1 -> Actualice su 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 toma nota con atención 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 ocurren en mi código.este es un error interno del servidor 500

Vea cómo la primera sección en <system.web> solo tiene uno entrada personalizada?El 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 demás errores son manejados por la configuración defaultRedirect="/ServerError" ..que dice, si no se encuentra una página 404, entonces vaya a la ruta /ServerError.

De acuerdo.eso esta fuera del camino..ahora a mis rutas enumeradas en global.asax

Paso 2: crear las rutas en Global.asax

Aquí tenéis 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 ignoradas -> axd's y favicons (ooo!¡Ruta de ignorar bonificación, para ti!) Luego (y el orden es IMPERATIVO AQUÍ), tengo mis dos rutas explícitas de manejo de errores ..seguido por 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 nuestra global.asax archivo, NO registramos globalmente el atributo HandleError.No, no, no señor.Nadda.No.Nien.Negativo.Nooooooo...

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, hay NO [HandleError] atributo aquí.¿Por qué?Porque el incorporado ASP.NET framework ya está manejando errores Y hemos especificado todo lo que debemos hacer para manejar un error :) ¡Está en este método!

A continuación, tengo los dos métodos de acción.Nada difícil ahí.Si desea mostrar alguna información de excepción, puede usar 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.Colóquelos en el lugar de vista normal, para este controlador.

enter image description here

Comentarios extra

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

Y eso, amigos míos, debería ser todo.

¡Felicidades por leer tanto y tener un Unicornio como premio!

enter image description here

He investigado MUCHO sobre cómo administrar correctamente los 404 en MVC (específicamente MVC3) , y esto, 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));
        }
    }
}

ErrorController:

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

(Optional)

Explicación :

AFAIK, hay 6 casos diferentes en los que una aplicación ASP.NET MVC3 puede generar 404s.

(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, desea lograr 3 objetivos:

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

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

(3) Envíe la respuesta directamente, sin involucrar una redirección 302.

Hay varias formas de intentar 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 programarse 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 programarse 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 programarse manualmente.
  3. Oculta las excepciones http de nivel de aplicación. P.ej. no puede usar la sección customErrors, System.Web.Mvc.HandleErrorAttribute, etc. No solo puede mostrar páginas de error genéricas.

(4)

<*>

y

<*>

Problemas con esta solución:

  1. Solo funciona en IIS 7+.
  2. No cumple con el objetivo (2) automáticamente. Debe programarse 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.

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 impide 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

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.

Aquí hay otro método que utiliza herramientas MVC que puede manejar solicitudes a 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 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 en su lugar.

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 objeto System.Web.Mvc.HandleErrorInfo 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 de 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 BaseController Unknown. 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 Http 404 al navegador sin una redirección utilizando atributos de filtro y vistas de error compartidas. También muestra que se 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, 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.

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

En MVC4 WebAPI 404 puede manejarse 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í

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

Mi solución, en caso de que alguien la 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.cshtml en la carpeta Shared, y listo.

Me parece que la configuración estándar CustomErrors debería funcionar sin embargo, debido a la dependencia de Server.Transfer parece que la implementación interna de ResponseRewrite no es compatible con MVC .

Esto me parece un agujero de funcionalidad deslumbrante, así que decidí volver a implementar esta función usando un módulo HTTP. La solución a continuación le permite manejar cualquier código de estado HTTP (incluido 404) redirigiendo 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
  • Habilita 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() {

        }


    }

}

Usuario

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 presten atención, notarán que en el modo de canalización integrada esto siempre responderá con HTTP 200 debido a la forma en que funciona Server.TransferRequest. Para devolver el código de error adecuado, uso el siguiente controlador de errores.

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

}

Lidiar con 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 los errores en web.config dentro de system.webserver pero eso solo devuelve páginas en blanco .

Mi objetivo cuando se me ocurrió esta solución era;

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

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. Observe 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. A continuación se muestra 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 configuré el código de respuesta

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

Haga 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 de vistas Compartido . Debajo está el mío;

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

Ahora en el controlador de su 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 tienes que manejar los errores de nivel IIS de otra manera.

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

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

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

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

@Puro.Krome

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

  2. ¿Puedes preguntarles a tus 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. redirectMode y existingResponse ¿De esta manera poder jugar bien con el SEO?

  3. ¿Puedes agregar alguna aclaración sobre todo eso (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", ELIMINAR customErrors ¿COMPLETAMENTE como alguien sugirió) después de hablar con tus amigos de Microsoft?

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

Actualizar:La respuesta de Unicornio hace un 302 Encontrado y un 200 OK y no se puede cambiar para devolver solo 404 usando una ruta.Tiene que ser un archivo físico que no sea muy MVC:ish.Entonces pasemos a otra solución.Lástima porque esta parecía ser la mejor respuesta MVC:ish hasta ahora.

Agregando 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 todo. No es necesario realizar cambios en Web.config.

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) Hacer herencia de esta clase abstracta en todos sus controladores

public class HomeController : MyController
{}  

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

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 sobre las respuestas presentadas aquí, así como 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 & # 225; 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 hacer que un filtro 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.

He revisado todos los artículos pero nada funciona para mí: Mi requerimiento de usuario debe escribir cualquier cosa en su url página 404 personalizada. Pensé que es muy sencillo. Pero debe entender el manejo de 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>

Encontré este artículo muy útil. Debería leerlo de inmediato. Página de error de Custome -Ben Foster

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top