Como posso lidar corretamente com 404 no ASP.NET MVC?

432

Estou usando RC2

Usando o roteamento de URL:

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

O item acima parece atender a solicitações como esta (assumindo a configuração padrão das tabelas de rotas pelo projeto inicial do MVC): "/ blá / blá / blá / blá"

Substituindo 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 tratam uma solicitação para um controlador Ruim / Desconhecido. Por exemplo, eu não tenho um "/ IDoNotExist", se eu solicitar isso, obtenho a página 404 genérica do servidor da web e não o meu 404 se eu usar o roteamento + substituição.

Então, finalmente, minha pergunta é: existe alguma maneira de capturar esse tipo de solicitação usando uma rota ou algo mais na própria estrutura MVC?

Ou devo apenas usar o Web.Config customErrors como meu manipulador 404 e esquecer tudo isso? Presumo que, se eu for com customErrors, terei que armazenar a página 404 genérica fora de / Views devido às restrições da Web.Config no acesso direto.

Brian
fonte
3
é erro 404, eu simplesmente não me incomodaria com isso. deixe exibir 404. como definitivamente o usuário digitou algo errado. ou se algo mudou, seu aplicativo deve aceitar essa solicitação e redirecionar permanentemente. 404 pertence ao servidor da web e não ao aplicativo. você sempre pode personalizar as páginas do iis por erro.
mamu 1/08/10
você pode dar uma olhada nessa solução também blog.dantup.com/2009/04/…
Desenvolvedor
ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages também tem algumas boas informações
Chris S
4
É uma pena que 4 versões estáveis ​​mais tarde e mais de 5 anos depois, a situação para lidar com 404s no asp.net MVC + IIS não tenha realmente melhorado e ainda seja o início das perguntas e respostas sobre como lidar com isso.
Joelmdev 29/08/14

Respostas:

271

O código é obtido em http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx e funciona no asp.net MVC 1.0 também

Veja como eu manejo exceções de http:

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

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

   Response.Clear();

   HttpException httpException = exception as HttpException;

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

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

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

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

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

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

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}
Shay Jacoby
fonte
23
update: definitivamente é necessário procurar um http 404, mas ainda não tenho certeza de quando você obteria um 500. Também é necessário definir explicitamente o Response.StatusCode = 404 ou 500, caso contrário o Google começará a indexar essas páginas Se você está retornando um código de status 200 que este código faz atualmente
Simon_Weaver
6
@ Simon_Weaver: concordou! Isso precisa retornar códigos de status 404. É essencialmente quebrado como uma solução 404 até que isso aconteça. Verifique isto: codinghorror.com/blog/2007/03/…
Matt Kocaj
1
Existe uma falha subjacente a toda essa sugestão - quando a execução chega ao Global.asax, muito do HttpContext está faltando. Você não pode rotear de volta para seus controladores como o exemplo sugere. Consulte os comentários no link do blog na parte superior.
Matt Kocaj
3
Como alguns dos comentários acima e a postagem vinculada mencionam, isso não parece funcionar. O controlador de erro é atingido, mas uma tela em branco retorna. (usando MVC 3)
RyanW
4
algo não se sente bem, todo o propósito de MVC é remover tudo o que abstração e ainda aqui está ele novamente ...
Alex Nolasco
255

Requisitos para 404

A seguir, são apresentados meus requisitos para uma solução 404 e abaixo mostro como implementá-la:

  • Quero lidar com rotas correspondentes com ações ruins
  • Eu quero lidar com rotas correspondentes com controladores ruins
  • Eu quero lidar com rotas não correspondentes (URLs arbitrárias que meu aplicativo não consegue entender) - não quero que elas cheguem até o Global.asax ou IIS, porque não consigo redirecionar de volta para o aplicativo MVC corretamente
  • Eu quero uma maneira de lidar da mesma maneira que acima, 404s personalizados - como quando um ID é enviado para um objeto que não existe (talvez excluído)
  • Quero que todos os meus 404 retornem uma visualização MVC (não uma página estática) para a qual eu possa bombear mais dados posteriormente, se necessário ( bons designs 404 ) e eles devem retornar o código de status HTTP 404

Solução

