Jak sprawić, by niestandardowe strony błędów działały w ASP.NET MVC 4

247

Chcę wyświetlić niestandardową stronę błędu dla 500, 404 i 403. Oto, co zrobiłem:

  1. Włączono niestandardowe błędy w pliku web.config w następujący sposób:

    <customErrors mode="On" 
                  defaultRedirect="~/Views/Shared/Error.cshtml">
    
        <error statusCode="403" 
               redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
    
        <error statusCode="404" 
               redirect="~/Views/Shared/FileNotFound.cshtml" />
    
    </customErrors>
    
  2. Zarejestrowany HandleErrorAttributejako globalny filtr akcji w FilterConfigklasie w następujący sposób:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomHandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
  3. Utworzono niestandardową stronę błędu dla każdego z powyższych komunikatów. Domyślny dla 500 był już dostępny od razu po wyjęciu z pudełka.

  4. W każdym niestandardowym widoku strony błędu zadeklarowano, że jest to model strony System.Web.Mvc.HandleErrorInfo

W przypadku 500 wyświetla niestandardową stronę błędu. Dla innych tak nie jest.

Czy czegoś brakuje?

Wygląda na to, że to nie wszystko, aby wyświetlić niestandardowe błędy, gdy czytam kod w OnExceptionmetodzie HandleErrorAttributeklasy i obsługuje on tylko 500.

Co muszę zrobić, aby obsłużyć inne błędy?

Chłodnica wody v2
źródło
21
Dziwne w tej konfiguracji jest to, że przekierowujesz do widoków, a nie działań kontrolera. Kto ma na przykład renderować te widoki i przekazywać w modelu? Tylko myślenie.
Oliver
2
Większość odpowiedzi tutaj albo nie obsługuje wszystkich przypadków, albo powoduje, że serwer WWW odpowiada w „niepoprawny” sposób, tj. Przekierowuje na stronę błędu, zamiast zwracać odpowiedź na błąd. Jeśli zależy Ci na tym, aby serwer odpowiadał w sposób zgodny z serwerami WWW, znajdziesz tutaj dość szczegółowy artykuł: benfoster.io/blog/aspnet-mvc-custom-error-pages . Ostrzegamy, że nie jest to tak proste, jak odpowiedzi tutaj, więc jeśli chcesz uzyskać łatwą odpowiedź, skorzystaj z jednej z poniższych.
rdans
1
Oto kolejny świetny artykuł na temat różnych technik obsługi błędów asp.net dusted.codes/…
Godsayah

Odpowiedzi:

352

Moja obecna konfiguracja (na MVC3, ale myślę, że nadal obowiązuje) polega na posiadaniu ErrorController, więc używam:

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

Kontroler zawiera następujące elementy:

public class ErrorController : Controller
{
    public ViewResult Index()
    {
        return View("Error");
    }
    public ViewResult NotFound()
    {
        Response.StatusCode = 404;  //you may want to set this to 200
        return View("NotFound");
    }
}

I widoki dokładnie tak, jak je wdrażasz. Staram się jednak dodawać trochę logiki, aby pokazać informacje o śladzie stosu i błędach, jeśli aplikacja jest w trybie debugowania. Błąd Error.cshtml wygląda mniej więcej tak:

@model System.Web.Mvc.HandleErrorInfo
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "Error";
}
<div class="list-header clearfix">
    <span>Error</span>
</div>
<div class="list-sfs-holder">
    <div class="alert alert-error">
        An unexpected error has occurred. Please contact the system administrator.
    </div>
    @if (Model != null && HttpContext.Current.IsDebuggingEnabled)
    {
        <div>
            <p>
                <b>Exception:</b> @Model.Exception.Message<br />
                <b>Controller:</b> @Model.ControllerName<br />
                <b>Action:</b> @Model.ActionName
            </p>
            <div style="overflow:scroll">
                <pre>
                    @Model.Exception.StackTrace
                </pre>
            </div>
        </div>
    }
