Application_Error nie uruchamia się, gdy customerrors = „On”

124

Mam kod w zdarzeniu global.asaxpliku, Application_Errorktóry jest wykonywany, gdy wystąpi błąd i wysyła do mnie e-mail ze szczegółami błędu.

void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();

    if (error.Message != "Not Found")
    {
        // Send email here...
    }

}

Działa to dobrze, gdy uruchamiam go w programie Visual Studio, jednak gdy publikuję na naszym serwerze na żywo, Application_Errorzdarzenie nie jest uruchamiane.

Po kilku testach mogę uzyskać Application_Errorodpalenie, gdy ustawię customErrors="Off", jednak ustawienie go z powrotem, aby customErrors="On"zatrzymać ponowne uruchomienie zdarzenia.

Czy ktoś może zasugerować, dlaczego Application_Errornie miałby strzelać, gdy customErrorssą włączone w web.config?

WDuffy
źródło
Mam dokładnie ten sam problem. Znalazłem również to pytanie SO: stackoverflow.com/questions/3713939/ ... które sugeruje ustawienie serwera IIS7 w trybie klasycznym. Niestety nie jest to dla nas opcja. Czy ktoś ma lepsze rozwiązania?
Jesse Webb
Oto kolejne powiązane pytanie, a jego odpowiedzi (z których żadna nie jest akceptowana) sugerują, aby w ogóle nie używać Application_Error () ... stackoverflow.com/questions/1194578/
Jesse Webb
@Gweebz Wysłałem odpowiedź, jak sobie z tym poradziłem, ale nadal nie znalazłem żadnej solidnej dokumentacji na temat tego, dlaczego otrzymuję takie zachowanie.
WDuffy,
Dodałem odpowiedź, która wyjaśnia, dlaczego Application_Error()metoda nie została wywołana. Wyjaśniłem również moje ostateczne rozwiązanie.
Jesse Webb
Naprawdę polecam ten artykuł , aby niestandardowe błędy działały.
ThomasArdal

Odpowiedzi:

133

AKTUALIZACJA
Ponieważ ta odpowiedź dostarcza rozwiązania, nie będę jej edytować, ale znalazłem znacznie czystszy sposób rozwiązania tego problemu. Zobacz moją drugą odpowiedź po szczegóły ...

Oryginalna odpowiedź:
Dowiedziałem się, dlaczego Application_Error()metoda nie jest wywoływana ...

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute()); // this line is the culprit
    }
...
}

Domyślnie (podczas generowania nowego projektu) aplikacja MVC ma pewną logikę w Global.asax.cspliku. Ta logika jest używana do mapowania tras i rejestrowania filtrów. Domyślnie rejestruje tylko jeden filtr: HandleErrorAttributefiltr. Gdy customErrors są włączone (lub przez żądania zdalne, gdy jest ustawiona na RemoteOnly), HandleErrorAttribute informuje MVC, aby szukał widoku błędu i nigdy nie wywołuje Application_Error()metody. Nie mogłem znaleźć dokumentacji na ten temat, ale jest to wyjaśnione w tej odpowiedzi na programmers.stackexchange.com .

Aby uzyskać metodę ApplicationError () wywoływaną dla każdego nieobsługiwanego wyjątku, po prostu usuń wiersz, który rejestruje filtr HandleErrorAttribute.

Teraz problem jest taki: jak skonfigurować customErrors, aby uzyskać to, czego chcesz ...

Sekcja customErrors jest domyślnie ustawiona na redirectMode="ResponseRedirect". Możesz również określić atrybut defaultRedirect jako trasę MVC. Stworzyłem ErrorController, który był bardzo prosty i zmieniłem mój web.config, aby wyglądał tak ...

web.config

<customErrors mode="RemoteOnly" redirectMode="ResponseRedirect" defaultRedirect="~/Error">
  <error statusCode="404" redirect="~/Error/PageNotFound" />
</customErrors>

Problem z tym rozwiązaniem polega na tym, że przekierowuje ono 302 do adresów URL błędów, a następnie te strony odpowiadają kodem stanu 200. Prowadzi to do indeksowania przez Google stron błędów, co jest złe. Nie jest też bardzo zgodny ze specyfikacją HTTP. Nie chciałem przekierować i zastąpić oryginalnej odpowiedzi moimi niestandardowymi widokami błędów.

Próbowałem się zmienić redirectMode="ResponseRewrite". Niestety ta opcja nie obsługuje tras MVC , tylko statyczne strony HTML lub ASPX. Na początku próbowałem użyć statycznej strony HTML, ale kod odpowiedzi wciąż miał 200, ale przynajmniej nie przekierowywał. Wtedy wpadłem na pomysł z tej odpowiedzi ...

