Páginas de erro personalizadas no asp.net MVC3


144

Estou desenvolvendo um site de base MVC3 e estou procurando uma solução para lidar com erros e renderizar visualizações personalizadas para cada tipo de erro. Imagine que eu tenho um controlador "Erro", onde sua ação principal é "Índice" (página de erro genérica) e esse controlador terá mais algumas ações para os erros que podem aparecer para o usuário como "Handle500" ou "HandleActionNotFound".

Portanto, todo erro que possa ocorrer no site pode ser tratado por esse controlador "Erro" (exemplos: "Controlador" ou "Ação" não encontrado, 500, 404, dbException etc.).

Estou usando o arquivo Sitemap para definir os caminhos do site (e não o roteamento).

Esta pergunta já foi respondida, esta é uma resposta para Gweebz

Meu método applicaiton_error final é o seguinte:

protected void Application_Error() {
//while my project is running in debug mode
if (HttpContext.Current.IsDebuggingEnabled && WebConfigurationManager.AppSettings["EnableCustomErrorPage"].Equals("false"))
{
    Log.Logger.Error("unhandled exception: ", Server.GetLastError());
}
else
{
    try
    {
        var exception = Server.GetLastError();

        Log.Logger.Error("unhandled exception: ", exception);

        Response.Clear();
        Server.ClearError();
        var routeData = new RouteData();
        routeData.Values["controller"] = "Errors";
        routeData.Values["action"] = "General";
        routeData.Values["exception"] = exception;

        IController errorsController = new ErrorsController();
        var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
        errorsController.Execute(rc);
    }
    catch (Exception e)
    {
        //if Error controller failed for same reason, we will display static HTML error page
        Log.Logger.Fatal("failed to display error page, fallback to HTML error: ", e);
        Response.TransmitFile("~/error.html");
    }
}
}

Quais devem ser as configurações no web.config para suportar isso? Provavelmente, você não incluiria nenhuma configuração de espelhos de ativação?
philbird

forums.asp.net/p/1782402/4894514.aspx/... tem algumas sugestões agradáveis como o IE não vai mostrar sua página de erro se estiver sob 512 bytes
RickAndMSFT

Respostas:


201

Aqui está um exemplo de como eu manejo erros personalizados. Eu defino um ErrorsControllercom ações que manipulam diferentes erros HTTP:

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        return Content("General failure", "text/plain");
    }

    public ActionResult Http404()
    {
        return Content("Not found", "text/plain");
    }

    public ActionResult Http403()
    {
        return Content("Forbidden", "text/plain");
    }
}

e depois assino o Application_Errorin Global.asaxe invoco esse controlador:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();
    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}

4
Apenas uma pequena nota. Como eu queria renderizar uma View em cada caso (404, 500, etc) em cada ActionResult, retornei uma View. No entanto, tentei capturar o conteúdo de Application_Error e, em caso de falha, uma página HTML estática é retornada. (Eu posso postar o código se alguém desejar)
John Louros

4
Não consigo obter visualizações nítidas para renderizar usando esta solução no MVC3. return View (model), por exemplo, só recebe uma tela em branco.
Extrakun 13/10/11

2
Adicionado TrySkipIisCustomErrors para corrigi-lo no IIS7 integrado. Veja stackoverflow.com/questions/1706934/…
Pavel Savara

1
@ajbeaven, Executeé um método definido na IControllerinterface. Isso não pode ser protegido. Olhe meu código com mais cuidado: IController errorsController = new ErrorsController();e observe o tipo da errorsControllervariável na qual estou invocando o Executemétodo. É do tipo, IControllerentão não há absolutamente nada impedindo que você chame esse método. E, a propósito, Executefoi protegido na classe Controller, bem como no MVC 3, então não há mudanças a esse respeito.
precisa

2
Corrigido especificando explicitamente o tipo de conteúdo da resposta:Response.ContentType = "text/html";
ajbeaven 20/12/13


6

