Niestandardowe strony błędów w asp.net MVC3

144

Tworzę witrynę bazową MVC3 i szukam rozwiązania do obsługi błędów i renderowania niestandardowych widoków dla każdego rodzaju błędu. Wyobraź sobie więc, że mam kontroler „Error”, którego główną akcją jest „Index” (ogólna strona błędu), a ten kontroler będzie miał jeszcze kilka akcji dla błędów, które mogą pojawić się użytkownikowi, jak „Handle500” lub „HandleActionNotFound”.

Tak więc każdy błąd, który może wystąpić na stronie internetowej, może zostać obsłużony przez ten kontroler „Błąd” (przykłady: „Nie znaleziono kontrolera” lub „Akcji”, 500, 404, wyjątek dbException, itp.).

Używam pliku mapy witryny do definiowania ścieżek witryny (a nie trasy).

Na to pytanie już odpowiedziano, to jest odpowiedź dla Gweebz

Moja ostatnia metoda applicaiton_error jest następująca:

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");
    }
}
}
John Louros
źródło
Jakie ustawienia powinny być w pliku web.config, aby to obsługiwać? Prawdopodobnie nie uwzględniłbyś żadnych ustawień httperrors?
philbird
forums.asp.net/p/1782402/4894514.aspx/… zawiera kilka fajnych wskazówek, takich jak IE nie pokaże strony błędu, jeśli ma mniej niż 512 bajtów
RickAndMSFT

Odpowiedzi:

201

Oto przykład, jak radzę sobie z niestandardowymi błędami. Definiuję ErrorsControllerz akcjami obsługującymi różne błędy 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");
    }
}

a następnie zapisuję się na Application_Errorin Global.asaxi wywołuję ten kontroler:

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);
}
Darin Dimitrov
źródło
4
Tylko mała uwaga. Ponieważ chciałem wyrenderować widok w każdym przypadku (404, 500 itd.) W każdym ActionResult, zwróciłem View. Jednak spróbowałem złapać zawartość Application_Error i w przypadku niepowodzenia zwracana jest statyczna strona HTML. (Mogę wysłać kod, jeśli ktoś zechce)
John Louros
4
Nie mogę renderować widoków maszynki do golenia przy użyciu tego rozwiązania na MVC3. return Widok (model) na przykład otrzymuje tylko pusty ekran.
Extrakun
2
Dodano TrySkipIisCustomErrors, aby naprawić to dla zintegrowanych usług IIS7. Zobacz stackoverflow.com/questions/1706934/…
Pavel Savara
1
@ajbeaven, Executeto metoda zdefiniowana w IControllerinterfejsie. To nie może być chronione. Przyjrzyj się uważniej mojemu kodowi: IController errorsController = new ErrorsController();i zwróć uwagę na typ errorsControllerzmiennej, dla której wywołuję Executemetodę. Jest typowy, IControllerwięc nic nie stoi na przeszkodzie, aby wywołać tę metodę. Nawiasem mówiąc, Executebył chroniony również w klasie Controller w MVC 3, więc nie ma zmiany w tym względzie.
Darin Dimitrov
2
Naprawiono przez jawne określenie typu treści odpowiedzi:Response.ContentType = "text/html";
ajbeaven,
6

Możesz to również zrobić w pliku Web.Config. Oto przykład, który działa w usługach 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>
Brett Allred
źródło
3

Widzę, że dodałeś wartość konfiguracyjną dla EnableCustomErrorPagei również sprawdzaszIsDebuggingEnabled czy uruchomić obsługę błędów.

Ponieważ istnieje już <customErrors/>konfiguracja w ASP.NET (która jest przeznaczona dokładnie do tego celu), najłatwiej jest po prostu powiedzieć:

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

Następnie w konfiguracji, którą umieścisz, <customErrors mode="RemoteOnly" />która jest bezpieczna do wdrożenia w ten sposób, a kiedy chcesz przetestować niestandardową stronę błędu, ustaw ją na<customErrors mode="On" /> , abyś mógł sprawdzić, czy działa.

Zauważ, że musisz również sprawdzić, czy HttpContext.Currentjest null, ponieważ wyjątek w Application_Startnadal będzie używał tej metody, chociaż nie będzie aktywnego kontekstu.

Simon_Weaver
źródło
2

Możesz wyświetlić przyjazną dla użytkownika stronę błędu z poprawnym kodem statusu http, implementując moduł Przyjaznej dla użytkownika obsługi wyjątków Jeffa Atwooda z niewielką modyfikacją kodu statusu http. Działa bez przekierowań. Chociaż kod pochodzi z 2004 roku (!), Dobrze współpracuje z MVC. Można go całkowicie skonfigurować w pliku web.config, bez żadnych zmian w kodzie źródłowym projektu MVC.

Modyfikacja wymagana do zwrócenia oryginalnego stanu HTTP zamiast 200statusu jest opisana w tym pokrewnym poście na forum .

Zasadniczo w Handler.vb możesz dodać coś takiego:

' 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
Martin_W
źródło
0

Używam MVC 4.5 i miałem problemy z rozwiązaniem Darina. Uwaga: rozwiązanie Darina jest doskonałe i użyłem go do wymyślenia mojego rozwiązania. Oto moje zmodyfikowane rozwiązanie:

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);
    }
}
}
MVCdragon
źródło
2
Jakie problemy miałeś z rozwiązaniem Darina?
Kenny Evitt
Nie opisałeś napotkanego problemu, który doprowadził do konkurencyjnej odpowiedzi.
ivanjonas