128

I have code in the global.asax file's Application_Error event which executes when an error occurs and emails details of the error to myself.

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

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

}

This works fine when I'm running it in Visual Studio, however when I publish to our live server the Application_Error event does not fire.

After some testing I can get the Application_Error firing when I set customErrors="Off", however setting it back to customErrors="On" stops the event from firing again.

Can anyone suggest why Application_Error would not be firing when customErrors are enabled in the web.config?

Michael
  • 8,362
  • 6
  • 61
  • 88
William
  • 8,007
  • 5
  • 39
  • 43
  • I am having the exact same problem. I also found this SO question: http://stackoverflow.com/questions/3713939/global-asax-application-error-not-firing which suggests to put the IIS7 server into classic mode. Unfortunately that is not an option for us. Anybody have any better solutions? – Jesse Webb Jul 27 '11 at 20:59
  • Here is another related question and it's answers (none of which are accepted) suggest not to use Application_Error() at all... http://stackoverflow.com/questions/1194578/should-you-use-the-method-application-error-in-your-global-asax-in-asp-net-mvc – Jesse Webb Jul 27 '11 at 21:01
  • @Gweebz I've posted an answer on how I got around this, but I still didn't find any solid documentation on why I was getting this behaviour. – William Jul 29 '11 at 10:19
  • 1
    I added an answer which explains why the `Application_Error()` method wasn't getting invoked. I also explained my final solution. – Jesse Webb Jul 29 '11 at 15:16
  • Really recommend [this article](https://dusted.codes/demystifying-aspnet-mvc-5-error-pages-and-error-logging) in order to get custom errors working. – ThomasArdal Aug 23 '16 at 05:45

9 Answers9

138

UPDATE
Since this answer does provide a solution, I will not edit it, but I have found a much cleaner way of solving this problem. See my other answer for details...

Original Answer:
I figured out why the Application_Error() method is not being invoked...

Global.asax.cs

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

By default (when a new project is generated), an MVC application has some logic in the Global.asax.cs file. This logic is used for mapping routes and registering filters. By default, it only registers one filter: a HandleErrorAttribute filter. When customErrors are on (or through remote requests when it is set to RemoteOnly), the HandleErrorAttribute tells MVC to look for an Error view and it never calls the Application_Error() method. I couldn't find documentation of this but it is explained in this answer on programmers.stackexchange.com.

To get the ApplicationError() method called for every unhandled exception, simple remove the line which registers the HandleErrorAttribute filter.

Now the problem is: How to configure the customErrors to get what you want...

The customErrors section defaults to redirectMode="ResponseRedirect". You can specify the defaultRedirect attribute to be a MVC route too. I created an ErrorController which was very simple and changed my web.config to look like this...

web.config

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

The problem with this solution is that it does a 302 redirect to your error URLs and then those pages respond with a 200 status code. This leads to Google indexing the error pages which is bad. It also isn't very conformant to the HTTP spec. What I wanted to do was not redirect, and overrite the original response with my custom error views.

I tried to change redirectMode="ResponseRewrite". Unfortunately, this option does not support MVC routes, only static HTML pages or ASPX. I tried to use an static HTML page at first but the response code was still 200 but, at least it didn't redirect. I then got an idea from this answer...

I decided to give up on MVC for error handling. I created an Error.aspx and a PageNotFound.aspx. These pages were very simple but they had one piece of magic...

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

This block tells the page to be served with the correct status code. Of course, on the PageNotFound.aspx page, I used HttpStatusCode.NotFound instead. I changed my web.config to look like this...

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

It all worked perfectly!

Summary:

  • Remove the line: filters.Add(new HandleErrorAttribute());
  • Use Application_Error() method to log exceptions
  • Use customErrors with a ResponseRewrite, pointing at ASPX pages
  • Make the ASPX pages responsible for their own response status codes

There are a couple downsides I have noticed with this solution.

  • The ASPX pages can't share any markup with Razor templates, I had to rewrite our website's standard header and footer markup for a consistent look and feel.
  • The *.aspx pages can be accessed directly by hitting their URLs

There are work-arounds for these problems but I wasn't concerned enough by them to do any extra work.

starball
  • 20,030
  • 7
  • 43
  • 238
Jesse Webb
  • 43,135
  • 27
  • 106
  • 143
  • 2
    +1! Really good workaround but, what might be the consequences of mixing MVC with aspx pages? – Diego Sep 27 '11 at 15:20
  • 2
    We have had this in PROD for a couple of months now and I haven't found and negative impact. The only 'gotcha' was that we had to change our CI and deploy to do an AspCompile MSBUILD task because MVC doesn't need it but when we added the *.as* files, they required it. There may be other problems but they have not surfaced yet... – Jesse Webb Sep 27 '11 at 22:59
  • I've been trying to use this approach in MVC4, and apparently it only works for me when I got ``. Having `filters.Add(new HandleErrorAttribute());` removed or not has no effect. – Grzegorz Sławecki Jul 22 '14 at 10:27
  • on the aspx page you can add an ajax call to call the view you want to show. Saves having to duplicate code – Mike Nov 03 '17 at 18:42
  • The filter registration was the root cause of a similar issue that I had. Thank you for the suggestion! – Ritwik Sen Oct 26 '22 at 11:29
77

I solved this by creating an ExceptionFilter and logging the error there instead of Application_Error. All you need to do is add a call to in in 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());
}
Mark
  • 1,108
  • 10
  • 11