Você também pode fazer isso no arquivo Web.Config. Aqui está um exemplo que funciona no IIS 7.5.

     <system.webServer>
          <httpErrors errorMode="DetailedLocalOnly" defaultResponseMode="File">
                <remove statusCode="502" subStatusCode="-1" />
                <remove statusCode="501" subStatusCode="-1" />
                <remove statusCode="412" subStatusCode="-1" />
                <remove statusCode="406" subStatusCode="-1" />
                <remove statusCode="405" subStatusCode="-1" />
                <remove statusCode="404" subStatusCode="-1" />
                <remove statusCode="403" subStatusCode="-1" />
                <remove statusCode="401" subStatusCode="-1" />
                <remove statusCode="500" subStatusCode="-1" />
                <error statusCode="500" path="/notfound.html" responseMode="ExecuteURL" />
                <error statusCode="401" prefixLanguageFilePath="" path="/500.html" responseMode="ExecuteURL" />
                <error statusCode="403" prefixLanguageFilePath="" path="/403.html" responseMode="ExecuteURL" />
                <error statusCode="404" prefixLanguageFilePath="" path="/404.html" responseMode="ExecuteURL" />
                <error statusCode="405" prefixLanguageFilePath="" path="/405.html" responseMode="ExecuteURL" />
                <error statusCode="406" prefixLanguageFilePath="" path="/406.html" responseMode="ExecuteURL" />
                <error statusCode="412" prefixLanguageFilePath="" path="/412.html" responseMode="ExecuteURL" />
                <error statusCode="501" prefixLanguageFilePath="" path="/501.html" responseMode="ExecuteURL" />
                <error statusCode="502" prefixLanguageFilePath="" path="/genericerror.html" responseMode="ExecuteURL" />
           </httpErrors>
</system.webServer>

3

Vejo que você adicionou um valor de configuração EnableCustomErrorPagee também está verificando IsDebuggingEnabledse deseja executar ou não o tratamento de erros.

Como já existe uma <customErrors/>configuração no ASP.NET (que é exatamente para esse fim), é mais fácil dizer:

    protected void Application_Error()
    {
        if (HttpContext.Current == null) 
        {
                // errors in Application_Start will end up here                
        }
        else if (HttpContext.Current.IsCustomErrorEnabled)
        {
                // custom exception handling
        }
    }

Em seguida, na configuração que você colocaria, <customErrors mode="RemoteOnly" />que é seguro implantar dessa maneira, e quando precisar testar sua página de erro personalizada, defina-a <customErrors mode="On" />para que você possa verificar se funciona.

Observe que você também precisa verificar se HttpContext.Currenté nulo, pois uma exceção Application_Startainda será esse método, embora não haja um contexto ativo.


2

Você pode exibir uma página de erro fácil de usar com o código de status http correto, implementando o módulo User Friendly Exception Handling, de Jeff Atwood, com uma pequena modificação no código de status http. Funciona sem redirecionamentos. Embora o código seja de 2004 (!), Ele funciona bem com o MVC. Ele pode ser configurado inteiramente no seu web.config, sem nenhuma alteração no código-fonte do projeto MVC.

A modificação necessária para retornar o status HTTP original em vez de um 200 status é descrita nesta postagem do fórum relacionada .

Basicamente, no Handler.vb, você pode adicionar algo como:

' In the header...
Private _exHttpEx As HttpException = Nothing

' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
    _exHttpEx = CType(ex, HttpException)
    HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If

0

Estou usando o MVC 4.5 e estava tendo problemas com a solução de Darin. Nota: A solução de Darin é excelente e eu a usei para criar minha solução. Aqui está minha solução modificada:

protected void Application_Error(object sender, EventArgs e)
{           
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.StatusCode = httpException.GetHttpCode();

Response.Clear();
Server.ClearError();


if (httpException != null)
{
    var httpContext = HttpContext.Current;

    httpContext.RewritePath("/Errors/InternalError", false);

    // MVC 3 running on IIS 7+
    if (HttpRuntime.UsingIntegratedPipeline)
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.Server.TransferRequest("/Errors/Http403", true);
                break;
            case 404:
                httpContext.Server.TransferRequest("/Errors/Http404", true);
                break;
            default:
                httpContext.Server.TransferRequest("/Errors/InternalError", true);
                break;
        }
    }
    else
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.RewritePath(string.Format("/Errors/Http403", true));
                break;
            case 404:
                httpContext.RewritePath(string.Format("/Errors/Http404", true));
                break;
            default:
                httpContext.RewritePath(string.Format("/Errors/InternalError", true));
                break;
        }

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

2
Que problemas você estava tendo com a solução de Darin?
Kenny Evitt

Você não descreveu o problema que causou uma resposta competitiva.
ivanjonas
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.