</div>
Pablo Romeo
źródło
7
Czy dla tego Pablo musiałeś umieścić coś w swoim błędzie Application_Error w Global.asax?
Alicia,
12
Z mojego doświadczenia wydaje się, że kod w kontrolerze nie jest wykonywany. MVC4 - zgłoszenie wyjątku System.Exception w innym kontrolerze spowoduje, że plik Error.cshtml będzie renderowany, ale nie przez ErrorController. Czy ktoś jeszcze tego doświadcza?
Nilzor
53
Dla każdego, kto uznał to za pomocne, ale potrzebowało więcej kontekstu; Tag <customErrors> wchodzi do <system.web> w web.config.
gooberverse
7
Aktualizacja dla innych - widocznie mój problem się dzieje, ponieważ miałem redirectMode="ResponseRewrite"na CustomerErrorselemencie
KyleMit
42
Proszę, na miłość boską, zignoruj ​​komentarz //you may want to set this to 200w kodzie. NIE RÓB TEGO!
Demencja
40

Zrobiłem rozwiązanie Pablo i zawsze miałem błąd (MVC4)

Widok „Błąd” lub jego wzorzec nie został znaleziony lub żaden silnik widoku nie obsługuje szukanej lokalizacji.

Aby się tego pozbyć, usuń linię

 filters.Add(new HandleErrorAttribute());

w FilterConfig.cs

Machinegon
źródło
Wszędzie szukałem rozwiązania tego problemu. To w końcu miało odpowiedź. Wiedziałem, dlaczego to robię, ale do cholery nie mogłem, nie myśląc drastycznie jak słowa innych ludzi. Wyobrażam sobie, że podzielam ból 360Airwalk, kiedy mówię dziękuję za zwrócenie na to uwagi. Legenda!
Adam,
Jest to jedna opcja i kontroler błędów działa dobrze. Wygląda jednak na to, że rejestrując filtry w FilterConfig.cs, szuka Error.cshtml w udostępnionych i oryginalnych folderach widoku kontrolerów. Gdy zmienisz Error.cshtml na cokolwiek innego, niż działa nasz niestandardowy kontroler błędów ErrorController. Ale jest miejsce, w którym możesz dodać tę rejestrację i jest to global.asax.cs. Jeśli dodasz wspomnianą linię w funkcji RegisterGlobalFilters (filtry GlobalFilterCollection) w global.asax.cs i usuniesz z FilterConfig.cs, to zadziała.
isaolmez
Myślę, że jest to związane z kolejnością rejestracji filtrów. Zachowaj kontroler błędów i przenieś rejestrację filtra do global.asax.cs. public static void RegisterGlobalFilters (GlobalFilterCollection filtry) {filter.Add (new HandleErrorAttribute ()); }
isaolmez,
24

Robię coś, co wymaga mniej kodowania niż inne opublikowane rozwiązania.

Po pierwsze, w moim pliku web.config mam następujące elementy:

<customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
   <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
   <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
</customErrors>

Kontroler (/Controllers/ErrorPageController.cs) zawiera następujące elementy:

public class ErrorPageController : Controller
{
    public ActionResult Oops(int id)
    {
        Response.StatusCode = id;

        return View();
    }
}

