Pergunta

Eu estou usando RC2

Usar URL Routing:

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

A descrição acima parece cuidar de pedidos como este (supondo rota padrão tabelas de configuração pelo projeto inicial MVC): "/ blah / blah / blah / blah"

Overriding HandleUnknownAction () no próprio controlador:

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

No entanto as estratégias anteriores não lidar com um pedido para um controlador de Bad / Unknown. Por exemplo, eu não tenho um "/ IDoNotExist", se eu solicitar este recebo a página 404 genérico a partir do servidor web e não o meu 404 se eu usar o roteamento + override.

Então, finalmente, a minha pergunta é:? Existe alguma maneira de capturar este tipo de pedido, utilizando uma rota ou qualquer outra coisa no framework MVC si

ou devo apenas como padrão usando customErrors Web.Config como meu manipulador 404 e esquecer tudo isso? Eu suponho se eu ir com customErrors eu vou ter que armazenar o genérico fora página 404 do / Visualizações devido às restrições Web.Config de acesso directo.

Foi útil?

Solução

O código é tirado de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net -mvc-rc2.aspx e obras em ASP.net MVC 1.0, bem

Aqui está como eu lidar com http exceções:

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

Outras dicas

Requisitos para 404

A seguir estão os meus requisitos para uma solução 404 e abaixo eu mostrar como eu implementá-lo:

  • Eu quero lidar com rotas combinadas com más ações
  • Eu quero lidar com rotas combinadas com maus controladores
  • Eu quero lidar com rotas pareados por un (URLs arbitrárias que meu aplicativo não consegue entender) - eu não quero estes borbulhando ao Global.asax ou IIS porque então i não pode voltar redirecionamento em meu aplicativo MVC corretamente
  • Eu quero uma maneira de lidar com da mesma maneira como acima, 404s personalizados - como quando um ID é enviado para um objeto que não existe (talvez suprimido)
  • Eu quero todos os meus 404s para retornar uma visão MVC (não uma página estática) para que eu possa bombear mais dados posteriormente, se necessário ( boa 404 projetos ) e que deve devolver o HTTP código de status 404

Solução

Eu acho que você deve salvar Application_Error no Global.asax para coisas mais elevadas, como exceções não tratadas e de registro (como A resposta de Shay Jacoby espectáculos), mas não 404 manuseio. É por isso que a minha sugestão mantém as coisas 404 fora do arquivo global.

Passo 1: Tenha um lugar comum para a lógica 404 de erros

Esta é uma boa idéia para manutenção. Use um href="https://stackoverflow.com/questions/108813/404-http-error-handler-in-asp-net-mvc-rc-5/108830#108830"> ErrorController tão bem concebido página 404 pode se adaptar facilmente. Além disso, certifique-se a sua resposta tem o 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
}

Passo 2: Use uma base de classe Controller para que você possa facilmente invocar o seu costume 404 ação e fio-se HandleUnknownAction

404s em ASP.NET MVC precisam ser pego em um número de lugares. O primeiro é HandleUnknownAction.

O método InvokeHttp404 cria um lugar comum para o reencaminhamento para o ErrorController e nossa nova ação Http404. Pense DRY !

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
}

Passo 3: Injeção Use dependência em seu controlador de fábrica e arame até 404 HttpExceptions

Assim como (não tem que ser StructureMap):

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

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

Eu acho que é melhor para erros de captura mais perto de onde são originários. É por isso que eu prefiro o acima para o manipulador Application_Error.

Este é o segundo lugar para 404s de captura.

Passo 4: Adicionar uma rota NotFound para Global.asax para urls que não conseguem ser analisado em seu aplicativo

Esta rota deve apontar para a nossa acção Http404. Observe o param url será uma URL relativa, porque o mecanismo de roteamento está descascando a parte do domínio aqui? É por isso que temos tudo o que a lógica url condicional na Etapa 1.

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