37

I found an article which describes a much cleaner way of making custom error pages in an MVC3 web app which does not prevent the ability to log the exceptions.

The solution is to use the <httpErrors> element of the <system.webServer> section.

I configured my Web.config like so...

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

I also configured customErrors to have mode="Off" (as suggested by the article).

That makes the responses overriden by an ErrorController's actions. Here is that controller:

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

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

The views are very straightforward, I just used standard Razor syntax to create the pages.

That alone should be enough for you to use custom error pages with MVC.

I also needed logging of Exceptions so I stole the Mark's solution of using a custom ExceptionFilter...

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

The last thing you need to so is register the Exception Filter in your Global.asax.cs file:

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

This feels like a much cleaner solution than my previous answer and works just as well as far as I can tell. I like it especially because it didn't feel like I was fighting against the MVC framework; this solution actually leverages it!

Jess
  • 23,901
  • 21
  • 124
  • 145
Jesse Webb
  • 43,135
  • 27
  • 106
  • 143
  • 6
    It is worth mentioning that I think this solution only works in IIS 7 and newer; the httpErrors element was only recently added. – Jesse Webb Mar 05 '12 at 19:46
  • This answer did not work for me, it have me, it game me a horrible blue: `HTTP Error 500.0 - Internal Server Error The page cannot be displayed because an internal server error has occurred.` Error screen, not my requested `/Error/Index` page – Serj Sagan Aug 12 '13 at 23:02
  • @SerjSagan I just tried this out in a new project to test out the steps and it works fine. Are you using IIS, IIS Express, or VS Dev Server? This will not work in VS Dev Server. When I had my project set to use VS Dev Server, not IIS, I noticed Yellow-screen errors instead of custom error pages. – Jesse Webb Aug 13 '13 at 14:59
  • 1
    @SerjSagan But it sounds like you are getting IIS Blue-screen errors, as opposed to the classic Yellow-screen errors. This makes me assume you are using some form of IIS. If so, have a read through the article I linked to in the first sentence, specifically the last section titled: "Few important notes". It says: `To able to overwrite global settings in global IIS settings (file: C:\Windows\System32\inetsrv\config \applicationHost.config) should be:
    `
    – Jesse Webb Aug 13 '13 at 15:00
  • 3
    @SerjSagan Changing errorMode to DetailedLocalOnly or Custom will show your page. – Daniel P Sep 13 '13 at 15:28
  • You added the HandleErrorAttribute even though you have customErrors set to off. Why is that necessary? – Bardia Apr 04 '16 at 17:31
  • This is the cleanest and most beatiful way to handle http errors in ASP.NET MVC. Thanks man! – Arad Alvand May 05 '18 at 18:30