Eu acho que você deve economizar Application_Errorno Global.asax para coisas mais altas, como exceções não tratadas e log (como mostra a resposta de Shay Jacoby ), mas não no tratamento 404. É por isso que minha sugestão mantém o material 404 fora do arquivo Global.asax.

Etapa 1: tenha um lugar comum para a lógica de erro 404

Essa é uma boa idéia para manutenção. Use um ErrorController para que futuras melhorias na sua página 404 bem projetada possam se adaptar facilmente. Além disso, verifique se a sua resposta possui 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
}

Etapa 2: use uma classe Controller básica para chamar facilmente sua ação 404 personalizada e conectar HandleUnknownAction

404s no ASP.NET MVC precisam ser capturados em vários locais. O primeiro é HandleUnknownAction.

O InvokeHttp404método cria um local comum para o roteamento para a ErrorControllernova Http404ação. Pense 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
}

Etapa 3: Use a injeção de dependência em sua fábrica de controladores e conecte 404 HttpExceptions

Assim (não precisa ser o 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 pegar erros mais perto de onde eles se originam. É por isso que eu prefiro o acima ao Application_Errormanipulador.

Este é o segundo lugar para capturar 404s.

Etapa 4: adicione uma rota NotFound ao Global.asax para URLs que não sejam analisados ​​no seu aplicativo

Essa rota deve apontar para a nossa Http404ação. Observe que o urlparâmetro será um URL relativo porque o mecanismo de roteamento está removendo a parte do domínio aqui? É por isso que temos toda essa lógica de URL condicional na Etapa 1.

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

Este é o terceiro e último local para capturar 404s em um aplicativo MVC que você não se invoca. Se você não pegar rotas sem correspondência aqui, o MVC passará o problema para o ASP.NET (Global.asax) e você realmente não deseja isso nessa situação.

Etapa 5: finalmente, chame 404s quando seu aplicativo não encontrar algo

Como quando um ID incorreto é enviado ao meu controlador de empréstimos (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);
    }

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

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

NOTA: Isso foi editado significativamente a partir da minha resposta original, mas a finalidade / requisitos são os mesmos - é por isso que não adicionei uma nova resposta

Matt Kocaj
fonte
12
Obrigado pela redação completa. Uma adição é que, quando executado no IIS7, você precisa adicionar a propriedade "TrySkipIisCustomErrors" como true. Caso contrário, o IIS ainda retornará a página 404 padrão. Adicionamos Response.TrySkipIiisCustomErrors = true; após a linha na Etapa 5 que define o código de status. msdn.microsoft.com/en-us/library/…
Rick
1
@Ryan A customErrorsseção web.config define páginas de redirecionamento estático que são tratadas em alto nível no aspnet, se não no IIS. Isso não é o que eu queria, pois precisava que o resultado fosse MVC Views (para que eu possa ter dados neles etc.). Eu não diria categoricamente que " customErrorsé obsoleto no MVC", mas para mim e para essa solução 404 eles certamente são.
Matt Kocaj
1
Além disso, alguém pode atualizar a Etapa 3 para que o StructureMap não seja usado? Talvez apenas um ControllerFactory genérico que seja fácil de implementar se você ainda não estiver usando um ControllerFactory.
David Murdoch
7
Isso funciona bem para o MVC3. Eu mudei ObjectFactory.GetInstancepara o MVC3, DependencyResolver.Current.GetServiceentão é mais genérico. Estou usando o Ninject.
Kamranicus
122
Alguém mais acha patentemente insano que algo tão comum como o 404 em uma estrutura da Web seja tão complicado?
você está
235

O ASP.NET MVC não suporta muito bem as páginas 404 personalizadas. Fábrica de controlador personalizado, rota abrangente, classe de controlador de base com HandleUnknownAction- argh!

As páginas de erro personalizadas do IIS são a melhor alternativa 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();
    }
}

Projeto de exemplo