Este é o terceiro e último lugar para 404s de captura em um aplicativo MVC que você não invocar-se. Se você não pegar rotas incomparáveis ??aqui, então MVC vai passar o problema até ASP.NET (Global.asax) e você realmente não quer que nesta situação.

Passo 5: Finalmente, chame 404s quando o seu aplicativo não consegue encontrar algo

Como quando um mau ID é enviado para o meu controlador Empréstimos (deriva 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);
    }

Seria bom se tudo isso poderia ser ligado em menos lugares com menos código, mas eu acho que essa solução é mais sustentável, mais testável e bastante pragmática.

Obrigado pelo feedback até agora. Eu adoraria conseguir mais.

NOTA: Este foi editado de forma significativa da minha resposta original mas o propósito / requisitos são os mesmos - é por isso que eu não adicionou uma nova resposta

ASP.NET MVC não suporta personalizados 404 páginas muito bem. fábrica de controlador personalizado, catch-all rota, classe de controlador de base com HandleUnknownAction - argh

O IIS páginas de erro personalizadas são alternativa melhor até agora:

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

Amostra Projeto

Rápida Resposta / TL; DR

enter descrição da imagem aqui

Para as pessoas preguiçosas lá fora:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Em seguida, remova esta linha de global.asax

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

E isto é só para IIS7 + e o IIS Express.

Se você estiver usando Cassini .. bem .. hum .. er .. estranho ... estranho


Long, explicou resposta

Eu sei que este foi respondida. Mas a resposta é realmente simples (Cheers para David Fowler e Damian Edwards para realmente responder a esta).

não há necessidade de fazer qualquer coisa personalizado .

Para ASP.NET MVC3, todos os bits e peças estão lá.

Etapa 1 -.> Atualize seu web.config em dois pontos

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

e

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

Agora tome boa nota das rotas decidi usar. Você pode usar qualquer coisa, mas as minhas rotas são

  • /NotFound <-. Para uma página 404 não encontrado, erro
  • /ServerError <- para qualquer outro erro, incluir erros que acontecem no meu código. este é um 500 Internal Server erro

Veja como a primeira seção em <system.web> tem apenas um entrada personalizada? A entrada statusCode="404"? Eu só listados um código de status, porque todos os outros erros, incluindo a 500 Server Error (ie. Aqueles erros traquinas que acontece quando seu código tem um bug e bate a solicitação do usuário) .. todos os outros erros são tratados pelo defaultRedirect="/ServerError" configuração .. que diz que, se você não é uma página 404 não encontrado, então por favor goto /ServerError rota.

Ok. que está fora do caminho .. agora aos meus rotas listadas na global.asax

Etapa 2 - Criação de rotas em Global.asax

Aqui está a minha parte do percurso completo ..

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

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

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

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

que lista dois ignorar rotas -> axd's e favicons (! Ooo bônus ignorar rota, para você!) Então (e a ordem é imperativo aqui), tenho meus dois de erro explícita rotas de manipulação .. seguido por quaisquer outras rotas. Neste caso, o padrão. Claro, eu tenho mais, mas isso é especial para o meu web site. Apenas certifique-se as rotas de erro estão no topo da lista. Ordem é imperativo .

Finalmente, enquanto estamos dentro do nosso arquivo global.asax, nós não registar globalmente o atributo HandleError. Não, não, não senhor. Nadda. Não. Nien. Negativo. Noooooooooo ...

Remova esta linha de global.asax

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

Passo 3 - Criar o controlador com os métodos de ação

Agora .. nós adicionar um controlador com dois métodos de ação ...

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

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

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

        return View();
    }

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

Ok, vamos verificar isso. Primeiro de tudo, há NÃO atributo [HandleError] aqui. Por quê? Porque construído no quadro ASP.NET já está a lidar com erros e temos especificado todas as merdas que precisamos fazer para lidar com um erro :) É neste método!