8

In case of ASP.NET MVC5 use

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

You can find it in FilterConfig.cs of App_Start folder.

NoWar
  • 36,338
  • 80
  • 323
  • 498
1

To get around this I ended up leaving customerrors disabled and handling all errors from the Application_Error event in global.asax. It's slightly tricky with MVC as I didn't want to return a 301 redirect, I wanted to return suitable error codes. More details can be viewed on my blog at http://www.wduffy.co.uk/blog/using-application_error-in-asp-net-mvcs-global-asax-to-handle-errors/ but the final code is listed below...

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

And here is the controller

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

}
William
  • 8,007
  • 5
  • 39
  • 43
  • I tried your solution but it didn't work for me. With the lines `Response.StatusCode = ###;`, it was showing the built-in MVC error pages at `C:\inetpub\custerr\en-US`. I also didn't like the idea of manually invoking HttpHandlers or Controllers from my Application_Error() method. I am glad you found a solution to your problem though, I know what kind of headaches this has given me. – Jesse Webb Jul 29 '11 at 15:10
  • For anyone reading this years later, this solution works just fine, but you have to implement views for the "ErrorsController" methods. The commenter above didn't implement views and got IIS errors. I went with this solution for custom error messaging, and no, MS hasn't addressed this issue years later. – Poultry in Motion May 04 '22 at 21:00
1

I like Mark's answer with the ExceptionFilter, but another option, if you have all your controllers derive from the same base controller, is to simply override OnException in your base controller. You can do your logging and emailing there. This has the advantage of being able to use whatever dependencies you've already got injected into your base controller with your IoC container.

You can still use your IoC with an IExceptionFilter, but it's a bit more tricky to configure your bindings.

Tim Hardy
  • 1,654
  • 1
  • 17
  • 36
0

This blog entry helped me:

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

If you're using IIS 7.0 or higher, you can alter your Web.config file to handle requests that are too large. There are some caveats, but here is an example:

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

There are additional details about these config file elements here:

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

Status code 404.13 is defined as "Content Length Too Large". One important thing to note is that the maxAllowedContentLength is specified in bytes. This is different from the maxRequestLength setting you find in the <system.web> section, which is specified in kilobytes.

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

Also note that the path attribute must be an absolute path when the responseMode is Redirect, so prepend the virtual directory name, if relevant. Jesse Webb's informative answers show how to do this with responseMode="ExecuteURL", and I would think that approach would work well, too.

This approach does not work if you're developing using the Visual Studio Development Server (Cassini, the Web server integrated into Visual Studio). I'm assuming it would work in IIS Express, but I haven't tested that.

Dan Jagnow
  • 1,175
  • 12
  • 28
0

I was having the same problem where Application_Error() wasn't getting hit. I tried everything, until finally I stepped through what was happening. I had some custom code in an ELMAH event that was adding JSON to the email it sends, and there was a null error in there!

Fixing the internal error allowed the code to continue on to the Application_Error() event as expected.

Matt Kemp
  • 2,742
  • 2
  • 28
  • 38
0

As far as I know, you are passing control over to the Page specified in the url parameter and your event notification would sit in here, rather than Application_Error

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

A lot of information can be found here: http://support.microsoft.com/kb/306355

ChrisBint
  • 12,773
  • 6
  • 40
  • 62
  • Thank Chris, I read the documentation and it suggests that Application_Error will be called when an unhandled error occurs (which I expect) and that should Server.ClearError() not be called the web.config customErrors section will be the final handle point. However, enabling customErrors is what is stopping Application_Error from firing. – William Jun 28 '11 at 15:40
  • The documentation you linked to is talking about ASP.NET application and it appears that MVC3 web applications are behaving differently. – Jesse Webb Jul 27 '11 at 21:00