Pavel Chuchuva
fonte
38
ISTO DEVE TER A RESPOSTA ACEITADA !!! Funciona excelente no ASP.NET MVC 3 com o IIS Express.
Andrei Rînea
7
Se você estiver usando o IIS7 +, este é definitivamente o caminho a percorrer. +1!
Elo80ka 16/05/11
3
é possível retornar apenas um status 404 quando você estiver trabalhando em JSON no mesmo projeto?
VinnyG
6
Isso está funcionando muito bem no iis express, mas assim que implanto o site na produção IIS 7.5, tudo o que recebo é uma página em branco em vez da exibição de erro.
Moulde 9/03/12
2
De acordo com meus testes (com MVC3), isso quebra customErrors mode="On"junto com o HandleErrorAttributepara ser funcional. Páginas de erro personalizadas para exceções não tratadas em ações do controlador não são mais veiculadas.
Slauma
153

Resposta Rápida / TL; DR

insira a descrição da imagem aqui

Para as pessoas preguiçosas por aí:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Em seguida, remova esta linha de global.asax

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

E isso é apenas para o IIS7 + e o IIS Express.

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


Resposta longa e explicada

Eu sei que isso foi respondido. Mas a resposta é MUITO SIMPLES (felicidades a David Fowler e Damian Edwards por realmente responderem a isso).

Não há necessidade de fazer nada personalizado .

Pois ASP.NET MVC3todos os pedaços estão lá.

Passo 1 -> Atualize seu web.config em DOIS spots.

<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, observe cuidadosamente as ROTAS que decidi usar. Você pode usar qualquer coisa, mas minhas rotas são

  • /NotFound <- para um 404 não encontrado, página de erro.
  • /ServerError<- para qualquer outro erro, inclua erros que acontecem no meu código. este é um erro interno do servidor 500

Veja como a primeira seção <system.web>possui apenas uma entrada personalizada? A statusCode="404"entrada? Eu listei apenas um código de status porque todos os outros erros, incluindo o 500 Server Error(ou seja, aqueles erros irritantes que acontecem quando seu código tem um bug e trava a solicitação do usuário) .. todos os outros erros são tratados pela configuração defaultRedirect="/ServerError".. que diz , se você não for uma página 404 não encontrada, vá para a rota /ServerError.

Está bem. que está fora do caminho .. agora para minhas rotas listadas emglobal.asax

Etapa 2 - Criando as rotas no Global.asax

Aqui está a minha seção de rota 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}
        );
}

Que lista duas rotas de ignorar -> axd'se favicons(ooo! Bônus ignora rota, para você!) Então (e a ordem é IMPERATIVA AQUI), eu tenho minhas duas rotas explícitas de tratamento de erros .. seguidas por outras rotas. Nesse caso, o padrão. Claro que tenho mais, mas isso é especial para o meu site. Apenas verifique se as rotas de erro estão no topo da lista. A ordem é imperativa .

Finalmente, enquanto estamos dentro do nosso global.asaxarquivo, NÃO registramos 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());

Etapa 3 - Crie o controlador com os métodos de ação

Agora .. adicionamos 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á NO [HandleError] atributo aqui. Por quê? Porque a ASP.NETestrutura interna já está lidando com erros E nós especificamos toda a merda que precisamos fazer para lidar com um erro :) É neste método!

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

Bônus WTF: Sim, criei um terceiro método de ação para testar o tratamento de erros.

Etapa 4 - Criar as vistas

E, finalmente, crie duas visualizações. Coloque-os no ponto de visualização normal, para este controlador.

insira a descrição da imagem aqui

Comentários de bônus

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

E isso, meus amigos, deveria ser isso.

Agora, parabéns por ler muito e tenha um unicórnio como prêmio!

insira a descrição da imagem aqui