Wreszcie widok zawiera następujące elementy (uproszczone, ale może zawierać:

@{ ViewBag.Title = "Oops! Error Encountered"; }

<section id="Page">
  <div class="col-xs-12 well">
    <table cellspacing="5" cellpadding="3" style="background-color:#fff;width:100%;" class="table-responsive">
      <tbody>
        <tr>
          <td valign="top" align="left" id="tableProps">
            <img width="25" height="33" src="~/Images/PageError.gif" id="pagerrorImg">
          </td>
          <td width="360" valign="middle" align="left" id="tableProps2">
            <h1 style="COLOR: black; FONT: 13pt/15pt verdana" id="errortype"><span id="errorText">@Response.Status</span></h1>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth"><font style="COLOR: black; FONT: 8pt/11pt verdana">Possible causes:</font>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth2">
            <font style="COLOR: black; FONT: 8pt/11pt verdana" id="LID1">
                            <hr>
                            <ul>
                                <li id="list1">
                                    <span class="infotext">
                                        <strong>Baptist explanation: </strong>There
                                        must be sin in your life. Everyone else opened it fine.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Presbyterian explanation: </strong>It's
                                        not God's will for you to open this link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong> Word of Faith explanation:</strong>
                                        You lack the faith to open this link. Your negative words have prevented
                                        you from realizing this link's fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Charismatic explanation: </strong>Thou
                                        art loosed! Be commanded to OPEN!<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Unitarian explanation:</strong> All
                                        links are equal, so if this link doesn't work for you, feel free to
                                        experiment with other links that might bring you joy and fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Buddhist explanation:</strong> .........................<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Episcopalian explanation:</strong>
                                        Are you saying you have something against homosexuals?<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Christian Science explanation: </strong>There
                                        really is no link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Atheist explanation: </strong>The only
                                        reason you think this link exists is because you needed to invent it.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Church counselor's explanation:</strong>
                                        And what did you feel when the link would not open?
                                    </span>
                                </li>
                            </ul>
                            <p>
                                <br>
                            </p>
                            <h2 style="font:8pt/11pt verdana; color:black" id="ietext">
                                <img width="16" height="16" align="top" src="~/Images/Search.gif">
                                HTTP @Response.StatusCode - @Response.StatusDescription <br>
                            </h2>
                        </font>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

To jest tak proste. Można go łatwo rozszerzyć, aby oferować bardziej szczegółowe informacje o błędach, ale ELMAH obsługuje to dla mnie, a statusCode i statusDescription to wszystko, czego zwykle potrzebuję.

coderpro.net
źródło
Myślę, że przekierowaniem w pliku .config pliku „~ / ErrorPage / Oops / 404” prawdopodobnie powinno być „~ / ErrorPage / Oops? 404”, prawda? Przynajmniej to działało dla mnie. Może to zależy tylko od trasy.
Josh Sutterfield,
Jak zasymulować błąd zgłoszony przez IIS. Czy to 500, czy 504. Co zrobić w kodzie ASP.Net MVC - 5, aby zasymulować wyjątek od IIS, dzięki czemu mogę przetestować swoją niestandardową stronę błędu
Niezniszczalny
12

Wydaje się, że jest tu kilka pomieszanych kroków. Przedstawię to, co zrobiłem od zera.

  1. Utwórz ErrorPagekontroler

    public class ErrorPageController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    
        public ActionResult Oops(int id)
        {
            Response.StatusCode = id;
            return View();
        }
    }
  2. Dodaj widoki dla tych dwóch akcji (kliknij prawym przyciskiem myszy -> Dodaj widok). Powinny one pojawić się w folderze o nazwie ErrorPage.

  3. Wewnątrz App_Startotwórz FilterConfig.csi skomentuj filtr obsługi błędów.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Remove this filter because we want to handle errors ourselves via the ErrorPage controller
        //filters.Add(new HandleErrorAttribute());
    }
  4. Wewnątrz web.config dodaj następujące <customerErrors>wpisy, w obszarzeSystem.Web

    <customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
        <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
        <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
    </customErrors>
  5. Test (oczywiście). Rzuć nieobsługiwany wyjątek w kodzie i zobacz, jak przechodzi na stronę o identyfikatorze 500, a następnie użyj adresu URL do strony, która nie istnieje, aby wyświetlić 404.

VictorySaber
źródło
Otrzymywałem ten błąd. An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.Wszystko, co odebrałem z twojego kodu, znajduje się w pliku web.config, dodałem <error redirect = "~/ControllerName/ActionName" statusCode="404"/>i działało dobrze :) Reszta kodu pochodzi z odpowiedzi @ Pablo. Używam MVC 5 i szkieletu encji 6. Nie filters.Add(new HandleErrorAttribute())FilterConfig.cs
usunąłem
Jak zasymulować błąd zgłoszony przez IIS. Czy to 500, czy 504. Co zrobić w kodzie ASP.Net MVC - 5, aby zasymulować wyjątek od IIS, dzięki czemu mogę przetestować swoją niestandardową stronę błędu
Niezniszczalny
Ponadto, jak zgłosić nieobsługiwany wyjątek (krok 5). Jestem nowy w kodowaniu, proszę o poradnik.
Niezniszczalny
Nadal nie działa dla mnie? Co z routingiem? Czy muszę też dodać stronę Routing for Error? Jeśli trafię na stronę: localhost: 84 / Enforcer / blah zostaniesz przekierowany do: localhost: 84 / Enforcer / Enforcer / Error / NotFound? Aspxerrorpath = /… Strona błędu wygląda jak standardowa strona błędu dostarczona przez Asp.NET. Jakieś pomysły?
Radek Strugalski
Element customerrors w webconfig powinien to robić. Twój (utworzony w projekcie) domyślny kod trasy powinien działać dobrze.
VictorySaber
11

