Handling all exceptions within global.asax

2019-05-31 09:27发布

问题:

I'm trying to handle all application exceptions within global.asax for an MVC 3 project and whilst everything is working correctly within Cassini, as soon as I deploy to IIS 7.5, IIS starts taking control away from my application and handles many exceptions itself. This has the consequences of bypassing my custom logging and also returning ugly views.

I have a similar approach to Darin's answer to this question. Here's what I'm using at the moment.

protected void Application_Error(object sender, EventArgs e)
{
    var app = (MvcApplication)sender;
    var context = app.Context;
    var exception = app.Server.GetLastError();

    LogExceptionDetails(exception, Request.Url.PathAndQuery);

    context.Response.Clear();
    context.ClearError();

    string redirectTo = "/error";
    HttpException httpException = exception as HttpException;

    if (httpException != null)
    {
        switch (httpException.GetHttpCode())
        {
            case 403:
                redirectTo += "/forbidden";
                break;
            case 404:
                redirectTo += "/notfound";
                break;
        }
    }

    Response.TrySkipIisCustomErrors = true;

    // I should really change this so I can return a proper statusCode
    Response.Redirect(redirectTo);
}

As an example, navigating to localhost/app_code will return an ugly view and won't be logged. I have managed to at least make IIS return my custom views by using:

<httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="403" />
    <error statusCode="403" path="/error/forbidden" responseMode="ExecuteURL" />
    <remove statusCode="404" />
    <error statusCode="404" path="/error/notfound" responseMode="ExecuteURL" />
</httpErrors>

That doesn't solve the logging problem though.

Other things I've tried include:

  1. Setting existingResponse="PassThrough".
  2. Using <clear />.
  3. Various combinations of httpErrors with and without customErrors.
  4. Setting <modules runAllManagedModulesForAllRequests="true" />.
  5. Response.TrySkipIisCustomErrors = true;

Is there a way to handle this programmatically, whilst keeping things centralised in global.asax, rather than messing around with the web.config?

回答1:

Yes. Here's what I'm doing, but it's not perfect unfortunately.

First, Turn off custom errors.

<customErrors mode="Off" />

Next, Change HttpErrors to Detailed. Note this is the part that I don't particularly like, mainly because if you do this it seems you might be making your stack traces accessible. I think so long as you handle all status codes in your error handling, by using a catch all, you should be OK. Please correct me if I am wrong.

<httpErrors errorMode="Detailed" />

You'll also need a catch all route in your global.asax to catch all MVC routes that don't match your defined routes, and send them to your 404. This could be problematic depending on your route setup, mainly, if you rely on a catch all route to handle your current non-404 routes. I use reflection to define all of my routes based on Action methods in my Controllers, so I don't rely on a catch all pattern for my application's routes.

Lastly, handle the errors in your global.asax. Use a catch all (such as 500) and do special routing for anything you want different, such as 404 errors.

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

    Server.ClearError();

    var routeData = new RouteData();
    routeData.Values.Add("controller", "Error");
    routeData.Values.Add("action", "500");

    if (ex.GetType() == typeof (HttpException))
    {
        var httpException = (HttpException) ex;
        var code = httpException.GetHttpCode();

        // Is it a 4xx Error
        if (code % 400 < 100)
        {
            routeData.Values["action"] = "404";
        }
    }

    IController errorController = new ErrorController();
    errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}

Note, in my example I treat all errors except for 4xx errors as 500. Also, in this example I'm using a controller named "ErrorController" and two actions named "500" and "404".

I hope this helps, and if you find a workaround to to the HttpErrors needing to be set to "Detailed", please do share!