Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Global error handling

I have a custom HandleError attribute that deals with errors on the MVC pipeline; I have an protected void Application_Error(object sender, EventArgs e) method on my Global.asax which handles errors from outside the pipeline.

I've come across an scenario I didn't know was possible; In implementing DI, there's a dependency for a connectionString, which is taken from the application configuration file.

As the connection string didn't exist yet, an error raises when creating the controller, this usually makes the Application_Error handler fire, and a proper error page is rendered (through rendering a partial view as string and sending it as the response, and in case this fails it just writes "Fatal exception." to the response.

Except in this case, I get the fugly default ASP.NET "runtime error" yellow screen of death. Telling me:

Runtime Error

Description: An application error occurred on the server. The current custom error settings for this application prevent the details of the application error from being viewed.

Details: To enable the details of this specific error message to be viewable on the local server machine, please create a tag within a "web.config" configuration file located in the root directory of the current web application. This tag should then have its "mode" attribute set to "RemoteOnly". To enable the details to be viewable on remote machines, please set "mode" to "Off".

I don't have a defaultRedirect set in my customErrors, nor is it turned Off, because I don't want to redirect but to render errors on the same page the user is in, avoiding a needless redirect.

How can I handle an scenario such as this? And what's even the reason why it behaves this way and not like any other error outside a controller?

I realize it's not likely to happen often, but I would like being able to stop the YSOD (partly because I want to hide the technology I'm using, but mostly because it's not pretty nor user friendly at all)

I even tried registering a handler for UnhandledExceptions, but it didn't fire either.

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

The code that ultimately produces this, is:

return ConfigurationManager.ConnectionStrings[key].ConnectionString;, where ConnectionStrings[key] is null.

Update

This is how applicaion errors are handled:

    protected void Application_Error(object sender, EventArgs e)
    {
        this.HandleApplicationError(new ResourceController());
    }

    public static void HandleApplicationError(this HttpApplication application, BaseController controller)
    {
        if (application == null)
        {
            throw new ArgumentNullException("application");
        }
        if (controller == null)
        {
            throw new ArgumentNullException("controller");
        }
        application.Response.Clear();
        Exception exception = application.Server.GetLastError();
        LogApplicationException(application.Response, exception);
        try
        {
            RenderExceptionViewResponse(application, exception, controller);
        }
        catch (Exception exceptionRenderingView) // now we're in trouble. let's be as graceful as possible.
        {
            RenderExceptionTextResponse(application, exceptionRenderingView);
        }
        finally
        {
            application.Server.ClearError();
        }
    }

    private static void LogApplicationException(HttpResponse response, Exception exception)
    {
        if (exception is HttpException)
        {
            HttpException httpException = (HttpException)exception;
            if (httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                _log.Debug(Resources.Error.WebResourceNotFound, httpException);
                response.Status = Resources.Constants.NotFound;
                return;
            }
        }
        _log.Error(Resources.Error.UnhandledException, exception);
    }

    private static void RenderExceptionViewResponse(HttpApplication application, Exception exception, BaseController controller)
    {
        if (!RenderAsJsonResponse(application, Resources.User.UnhandledExceptionJson))
        {
            ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
            string result = controller.RenderViewToString(Resources.Constants.ErrorViewName, model);
            application.Response.Write(result);
        }
    }

    private static void RenderExceptionTextResponse(HttpApplication application, Exception exceptionRenderingView)
    {
        application.Response.Clear();

        if (!RenderAsJsonResponse(application, Resources.User.FatalExceptionJson))
        {
            application.Response.Write(Resources.User.FatalException);
        }
        _log.Fatal(Resources.Error.FatalException, exceptionRenderingView);
    }

    private static bool RenderAsJsonResponse(HttpApplication application, string message)
    {
        if (application.Request.IsAjaxRequest())
        {
            application.Response.Status = Resources.Constants.HttpSuccess;
            application.Response.ContentType = Resources.Constants.JsonContentType;
            application.Response.Write(message);
            return true;
        }
        return false;
    }

This is the attribute I use to decorate my base controller:

public class ErrorHandlingAttribute : HandleErrorAttribute
{
    public Type LoggerType { get; set; }

    public ErrorHandlingAttribute()
        : this(typeof(ErrorHandlingAttribute))
    {
    }

    public ErrorHandlingAttribute(Type loggerType)
    {
        LoggerType = loggerType;
    }

    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled)
        {
            return;
        }
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            OnAjaxException(filterContext);
        }
        else
        {
            OnRegularException(filterContext);
        }
    }

    internal protected void OnRegularException(ExceptionContext filterContext)
    {
        Exception exception = filterContext.Exception;

        ILog logger = LogManager.GetLogger(LoggerType);
        logger.Error(Resources.Error.UnhandledException, exception);

        filterContext.HttpContext.Response.Clear();

        ErrorViewModel model = WebUtility.GetErrorViewModel(exception);
        filterContext.Result = new ViewResult
        {
            ViewName = Resources.Constants.ErrorViewName,
            ViewData = new ViewDataDictionary(model)
        };
        filterContext.ExceptionHandled = true;
    }

    internal protected void OnAjaxException(ExceptionContext filterContext)
    {
        Exception exception = filterContext.Exception;

        ILog logger = LogManager.GetLogger(LoggerType);
        logger.Error(Resources.Error.UnhandledAjaxException, exception);

        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.Status = Resources.Constants.HttpSuccess;

        string errorMessage = WebUtility.GetUserExceptionMessage(exception, true);

        filterContext.Result = new ExceptionJsonResult(new[] { errorMessage });
        filterContext.ExceptionHandled = true;
    }
}

And this is my customErrors:

<customErrors mode="On" />

As you can see these are pretty extensive, however they do not even fire in the case of accessing the ConnectionStrings where the ConnectionString doesn't exist; which is kind of puzzling.

It does fire in any controller contained exception or exceptions not within a controller, so I don't understand why this case is any different.

like image 583
bevacqua Avatar asked Feb 27 '12 07:02

bevacqua


People also ask

How error handling is done in MVC?

Building Web Applications with ASP.NET Core 3 MVC In ASP.NET, error handling is done using the standard try catch approach or using application events. ASP.NET MVC comes with built-in support for exception handling using a feature known as exception filters.

How does MVC handle application error in global ASAX?

Setting HandleError Attribute as a Global Filter Any unhandled exception that takes place within the boundary of the MVC application will now be handled by this global error handler. To test this global handler, comment out the [HandleError] attribute from the action or the controller and then run the application.

How does ASP.NET handle exceptions globally?

ASP.NET Core Error Handling You can register it as a global filter, and it will function as a global exception handler. Another option is to use a custom middleware designed to do nothing but catch unhandled exceptions. You must also register your filter as part of the Startup code.


1 Answers

Application_Error might be the recommended way of handling errors in ASP.NET WebForms. But not in MVC.

We got error filters that take care of errors for us. The problem with the filter is that it only works when a controller has been invoked. Which is the problem for 404 and 401 errors (not found and authorization) and your database connection problem.

customErrors is the way to go here. I don't see why the redirect should be a problem?

I'm going through proper error handling in a blog article: http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/

like image 73
jgauffin Avatar answered Oct 07 '22 12:10

jgauffin