Em seguida, eu tenho os dois métodos de ação. Nada difícil lá. Se u quiser mostrar qualquer informação de exceção, então u pode usar Server.GetLastError() para obter essa informação.

WTF Bonus: Sim, eu fiz um método de ação terceira, para o tratamento de erros de teste

.

Passo 4 - Criar as vistas

E, finalmente, criar dois pontos de vista. Coloque em no ponto normal de vista, para este controlador.

enter descrição da imagem aqui

comentários bônus

  • Você não precisa de um Application_Error(object sender, EventArgs e)
  • As etapas acima todo o trabalho 100% perfeitamente com Elmah . Elmah wroxs fraking!

E isso, meus amigos, deve ser ele.

Agora, parabéns para ler esta muito e tem um unicórnio como um prêmio!

enter descrição da imagem aqui

Eu investigado A LOT sobre a forma de gerir adequadamente 404s em MVC (especificamente MVC3) , e isso, IMHO é a melhor solução que eu vim acima com :

No global.asax:

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

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

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

ErrorsController:

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

        object model = Request.Url.PathAndQuery;

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

        return result;
    }
}

(Opcional)

Explicação:

AFAIK, há 6 casos diferentes que um aplicações ASP.NET MVC3 pode gerar 404s.

(gerada automaticamente pelo ASP.NET Framework:)

(1) faz um URL não encontrar uma correspondência na tabela de rotas.

(gerada automaticamente pelo ASP.NET MVC Framework:)

(2) Uma URL encontra uma correspondência na tabela de rota, mas especifica um controlador inexistente.

(3) Uma URL encontra uma correspondência na tabela de rota, mas especifica uma ação inexistente.

(gerada manualmente:)

(4) fortes uma acção retorna uma HttpNotFoundResult usando o método HttpNotFound ().

(5) Uma ação gera uma HttpException com o código de status 404.

(6) Ações Uma modifica manualmente a propriedade Response.StatusCode a 404.

Normalmente, você quer realizar 3 objetivos:

(1) Mostrar uma página de erro 404 personalizada para o usuário.

(2) Manter o código de status 404 na resposta do cliente (especialmente importante para SEO).

(3) Enviar a resposta diretamente, sem envolver um redirecionamento 302.

Existem várias maneiras de tentar fazer isso:

(1)

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

Problemas com esta solução:

  1. Não está conforme com objetivo (1) em casos (1), (4), (6).
  2. Não está conforme com objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Não está conforme com objetivo (3).

(2)

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

Problemas com esta solução:

  1. Só funciona no IIS 7 +.
  2. Não está conforme com objetivo (1) em casos (2), (3), (5).
  3. Não está conforme com objetivo (2) automaticamente. Deve 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 com esta solução:

  1. Só funciona no IIS 7 +.
  2. Não está conforme com objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Ele obscurece aplicação exceções nível http. Por exemplo. seção customErrors, System.Web.Mvc.HandleErrorAttribute, etc. não pode usá-lo não só pode mostrar páginas de erro genéricos.

(4)

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

e

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

Problemas com esta solução:

  1. Só funciona no IIS 7 +.
  2. Não está conforme com objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Não está conforme com objetivo (3) em casos (2), (3), (5).

As pessoas que têm incomodado com isso antes mesmo tentou criar suas próprias bibliotecas (ver http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Mas a solução anterior parece cobrir todos os casos, sem a complexidade de usar uma biblioteca externa.

Eu realmente gosto solução cottsaks e acho que é explicado de forma muito clara. minha única adição era alter etapa 2 da seguinte maneira

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
}

Basicamente, este pára urls contendo ações e controladores de inválidos de desencadear a rotina exceção duas vezes. por exemplo, para urls como asdfsdf / dfgdfgd

A única maneira que eu poderia obter @ método de cottsak ao trabalho para controladores inválidos foi modificar o pedido rota existente na CustomControllerFactory, assim:

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