Pure.Krome
fonte
Então, eu tentei implementar isso, mas alguns problemas ... primeiro, você precisa de um ~ antes do caminho no weeb.config ou ele não funciona para diretórios virtuais. 2-Se os erros personalizados do IIS forem acionados e a exibição estiver usando um layout que não renderiza, apenas uma página em branco. Resolvi isso adicionando esta linha ao controlador "Response.TrySkipIisCustomErrors = true;" . No entanto, ainda não funcionará se você acessar um URL que seja um arquivo, mas 404. como mysite / Whatever / fake.html recebe uma página em branco.
Robert Noack
3
-1, desculpe, para mim, qualquer solução que altere o URL para 404 está errada. e com o webconfig, no MVC, não há como lidar com isso sem alterar a URL, ou você precisa criar arquivos html estáticos ou aspx (sim, arquivos aspx antigos simples) para poder fazer isso. sua solução é boa se você gosta ?aspxerrorpath=/er/not/foundde ter em urls.
Gutek
7
Isto pode soar muito estranho - mas a minha resposta foi dada há muito tempo e estou de acordo com o seu @Gutek, eu não gosto de fazer um redirecionamento para uma página de erro mais . Eu costumava (ref a minha resposta: P). Se o erro ocorreu em / some / resource .., esse recurso deve retornar 404 ou 500 etc. implicações massivas de SEO caso contrário. Ahh .. como os tempos mudam :)
Pure.Krome
@Gutek Você conhece customErrors redirectMode = "ResponseRewrite"? E retornando 404 do não é ideal a partir de uma perspectiva de segurança
Jowen
1
@ Chris <insira sua divindade favorita aqui> caramba. Nem me lembro o que era agora. Bem, minha coleção de memes para o resgate ... e ... consertada.
Pure.Krome
86

Eu investiguei MUITO sobre como gerenciar corretamente 404s no MVC (especificamente MVC3) , e isso, IMHO é a melhor solução que eu vim:

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

Controlador de erros:

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, existem 6 casos diferentes em que aplicativos do ASP.NET MVC3 podem gerar 404s.

(Gerado automaticamente pelo ASP.NET Framework :)

(1) Um URL não encontra uma correspondência na tabela de rotas.

(Gerado automaticamente pelo ASP.NET MVC Framework :)

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

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

(Gerado manualmente :)

(4) Uma ação retorna um HttpNotFoundResult usando o método HttpNotFound ().

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

(6) Uma ação modifica manualmente a propriedade Response.StatusCode para 404.

Normalmente, você deseja realizar três objetivos:

(1) Mostre uma página de erro 404 personalizada ao usuário.

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

(3) Envie 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 cumpre o objetivo (1) nos casos (1), (4), (6).
  2. Não cumpre o objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Não cumpre o 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. Funciona apenas no IIS 7+.
  2. Não cumpre o objetivo (1) nos casos (2), (3), (5).
  3. Não cumpre o 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. Funciona apenas no IIS 7+.
  2. Não cumpre o objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Oculta as exceções http no nível do aplicativo. Por exemplo, não é possível usar a seção customErrors, System.Web.Mvc.HandleErrorAttribute, etc. Ele não pode apenas mostrar páginas de erro genéricas.

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. Funciona apenas no IIS 7+.
  2. Não cumpre o objetivo (2) automaticamente. Deve ser programado manualmente.
  3. Não cumpre o objetivo (3) nos casos (2), (3), (5).