Postanowiłem zrezygnować z MVC do obsługi błędów. Stworzyłem Error.aspxi PageNotFound.aspx. Te strony były bardzo proste, ale miały jeden kawałek magii ...

<script type="text/C#" runat="server">
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
    }
</script>

Ten blok informuje, że strona ma być obsługiwana z poprawnym kodem stanu. Z grubsza, na stronie PageNotFound.aspx użyłem HttpStatusCode.NotFoundzamiast tego. Zmieniłem plik web.config, aby wyglądał tak ...

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx">
  <error statusCode="404" redirect="~/PageNotFound.aspx" />
</customErrors>

Wszystko działało idealnie!

Podsumowanie:

  • Usuń linię: filters.Add(new HandleErrorAttribute());
  • Użyj Application_Error()metody do rejestrowania wyjątków
  • Użyj customErrors z ResponseRewrite, wskazując na strony ASPX
  • Spraw, aby strony ASPX były odpowiedzialne za własne kody stanu odpowiedzi

W przypadku tego rozwiązania zauważyłem kilka wad.

  • Strony ASPX nie mogą udostępniać żadnych znaczników z szablonami Razor, musiałem przepisać standardowe znaczniki nagłówka i stopki naszej witryny, aby uzyskać spójny wygląd i styl.
  • Dostęp do stron * .aspx można uzyskać bezpośrednio, klikając ich adresy URL

Istnieją sposoby obejścia tych problemów, ale nie przejmowałem się nimi na tyle, aby wykonać jakąkolwiek dodatkową pracę.

Mam nadzieję, że to pomoże wszystkim!

Jesse Webb
źródło
2
+1! Naprawdę dobre obejście, ale jakie mogą być konsekwencje mieszania MVC ze stronami aspx?
Diego
2
Mamy to w PROD już od kilku miesięcy i nie znalazłem ani negatywnego wpływu. Jedynym problemem było to, że musieliśmy zmienić nasz CI i wdrożyć, aby wykonać zadanie AspCompile MSBUILD, ponieważ MVC go nie potrzebuje, ale kiedy dodaliśmy pliki .as , wymagali tego. Mogą być inne problemy, ale jeszcze się nie pojawiły ...
Jesse Webb
Próbowałem użyć tego podejścia w MVC4 i najwyraźniej działa to tylko wtedy, gdy mam <customErrors mode="Off" />. Usunięcie filters.Add(new HandleErrorAttribute());lub nie ma żadnego efektu.
Grzegorz Sławecki
na stronie aspx możesz dodać wywołanie Ajax, aby wywołać widok, który chcesz pokazać. Oszczędza konieczność duplikowania kodu
Mike
71

Rozwiązałem ten problem, tworząc ExceptionFilter i zapisując tam błąd zamiast Application_Error. Wszystko, co musisz zrobić, to dodać wywołanie do w RegisterGlobalFilters

log4netExceptionFilter.cs

using System
using System.Web.Mvc;

public class log4netExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        Exception ex = context.Exception;
        if (!(ex is HttpException)) //ignore "file not found"
        {
            //Log error here
        }
    }
}

Global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new log4netExceptionFilter()); //must be before HandleErrorAttribute
    filters.Add(new HandleErrorAttribute());
}
znak
źródło
6
+1 Jeśli ta odpowiedź działa, z pewnością wydaje mi się najbardziej eleganckim i być może zamierzonym sposobem (według frameworka MVC) na obsługę błędów.
Matt Hamsmith
2
Działa to idealnie dla mnie i jest o wiele ładniejsze niż zaakceptowana tutaj odpowiedź. Miły!
Alex Warren,
3
To zadziała w celu rozwiązania problemu wyjątków rejestrowania. Jak połączyłeś to z wyświetlaniem niestandardowych stron błędów?
Jesse Webb
3
Spróbowałem tego i działało ładnie, z wyjątkiem 404. W przypadku 404 nie pokazywałoby widoku Errors.cshtml, dałoby mi tylko YSoD. Jeśli niestandardowe 404 nie są wymagane, to rozwiązanie jest zdecydowanie czystsze!
Jesse Webb,
1
Lubię to. Działa z ustawieniem „CustomErrors = On”
Illidan
36

Znalazłem artykuł, który opisuje znacznie bardziej przejrzysty sposób tworzenia niestandardowych stron błędów w aplikacji internetowej MVC3, który nie uniemożliwia rejestrowania wyjątków.

Rozwiązaniem jest użycie <httpErrors>elementu <system.webServer>sekcji.

Skonfigurowałem mój Web.config w ten sposób ...

<httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace">
  <remove statusCode="404" subStatusCode="-1" />
  <remove statusCode="500" subStatusCode="-1" />
  <error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
  <error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>

Skonfigurowałem również tak, customErrorsaby mieć mode="Off"(jak sugeruje artykuł).

To sprawia, że ​​odpowiedzi są zastępowane przez akcje ErrorControllera. Oto ten kontroler:

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult NotFound()
    {
        return View();
    }
}

Widoki są bardzo proste, po prostu użyłem standardowej składni Razor do stworzenia stron.

Samo to powinno wystarczyć do korzystania z niestandardowych stron błędów z MVC.

Potrzebowałem również rejestrowania wyjątków, więc ukradłem rozwiązanie Marka polegające na użyciu niestandardowego filtra ExceptionFilter ...

public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext exceptionContext)
    {
        var exception = exceptionContext.Exception;
        var request = exceptionContext.HttpContext.Request;
        // log stuff
    }
}

Ostatnią rzeczą, którą musisz zrobić, jest zarejestrowanie filtra wyjątków w pliku Global.asax.cs :

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new ExceptionPublisherExceptionFilter());
    filters.Add(new HandleErrorAttribute());
}

Wydaje mi się, że jest to znacznie czystsze rozwiązanie niż moja poprzednia odpowiedź i działa tak dobrze, jak potrafię. Podoba mi się to szczególnie dlatego, że nie czułem się jakbym walczył z frameworkiem MVC; to rozwiązanie faktycznie to wykorzystuje!

Jesse Webb
źródło
6
Warto wspomnieć, że myślę, że to rozwiązanie działa tylko w IIS 7 i nowszych; element httpErrors został niedawno dodany.
Jesse Webb,
Ta odpowiedź nie działa dla mnie, ma mnie, gra mnie okropnie niebiesko: HTTP Error 500.0 - Internal Server Error The page cannot be displayed because an internal server error has occurred. ekran błędu, a nie moja żądana /Error/Indexstrona
Serj Sagan
@SerjSagan Właśnie wypróbowałem to w nowym projekcie, aby przetestować kroki i działa dobrze. Czy używasz usług IIS, IIS Express lub VS Dev Server? To nie zadziała w VS Dev Server. Kiedy ustawiłem projekt na korzystanie z VS Dev Server, a nie IIS, zauważyłem błędy na żółtym ekranie zamiast niestandardowych stron błędów.
Jesse Webb
1
@SerjSagan Ale wygląda na to, że otrzymujesz błędy niebieskiego ekranu usług IIS, w przeciwieństwie do klasycznych błędów żółtego ekranu. To sprawia, że ​​zakładam, że używasz jakiejś formy usług IIS. Jeśli tak, przeczytaj artykuł, do którego utworzyłem łącze w pierwszym zdaniu, a konkretnie ostatnią sekcję zatytułowaną: „Kilka ważnych uwag”. Mówi:To able to overwrite global settings in global IIS settings (file: C:\Windows\System32\inetsrv\config \applicationHost.config) should be: <section name="httpErrors" overrideModeDefault="Allow" />
Jesse Webb
3
@SerjSagan Zmiana errorMode na DetailedLocalOnly lub Custom spowoduje wyświetlenie Twojej strony.
Daniel P
8

W przypadku ASP.NET MVC5 użyj

public class ExceptionPublisherExceptionFilter : IExceptionFilter
    {
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        public void OnException(ExceptionContext exceptionContext)
        {
            var exception = exceptionContext.Exception;
            var request = exceptionContext.HttpContext.Request;
            // HttpException httpException = exception as HttpException;
            // Log this exception with your logger
            _logger.Error(exception.Message);
        }
    }

Można go znaleźć w FilterConfig.csz App_Startfolderu.

Deweloper
źródło
1

Podoba mi się odpowiedź Marka za pomocą ExceptionFilter, ale inną opcją, jeśli masz wszystkie kontrolery wywodzące się z tego samego kontrolera podstawowego, jest po prostu zastąpienie OnException w kontrolerze podstawowym. Możesz tam logować się i wysyłać e-maile. Ma to tę zaletę, że można używać wszelkich zależności, które zostały już wprowadzone do kontrolera podstawowego z kontenerem IoC.

Nadal możesz używać swojego IoC z IExceptionFilter, ale konfigurowanie powiązań jest nieco trudniejsze.

Tim Hardy
źródło
0

O ile wiem, przekazujesz kontrolę do strony określonej w parametrze url, a powiadomienie o zdarzeniu będzie się znajdować tutaj, a nie Application_Error

<customErrors defaultRedirect="myErrorPage.aspx"
              mode="On">
</customErrors>