Eu devo mencionar que estou usando MVC 2.0.

Aqui é um outro método usando ferramentas MVC que você pode manipular as solicitações para nomes feios controlador, maus nomes de rota, e quaisquer outros critérios que você vê dentro de ajuste de um método de ação. Pessoalmente, eu prefiro evitar tantas configurações web.config quanto possível, porque eles fazem a 302/200 redirecionamento e não suportam ResponseRewrite (Server.Transfer) usando vistas Navalha. Eu prefiro voltar a 404 com uma página de erro personalizada por razões de SEO.

Parte disso é nova visão sobre a técnica de cottsak acima.

Esta solução também usa as configurações web.config mínimos favorecendo os MVC 3 Filtros de erro.

Uso

Apenas lançar um HttpException de uma ação ou costume ActionFilterAttribute.

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

Etapa 1

Adicione a seguinte definição para o seu web.config. Isso é necessário para usar HandleErrorAttribute do MVC.

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

Etapa 2

Adicionar um HandleHttpErrorAttribute costume semelhante ao HandleErrorAttribute do framework MVC, exceto para erros de 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

Etapa 3

Adicionar filtros ao GlobalFilterCollection (GlobalFilters.Filters) em Global.asax. Esta rota exemplo vontade tudo InternalServerError (500) erros ao erro compartilhada vista (Views/Shared/Error.vbhtml). NotFound (404) erros serão enviados para ErrorHttp404.vbhtml nas visões compartilhadas também. Eu adicionei um erro 401 aqui para mostrar-lhe como isso pode ser estendido para códigos de erro HTTP adicionais. Note-se que estas devem ser vistas compartilhada, e todos eles usam o objeto System.Web.Mvc.HandleErrorInfo como um 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"})

Etapa 4

Crie uma classe de controlador de base e herdar a partir dele em seus controladores. Este passo permite-nos para lidar com nomes de ação desconhecidos e aumentar o erro HTTP 404 ao nosso 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

Etapa 5

Criar uma substituição ControllerFactory, e substituí-lo em seu arquivo Global.asax em Application_Start. Este passo permite-nos aumentar o HTTP 404 exceção quando um nome do controlador inválido foi especificado.

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)

Etapa 6

Inclua uma rota especial em seus RoutTable.Routes para a ação BaseController Desconhecido. Isso nos ajudará a elevar a 404 no caso em que um usuário acessa um controlador desconhecido, ou ação desconhecida.

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

Resumo

Este exemplo demonstra como se pode usar o framework MVC para retornar HTTP 404 códigos de erro para o navegador sem um redirecionamento utilizando atributos de filtro e visões de erro compartilhados. Ele também demonstra mostrando a mesma página de erro personalizada quando os nomes do controlador inválido e nomes de ação são especificados.

Vou acrescentar um screenshot de um nome do controlador inválido, nome da ação, e um costume 404 levantada a partir da ação Início / TriggerNotFound se eu conseguir votos suficientes para deixar um =). Fiddler retorna uma mensagem 404 quando eu acessar os seguintes URLs usando esta solução:

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

O post de cottsak acima e estes artigos foram boas referências.

A minha solução encurtado, que trabalha com áreas não tratadas, controladores e ações:

  1. Criar um 404.cshtml vista.

  2. Criar uma classe base para seus 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. Criar uma fábrica de controlador personalizado devolver o controlador de base como um retorno:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
    
  4. Adicionar a Application_Start() a seguinte linha:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
    

Em MVC4 WebAPI 404 pode ser pega da seguinte maneira,

CURSOS ApiController

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

HOME CONTROLADOR

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

enter descrição da imagem aqui

Tente NotFoundMVC em NuGet. Ele funciona, nenhuma configuração.

A minha solução, no caso de alguém acha útil.

Em Web.config:

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

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

Adicionar um PageNotFound.cshtml na pasta Shared, e é isso.