As pessoas que já enfrentaram problemas antes tentaram criar suas próprias bibliotecas (consulte 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.

Marco
fonte
Ótima resposta. Digno de muitos mais votos positivos. Por que seu código global.asax não funciona / pertence ao Application_Error.
NinjaNye
7
Obrigado! Isso não pode ser feito em Application_Error, porque 404s explícitos lançados de um controlador não são considerados erros no ASP.NET. Se você retornar um HttpNotFound () de um controlador, o evento Application_Error nunca será acionado.
7773 Marco
1
Eu acho que você esqueceu public ActionResult NotFound() {}no seu ErrorController. Além disso, você pode explicar como _NotFoundseria a sua parcial para solicitações AJAX?
D4n3
2
Com o MVC 4 eu sempre entro MissingMethodException: Cannot create an abstract classem linha c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Alguma idéia?
µBio 12/11/12
1
Se o URL "não encontrado" incluir um ponto no caminho (por exemplo, exemplo.com/hi.bob ), o Application_EndRequest não será acionado, e eu recebo a página 404 genérica do IE.
Bob.at.Indigo.Health
13

Eu realmente gosto da solução cottsaks e acho que é explicado com muita clareza. minha única adição foi alterar o passo 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, isso impede que URLs que contêm ações inválidas e controladores acionem a rotina de exceção duas vezes. por exemplo, para URLs como asdfsdf / dfgdfgd

Dave Lowe
fonte
4
Isto e excelente. Esses casos "duas vezes" estavam começando a me incomodar. atualizei minha resposta
Matt Kocaj
a solução acima funciona se o usuário digitar o nome do controlador e da ação incorretos?
Monojit Sarkar
6

A única maneira de conseguir que o método do @ cottsak funcionasse para controladores inválidos era modificar a solicitação de rota existente no CustomControllerFactory, da seguinte maneira:

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

Devo mencionar que estou usando o MVC 2.0.

Dave K
fonte
Você sabe por quê? (Específico MVC2?)
Matt Kocaj
Eu acho que a chave era modificar a solicitação existente em vez de fazer uma nova, mas fiz isso há pouco tempo, então não tenho certeza. O "InvokeHttp404" não funcionou na fábrica do controlador.
Dave K
Atualizei minha resposta hoje com algumas especificidades do MVC2. Você pode me dizer se minha solução, conforme detalhado acima, ainda não funciona para você?
Matt Kocaj
4

Aqui está outro método usando as ferramentas MVC, que você pode manipular solicitações para nomes de controladores incorretos, nomes de rotas incorretos e qualquer outro critério que considerar adequado dentro de um método Action. Pessoalmente, prefiro evitar o maior número possível de configurações do web.config, porque elas fazem o redirecionamento 302/200 e não suportam ResponseRewrite ( Server.Transfer) usando as visualizações do Razor. Prefiro retornar um 404 com uma página de erro personalizada por motivos de SEO.

Parte disso é uma nova abordagem da técnica de cottsak acima.

Essa solução também usa configurações mínimas do web.config, favorecendo os filtros de erro do MVC 3.

Uso

Basta lançar um HttpException de uma ação ou ActionFilterAttribute personalizado.

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

Passo 1

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

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

Passo 2

Adicione um HandleHttpErrorAttribute personalizado semelhante ao HandleErrorAttribute da estrutura MVC, exceto por 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

Adicione filtros ao GlobalFilterCollection ( GlobalFilters.Filters) em Global.asax. Este exemplo roteará todos os erros InternalServerError (500) para a visualização compartilhada de erros ( Views/Shared/Error.vbhtml). Os erros NotFound (404) também serão enviados para ErrorHttp404.vbhtml nas visualizações compartilhadas. Adicionei um erro 401 aqui para mostrar como isso pode ser estendido para códigos de erro HTTP adicionais. Observe que essas devem ser visualizações compartilhadas e todas elas usam o System.Web.Mvc.HandleErrorInfoobjeto como modelo.

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

Passo 4

Crie uma classe de controlador base e herda dela em seus controladores. Esta etapa nos permite manipular nomes de ações desconhecidos e aumentar o erro HTTP 404 para o 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

Crie uma substituição ControllerFactory e substitua-a no seu arquivo Global.asax em Application_Start. Esta etapa nos permite gerar a exceção HTTP 404 quando um nome de 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 sua RoutTable.Routes para a ação BaseController Unknown. Isso nos ajudará a aumentar o 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} _
)

Sumário

Este exemplo demonstrou como é possível usar a estrutura MVC para retornar 404 códigos de erro HTTP ao navegador sem redirecionar usando atributos de filtro e visualizações de erro compartilhadas. Também demonstra mostrar a mesma página de erro personalizada quando nomes de controladores e ações inválidos são especificados.

Adicionarei uma captura de tela de um nome de controlador inválido, nome da ação e um 404 personalizado gerado a partir da ação Home / TriggerNotFound se eu tiver votos suficientes para postar um =). O violinista retorna uma mensagem 404 quando acesso os seguintes URLs usando esta solução:

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

post acima da cottsak e esses artigos foram boas referências.

sky-dev
fonte
Hmm, eu não consegui fazer isso funcionar: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- alguma idéia de por que eu conseguiria isso?
enashnash
redirectMode = "ResponseRedirect". Isso retornará um OK 302 Encontrado + um OK 200, o que não é bom para SEO!
PussInBoots
4

Minha solução abreviada que trabalha com áreas não tratadas, controladores e ações:

  1. Crie uma visualização 404.cshtml.

  2. Crie 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. Crie uma fábrica de controladores personalizados retornando seu controlador de base como substituto:

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

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

No MVC4, o WebAPI 404 pode ser manipulado 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;
    }