Poleciłbym użyć pliku Global.asax.cs.

 protected void Application_Error(Object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    {
        Server.Transfer("~/Error.aspx");
    }
    if (exception != null)
    {
        Server.Transfer("~/Error.aspx");
    }
    try
    {
        // This is to stop a problem where we were seeing "gibberish" in the
        // chrome and firefox browsers
        HttpApplication app = sender as HttpApplication;
        app.Response.Filter = null;
    }
    catch
    {
    }
}
maxspan
źródło
1
Nie sądziłem, że możesz wykonać Server.Transfer () w MVC. Czy uważasz, że PO ma mieszaną stronę?
Rap
1
dlaczego powinniśmy używać Application_Error w mvc? Mamy opcje takie jak [handleerror] przypisywane z opcjami adresu URL przekierowania. Czy jest z tym jakaś konkretna zaleta?
Kurkula,
Powinniśmy użyć HandleErrorAttribute w MVC i poprzez przesłonięcie metody OnException możemy sobie z nimi poradzić w znacznie lepszy sposób
Kumar Lachhani
7

Opierając się na odpowiedzi opublikowanej przez maxspan, przygotowałem minimalny przykładowy projekt na GitHub pokazujący wszystkie działające części.

Zasadniczo po prostu dodajemy Application_Errormetodę do global.asax.cs, aby przechwycić wyjątek i dać nam możliwość przekierowania (lub bardziej poprawnie, żądania przeniesienia ) na niestandardową stronę błędu.

    protected void Application_Error(Object sender, EventArgs e)
    {
        // See http://stackoverflow.com/questions/13905164/how-to-make-custom-error-pages-work-in-asp-net-mvc-4
        // for additional context on use of this technique

        var exception = Server.GetLastError();
        if (exception != null)
        {
            // This would be a good place to log any relevant details about the exception.
            // Since we are going to pass exception information to our error page via querystring,
            // it will only be practical to issue a short message. Further detail would have to be logged somewhere.

            // This will invoke our error page, passing the exception message via querystring parameter
            // Note that we chose to use Server.TransferRequest, which is only supported in IIS 7 and above.
            // As an alternative, Response.Redirect could be used instead.
            // Server.Transfer does not work (see https://support.microsoft.com/en-us/kb/320439 )
            Server.TransferRequest("~/Error?Message=" + exception.Message);
        }

    }

Kontroler błędów:

/// <summary>
/// This controller exists to provide the error page
/// </summary>
public class ErrorController : Controller
{
    /// <summary>
    /// This action represents the error page
    /// </summary>
    /// <param name="Message">Error message to be displayed (provided via querystring parameter - a design choice)</param>
    /// <returns></returns>
    public ActionResult Index(string Message)
    {
        // We choose to use the ViewBag to communicate the error message to the view
        ViewBag.Message = Message;
        return View();
    }

}

Wyświetl stronę błędu:

<!DOCTYPE html>

<html>
<head>
    <title>Error</title>
</head>
<body>

    <h2>My Error</h2>
    <p>@ViewBag.Message</p>
</body>
</html>

Nic innego nie jest zaangażowany, inne niż wyłączenie / usunięcie filters.Add(new HandleErrorAttribute())w FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute()); // <== disable/remove
    }
}

Chociaż jest bardzo prosty do wdrożenia, jedyną wadą, którą widzę w tym podejściu, jest użycie kwerendy do dostarczania informacji o wyjątkach do docelowej strony błędu.

użytkownik3380909
źródło
3

Miałem wszystko skonfigurowane, ale nadal nie widziałem odpowiednich stron błędów dla kodu statusu 500 na naszym serwerze pomostowym, mimo że wszystko działało dobrze na lokalnych serwerach programistycznych.

Znalazłem ten post na blogu od Ricka Strahla, który mi pomógł.

Musiałem dodać Response.TrySkipIisCustomErrors = true;do mojego niestandardowego kodu obsługi błędów.

DCShannon
źródło
@ Shaun314 Masz na myśli, gdzie umieszczasz ten kod? W akcji, która obsługuje żądanie. Możesz zobaczyć przykłady w tym poście na blogu.
DCShannon,
2

Oto moje rozwiązanie. Używanie [ExportModelStateToTempData] / [ImportModelStateFromTempData] jest moim zdaniem niewygodne.

~ / Widoki / Strona główna / Error.cshtml:

@{
    ViewBag.Title = "Error";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Error</h2>
<hr/>

<div style="min-height: 400px;">

    @Html.ValidationMessage("Error")

    <br />
    <br />

    <button onclick="Error_goBack()" class="k-button">Go Back</button>
    <script>
        function Error_goBack() {
            window.history.back()
        }
    </script>

</div>

~ / Kontrolery / HomeController.sc:

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Error()
    {
        return this.View();
    }

    ...
}