Parece-me que a configuração CustomErrors padrão deve apenas trabalho no entanto, devido à dependência de Server.Transfer parece que a implementação interna do ResponseRewrite não é compatível com MVC.

Isto parece uma funcionalidade buraco olhando para mim, então eu decidi re-implementar esse recurso usando um HTTP módulo. A solução abaixo permite-lhe lidar com qualquer código de status HTTP (incluindo 404), redirecionando a qualquer rota MVC válido apenas como você faria normalmente.

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

Esta foi testado nas seguintes plataformas;

  • MVC4 no modo de pipeline integrado (IIS Express 8)
  • MVC4 no modo Clássico (VS Development Server, Cassini)
  • MVC4 no modo Clássico (IIS6)

Benefícios

  • solução genérica que pode ser descartado em qualquer projeto MVC
  • Ativa o suporte para erros personalizados tradicionais de configuração
  • funciona em ambas as Integrated Pipeline e modos Classic

A Solução

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

Inclua este como o módulo HTTP final em seu 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 aqueles de vocês prestando atenção você vai notar que no modo integrado Pipeline esta sempre responderá com HTTP 200 devido às obras Server.TransferRequest maneira. Para retornar o código de erro adequada eu uso o seguinte controlador de erro.

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

}

Como lidar com erros em ASP.NET MVC é apenas uma dor na bunda. Eu tentei um monte de sugestões sobre esta página e em outras questões e locais e nada funciona bem. Uma sugestão foi para lidar com erros em web.config dentro system.webServer , mas que apenas retorna as páginas em branco .

Meu objetivo quando esbarra com essa solução era;

  • NÃO REDIRECT
  • Retorno códigos de status adequado não 200 / Ok como o erro padrão manipulação

Aqui está a minha solução.

1 .Add o seguinte para system.web seção

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

As alças acima de quaisquer urls não tratada pelo routes.config e exceções não tratadas, especialmente aquelas encontradas nas vistas. Aviso eu usei aspx não html . Isso é para que eu possa adicionar um código de resposta no traseiro código.

2 . Crie uma pasta chamada Erro (ou o que você preferir) na raiz do seu projeto e adicionar as duas webforms. Abaixo está a minha 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>

E no código por trás eu definir o código de resposta

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

Faça o mesmo para o 500 páginas

3 erros punho .Para dentro os controladores. Há muitas maneiras de fazer isso. Isto é o que funcionou para mim. Todos os meus controladores de herdar de um controlador de base. No controlador de base, eu tenho os seguintes 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 .Add o CustomError.cshtml à sua pasta compartilhada vistas. Abaixo é meu;

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

Agora, em seu controlador aplicativo que você pode fazer algo como isto;

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

Agora, para o ressalva . Não vai lidar com erros de arquivos estáticos. Então se você tem uma rota tais como example.com/widgets e o usuário muda para example.com/widgets.html , eles vão receber a página de erro padrão para o IIS você tem que lidar com IIS erros de nível de alguma outra forma.

Publicação de uma resposta desde o meu comentário era muito longo ...

É tanto um comentário e perguntas para o unicórnio post / resposta:

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

Eu prefiro esta resposta sobre os outros para a sua simplicidade eo fato de que, aparentemente, algumas pessoas da Microsoft foram consultados. Eu tenho três perguntas, todavia, e se eles podem ser respondidas, então vou chamar esta resposta o Santo Graal de todos os 404/500 respostas de erro nas interwebs para um (x) aplicativo ASP.NET MVC.

@ Pure.Krome

  1. Você pode atualizar sua resposta com o material SEO a partir dos comentários apontado por GWB (nunca houve qualquer mencionar isso em sua resposta) - <customErrors mode="On" redirectMode="ResponseRewrite"> e <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Você pode perguntar a seus amigos da equipe ASP.NET, se não há problema em fazê-lo assim - seria bom ter alguma confirmação - talvez seja uma grande falta de nenhum para redirectMode mudança e existingResponse desta forma a ser capaz de jogar bem com SEO ?!

  3. Você pode adicionar alguns esclarecimentos em torno todo esse material (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrors COMPLETAMENTE como alguém sugeriu) depois de conversar com seus amigos na Microsoft?