CONTROLADOR HOME

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

VISÃO

<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

insira a descrição da imagem aqui

Diganta Kumar
fonte
2

Tente NotFoundMVC no nuget. Funciona, sem configuração.

DarthVader
fonte
http://localhost/Views/Shared/NotFound.cshtmlnão resulta em uma página 404 personalizada.
111113 Dan Friedman
É muito fácil de personalizar. Você tem acesso ao URL solicitado e ao referenciador, para poder fazer o que quiser. Eu uso este pacote e funciona muito bem.
Avrohom Yisroel
Este é um ótimo pacote, desde que você não use ações assíncronas da Tarefa <ActionResult> (ou outras assíncronas similares). No MVC 5, este é um cenário quebrado. Existe um garfo no GitHub para contornar isso, mas para mim é um não, não.
Stargazer
2

Minha solução, caso alguém ache útil.

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

Adicione um PageNotFound.cshtmlna Sharedpasta e pronto.

Konamiman
fonte
2
Esse problema não gera um redirecionamento 302 e um status 200 (OK) para o cliente? Eles ainda não deveriam estar recebendo o status 404?
Sam
@Konamiman Você tem certeza de que a linha do seu código deve ser lida model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;e não model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(e em vez de &&)?
Jean-François Beauchamp
2

Parece-me que a CustomErrorsconfiguração padrão deve funcionar no entanto, devido à confiança Server.Transferque parece que a implementação interna do ResponseRewritenão é compatível com o MVC.

Isso me parece uma falha gritante na funcionalidade, então decidi reimplementar esse recurso usando um módulo HTTP. A solução abaixo permite que você lide com qualquer código de status HTTP (incluindo 404) redirecionando para qualquer rota MVC válida, como faria normalmente.

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

Isso 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 inserida em qualquer projeto MVC
  • Ativa o suporte para a configuração tradicional de erros personalizados
  • Funciona nos modos Pipeline integrado e Clássico

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 isso como o módulo HTTP final no 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 que prestam atenção, notarão que, no modo Pipeline integrado, isso sempre responderá com o HTTP 200 devido à maneira como Server.TransferRequestfunciona. Para retornar o código de erro adequado, 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());
    }

}
Taz vermelho
fonte
2

Lidar com erros no ASP.NET MVC é apenas um problema. Tentei muitas sugestões nesta página e em outras perguntas e sites e nada funciona bem. Uma sugestão foi manipular erros no web.config dentro do system.webserver, mas isso apenas retorna páginas em branco .

Meu objetivo ao criar esta solução era:

  • NÃO REDIRECIONAR
  • Retornar CÓDIGOS DE ESTADO ADEQUADO não 200 / Ok, como o tratamento de erros padrão

Aqui está a minha solução.

1. Adicione o seguinte à seção 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>

O item acima lida com quaisquer URLs não manipulados pelo routes.config e com exceções não tratadas, especialmente aquelas encontradas nas visualizações. Observe que eu usei aspx, não html . É assim que eu posso adicionar um código de resposta no código por trás.

2 . Crie uma pasta chamada Erro (ou o que você preferir) na raiz do seu projeto e adicione os dois webforms. Abaixo está 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, defino o código de resposta

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

Faça o mesmo para a página 500

3 erros punho .Para dentro os controladores. Há muitas maneiras de fazer isso. Isto é o que funcionou para mim. Todos os meus controladores herdam de um controlador base. No controlador 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. Adicione o CustomError.cshtml à sua pasta Views compartilhadas . Abaixo está o meu;

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

Agora, no seu controlador de aplicativo, você pode fazer algo assim;

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 a ressalva . Ele não manipula erros de arquivo estático. Portanto, se você tiver uma rota como exemplo.com/widgets e o usuário alterá-la para exemplo.com/widgets.html , eles obterão a página de erro padrão do IIS; portanto, você deverá lidar com os erros no nível do IIS de outra maneira.

Moses Machua
fonte
1

Postando uma resposta desde que meu comentário foi muito longo ...

É um comentário e perguntas para a postagem / resposta do unicórnio:

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