Wiele informacji można znaleźć tutaj: http://support.microsoft.com/kb/306355

ChrisBint
źródło
Dziękuję Chrisowi, przeczytałem dokumentację i sugeruje, że Application_Error zostanie wywołany, gdy wystąpi nieobsłużony błąd (czego się spodziewam) i że Server.ClearError () nie powinien być nazywany sekcją web.config customErrors będzie ostatnim punktem obsługi. Jednak włączenie customErrors uniemożliwia uruchomienie Application_Error.
WDuffy
Dokumentacja, do której utworzono łącze, mówi o aplikacji ASP.NET i wygląda na to, że aplikacje internetowe MVC3 zachowują się inaczej.
Jesse Webb
0

Aby obejść ten problem, zostawiłem wyłączone błędy klienta i obsłużyłem wszystkie błędy ze zdarzenia Application_Error w global.asax. Jest to trochę trudne z MVC, ponieważ nie chciałem zwracać przekierowania 301, chciałem zwrócić odpowiednie kody błędów. Więcej szczegółów można znaleźć na moim blogu pod adresem http://www.wduffy.co.uk/blog/using-application_error-in-asp-net-mvcs-global-asax-to-handle-errors/, ale ostateczny kod to wymienione poniżej...

void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();
    var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

    if (code != 404)
    {
            // Generate email with error details and send to administrator
    }

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

    string path = Request.Path;
    Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(Context);
    Context.RewritePath(path, false);
}

A oto kontroler

public class ErrorsController : Controller
{

    [HttpGet]
    public ActionResult Http404(string source)
    {
            Response.StatusCode = 404;
            return View();
    }

    [HttpGet]
    public ActionResult Http500(string source)
    {
            Response.StatusCode = 500;
            return View();
    }

}
WDuffy
źródło
Wypróbowałem twoje rozwiązanie, ale nie zadziałało. W liniach Response.StatusCode = ###;pokazywał wbudowane strony błędów MVC pod adresem C:\inetpub\custerr\en-US. Nie podobał mi się również pomysł ręcznego wywoływania HttpHandlers lub Controllerów z mojej metody Application_Error (). Cieszę się jednak, że znalazłeś rozwiązanie swojego problemu, wiem, jakie bóle głowy mi to przysporzyło.
Jesse Webb
0

Ten wpis na blogu pomógł mi:

http://asp-net.vexedlogic.com/2011/04/23/asp-net-maximum-request-length-exceeded/

Jeśli używasz usług IIS 7.0 lub nowszych, możesz zmienić plik Web.config, aby obsługiwał zbyt duże żądania. Istnieją pewne zastrzeżenia, ale oto przykład:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="1048576" />
    </requestFiltering>
  </security>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" subStatusCode="13" />
    <error statusCode="404" subStatusCode="13" prefixLanguageFilePath="" path="/UploadTooLarge.htm" responseMode="Redirect" />
  </httpErrors>
</system.webServer>

Dodatkowe szczegóły dotyczące tych elementów pliku konfiguracyjnego znajdują się tutaj:

http://www.iis.net/ConfigReference/system.webServer/security/requestFiltering/requestLimits

Kod stanu 404.13 jest zdefiniowany jako „Zbyt duża długość treści”. Należy zwrócić uwagę na to, że wartość maxAllowedContentLengthjest podawana w bajtach. Różni się to od maxRequestLengthustawienia, które znajdziesz w <system.web>sekcji, które jest określone w kilobajtach.

<system.web>
  <httpRuntime maxRequestLength="10240" />
</system.web>

Należy również pamiętać, że pathatrybut musi być ścieżką bezwzględną, gdy responseModejest Redirect, więc w razie potrzeby dodaj nazwę katalogu wirtualnego na początku. Pouczające odpowiedzi Jessego Webba pokazują, jak to zrobić responseMode="ExecuteURL", i myślę, że to podejście też się sprawdzi.

To podejście nie działa, jeśli tworzysz przy użyciu Visual Studio Development Server (Cassini, serwer sieci Web zintegrowany z Visual Studio). Zakładam, że zadziałaby w IIS Express, ale nie testowałem tego.

Dan Jagnow
źródło
0

Miałem ten sam problem, kiedy Application_Error()nie zostałem trafiony. Próbowałem wszystkiego, aż w końcu przeszedłem przez to, co się działo. Miałem jakiś niestandardowy kod w zdarzeniu ELMAH, które dodawało JSON do wysyłanego e-maila, i był tam błąd zerowy!

Naprawienie błędu wewnętrznego pozwoliło kodowi kontynuować Application_Error()zdarzenie zgodnie z oczekiwaniami.

Matt Kemp
źródło