~ / Kontrolery / BaseController.sc:

public class BaseController : Controller
{
    public BaseController() { }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            if (filterContext.Controller.TempData.ContainsKey("Error"))
            {
                var modelState = filterContext.Controller.TempData["Error"] as ModelState;
                filterContext.Controller.ViewData.ModelState.Merge(new ModelStateDictionary() { new KeyValuePair<string, ModelState>("Error", modelState) });
                filterContext.Controller.TempData.Remove("Error");
            }
        }
        if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
        {
            if (filterContext.Controller.ViewData.ModelState.ContainsKey("Error"))
            {
                filterContext.Controller.TempData["Error"] = filterContext.Controller.ViewData.ModelState["Error"];
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

~ / Kontrolery / MyController.sc:

public class MyController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Details(int id)
    {
        if (id != 5)
        {
            ModelState.AddModelError("Error", "Specified row does not exist.");
            return RedirectToAction("Error", "Home");
        }
        else
        {
            return View("Specified row exists.");
        }
    }
}

Życzę udanych projektów ;-)

PRZYZNAĆ
źródło
2

Błędy mogą działać poprawnie bez włamywania się do pliku global.cs, nie zadzieraj z HandleErrorAttribute, wykonując Response.TrySkipIisCustomErrors, podłączając błąd Application_Error lub cokolwiek innego:

W system.web (zwykłe włączanie / wyłączanie)

<customErrors mode="On">
  <error redirect="/error/401" statusCode="401" />
  <error redirect="/error/500" statusCode="500" />
</customErrors>

oraz w system.webServer

<httpErrors existingResponse="PassThrough" />

Teraz wszystko powinno działać zgodnie z oczekiwaniami, a za pomocą narzędzia ErrorController możesz wyświetlać wszystko, czego potrzebujesz.

Robert Hoffmann
źródło
Jak zasymulować błąd zgłoszony przez IIS. Czy to 500, czy 504. Co zrobić w kodzie ASP.Net MVC - 5, aby zasymulować wyjątek od IIS, dzięki czemu mogę przetestować swoją niestandardową stronę błędu
Niezniszczalny
@ Niezniszczalny tymczasowo zmień kod, aby zgłosić wyjątek.
theycallmemorty
Nie zrobiło to dla mnie różnicy. Nie przechodzę do mojej niestandardowej strony błędu z powodu wyjątku lub błędu 404 nie znaleziono.
pnizzle
0

Wygląda na to, że spóźniłem się na przyjęcie, ale lepiej też to sprawdzić.

Więc system.webdo buforowania wyjątków w aplikacji, takich jak return HttpNotFound ()

  <system.web>
    <customErrors mode="RemoteOnly">
      <error statusCode="404" redirect="/page-not-found" />
      <error statusCode="500" redirect="/internal-server-error" />
    </customErrors>
  </system.web>

i system.webServerdo nadrabiania błędów, które zostały złapane przez IIS i nie dotarły do ​​frameworka asp.net

 <system.webServer>
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/page-not-found" responseMode="Redirect"/>
      <remove statusCode="500"/>
      <error statusCode="500" path="/internal-server-error" responseMode="Redirect"/>
  </system.webServer>

W tej ostatniej, jeśli martwić odpowiedzi klienta następnie zmienić responseMode="Redirect"się responseMode="File"i służyć plik HTML statyczne, ponieważ ten pokaże przyjazną stronę z kodem 200 odpowiedzi.

Albo
źródło
0

W pliku web.config dodaj to pod znacznikiem system.webserver, jak poniżej,

<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="404"/>
  <remove statusCode="500"/>
  <error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound"/>
  <error statusCode="500" responseMode="ExecuteURL"path="/Error/ErrorPage"/>
</httpErrors>

i dodaj kontroler jako,

public class ErrorController : Controller
{
    //
    // GET: /Error/
    [GET("/Error/NotFound")]
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;

        return View();
    }

    [GET("/Error/ErrorPage")]
    public ActionResult ErrorPage()
    {
        Response.StatusCode = 500;

        return View();
    }
}

i dodać ich szanowane poglądy, to na pewno zadziała chyba dla wszystkich.

To rozwiązanie znalazłem w: Neptune Century

Dpk-Kumar
źródło