Prefiro essa resposta que as outras por sua simplicidade e pelo fato de que aparentemente algumas pessoas da Microsoft foram consultadas. No entanto, tenho três perguntas e, se puderem ser respondidas, chamarei essa resposta de Santo Graal de todas as respostas de erro 404/500 nas interwebs de um aplicativo ASP.NET MVC (x).

@ Pure.Krome

  1. Você pode atualizar sua resposta com o conteúdo de SEO a partir dos comentários apontados pelo GWB (nunca houve menção disso na sua resposta) - <customErrors mode="On" redirectMode="ResponseRewrite">e <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Você pode perguntar aos seus amigos da equipe do ASP.NET se está tudo bem em fazê-lo dessa maneira - seria bom ter alguma confirmação - talvez seja um grande não-não para mudar redirectModee existingResponse, dessa maneira, ser capaz de jogar bem com o SEO ?!

  3. Você pode adicionar alguns esclarecimentos em torno todas essas coisas ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", remova customErrorscompletamente, como alguém sugeriu) depois de conversar com seus amigos na Microsoft?

Como eu estava dizendo; seria ótimo se pudéssemos tornar sua resposta mais completa, pois essa parece ser uma pergunta bastante popular com mais de 54.000 visualizações.

Atualização : a resposta do unicórnio faz um 302 encontrado e um 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 seja muito MVC: ish. Então, vamos para outra solução. Que pena, porque essa parecia ser a melhor resposta MVC: ish até agora.

O Gato de Botas
fonte
1

Adicionando minha solução, que é quase idêntica à de Herman Kan, com uma pequena ruga para permitir que ela funcione no meu projeto.

Crie um controlador de erro personalizado:

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

Em seguida, crie uma 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);
    }
}

Por fim, adicione uma substituição ao controlador de erro 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));
}

E é isso. Não há necessidade de alterações no Web.config.

Rob Lyndon
fonte
1

1) Faça a classe abstrata 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 herança dessa classe abstrata em todos os seus controladores

public class HomeController : MyController
{}  

3) E adicione uma exibição chamada "NotFound" na sua pasta View-Shared.

Mehmet
fonte
0

Passei pela maioria das soluções postadas neste tópico. Embora essa pergunta possa ser antiga, ainda é muito aplicável a novos projetos até agora, por isso passei muito tempo lendo as respostas apresentadas aqui e em outros lugares.

Como o @Marco apontou os diferentes casos em que um 404 pode acontecer, verifiquei a solução que compilei nessa lista. Além de sua lista de requisitos, também adicionei mais um.

  • A solução deve ser capaz de lidar com chamadas MVC e AJAX / WebAPI da maneira mais apropriada. (ou seja, se 404 acontecer no MVC, ele deverá mostrar a página Não Encontrado e se 404 ocorrer no WebAPI, não deverá seqüestrar a resposta XML / JSON para que o Javascript consumidor possa analisá-la facilmente).

Esta solução é 2 vezes:

A primeira parte vem de @Guillaume em https://stackoverflow.com/a/27354140/2310818 . Sua solução cuida de qualquer 404 causado por rota inválida, controlador inválido e ação inválida.

A idéia é criar um formulário da Web e, em seguida, chamar a ação NotFound do seu controlador de erros do MVC. Ele faz tudo isso sem nenhum redirecionamento, para que você não veja um único 302 no Fiddler. O URL original também é preservado, o que torna esta solução fantástica!


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

A idéia é fazer um filtro examinar a resposta, bem como a exceção lançada por seus controladores MVC, e chamar a ação apropriada em seu Controlador de Erros. Novamente, esta solução funciona sem nenhum redirecionamento e o URL original é preservado!


Como você pode ver, essas duas soluções juntas oferecem um mecanismo de tratamento de erros muito robusto e atendem a todos os requisitos listados pelo @Marco, bem como aos meus requisitos. Se você quiser ver uma amostra de trabalho ou uma demonstração dessa solução, deixe nos comentários e ficarei feliz em reuni-la.

Parth Shah
fonte
0

Eu já passei por todos os artigos, mas nada funciona para mim: meu requisito, o tipo de usuário, qualquer coisa na sua página 404 personalizada de url deve aparecer.

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

Achei este artigo muito útil. Deve ser lido de uma só vez. Página de erro de custódia-Ben Foster

Thakur Rock
fonte