Como eu estava dizendo; seria supernice se pudéssemos fazer a sua resposta mais completa como esta parece ser uma questão bastante popular com 54 000+ visualizações.

Atualizar : Unicorn resposta faz um 302 Found e uma 200 OK e não pode ser alterado para retornar apenas 404 usando uma rota. Tem que ser um arquivo físico que não é muito MVC: ish. Então, passar para outra solução. Muito ruim porque este parecia ser o final MVC:. Resposta ish até aqui

Adicionando a minha solução, que é quase idêntico ao Herman Kan de, com uma pequena ruga para permitir que o trabalho para o meu projeto.

Criar um controlador de erro personalizada:

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

Em seguida, criar uma fábrica de controlador personalizado:

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

Finalmente, adicione uma substituição para o controlador de erro personalizada:

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

E é isso. Não há necessidade de Web.config muda.

1) Faça abstract class Controller.

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) Faça inheritence a partir desta classe abstrata em seus todos os controladores

public class HomeController : MyController
{}  

3) e adicionar uma exibição denominada "NotFound" em você Ver-Shared pasta.

eu passei a maior parte das soluções postadas neste segmento. Enquanto esta questão pode ser velho, ele ainda é muito aplicável aos novos projectos até agora, então eu passei um monte de tempo lendo sobre as respostas aqui apresentadas, bem como mais onde.

Como @Marco apontou os diferentes casos em que um 404 pode acontecer, eu verifiquei a solução que eu compilados juntos contra essa lista. Além de sua lista de exigências, eu também acrescentou mais um.

  • A solução deve ser capaz de lidar com MVC, bem como chamadas AJAX / WebAPI da maneira mais apropriada. (Ou seja, se 404 acontece no MVC, ele deve mostrar a página não encontrada e se 404 acontece em WebAPI, não deve roubar a resposta XML / JSON para que o Javascript consumir pode analisá-lo facilmente).

Esta solução é duas vezes:

Primeira parte dela vem de @Guillaume em https://stackoverflow.com/a/27354140/2310818 . A solução cuida de qualquer 404 que foram causados ??devido à rota inválida, controlador inválido e ação inválido.

A idéia é criar um formulário da Web e, em seguida, torná-lo chamar a ação NotFound de seus erros controlador de MVC. Ele faz tudo isso sem qualquer redirecionamento para que você não vai ver um único 302 em Fiddler. A URL original também é preservada, o que torna esta solução fantástica!


A segunda parte do se trata de @ Germán em https://stackoverflow.com/a/5536676/2310818 . A solução cuida de qualquer 404 retornado por suas ações na forma de HttpNotFoundResult () ou lançar nova HttpException ()!

A idéia é ter um olhar filtro na resposta, bem como a exceção lançada por seus controladores MVC e chamar a ação apropriada em seus erros do controlador. Mais uma vez esta solução funciona sem qualquer redirecionamento e a URL original é preservada!


Como você pode ver, ambas as soluções, juntos, oferecem um erro muito robusto manipulação mecanismo e alcançar todos os requisitos listados por @Marco, bem como as minhas necessidades. Se você gostaria de ver uma amostra de trabalho ou uma demonstração desta solução, por favor deixe nos comentários e eu ficaria feliz em colocá-lo juntos.

Eu passei por todos os artigos, mas nada funciona para mim: Meu usuário exigência tipo qualquer coisa em seu costume url página 404 deve show.I pensamos que é muito reto forward.But você deve entender a manipulação de 404 corretamente:

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

Eu encontrei este artigo muito helpfull.should ser lido de uma só vez. página de erro Custome -Ben Foster

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top