Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.MVC HandleError attribute doesn't work

I know it's a common issue but I've crawled many discussions with no result.

I'm trying to handle errors with the HandleError ASP.MVC attrbiute. I'm using MVC 4.

My Error page is places in Views/Shared/Error.cshtml and looks like that:

Test error page
<hgroup class="title">
    <h1 class="error">Error.</h1>
    <h2 class="error">An error occurred while processing your request.</h2>
</hgroup>

My FilterConfig.cs in the App-Start folder is:

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

My controller:

public class TestController : Controller
    {
        [HandleError(View = "Error")]
        public ActionResult Index()
        {
            throw new Exception("oops");
        }
    }

And finally my Web.config in has the following node:

<customErrors mode="On" defaultRedirect="Error">
</customErrors>

When I call the controller action I get a white screen with a following text:

Server Error in '/' Application.

Runtime Error Description: 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.

If defaultRedirect="Error" is not set in the Web.config then I get yellow screen with a following text:

Server Error in '/' Application.

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

Notes: The current error page you are seeing can be replaced by a custom error page by modifying the "defaultRedirect" attribute of the application's configuration tag to point to a custom error page URL.

Does anybody know what can be wrong?

EDIT:

Errors were caused by using strongly typed layout. When error is thrown MVC's error handling mechanism is creating HandleErrorInfo object which is passed to the Error view. However if we use strongly typed layout then types doesn't match.

Solution in my case is using Application_Error method in Global.asax, which was perfectly described by the SBirthare below.

like image 577
Arkadiusz Kałkus Avatar asked Oct 14 '14 09:10

Arkadiusz Kałkus


1 Answers

Over the years I have struggled to implement "handling custom errors" in ASP.NET MVC smoothly.

I have had successfully used Elmah before however was overwhelmed with the numerous cases that needs to be handled and tested differently (i.e. local vs IIS).

Recently in one of my project which is now live, I have used following approach (seems to be working fine in local and production env).

I do not specify customErrors or any settings in web.config at all.

I override Application_Error and handle all cases there, invoking specific actions in ErrorController.

I am sharing this if it helps and also to get a feedback (though things are working, you never know when it starts breaking ;))

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
    }

    protected void Application_Error(object sender, EventArgs e)
    {
        System.Diagnostics.Trace.WriteLine("Enter - Application_Error");

        var httpContext = ((MvcApplication)sender).Context;

        var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
        var currentController = " ";
        var currentAction = " ";

        if (currentRouteData != null)
        {
            if (currentRouteData.Values["controller"] != null &&
                !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
            {
                currentController = currentRouteData.Values["controller"].ToString();
            }

            if (currentRouteData.Values["action"] != null &&
                !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
            {
                currentAction = currentRouteData.Values["action"].ToString();
            }
        }

        var ex = Server.GetLastError();

        if (ex != null)
        {
            System.Diagnostics.Trace.WriteLine(ex.Message);

            if (ex.InnerException != null)
            {
                System.Diagnostics.Trace.WriteLine(ex.InnerException);
                System.Diagnostics.Trace.WriteLine(ex.InnerException.Message);
            }
        }

        var controller = new ErrorController();
        var routeData = new RouteData();
        var action = "CustomError";
        var statusCode = 500;

        if (ex is HttpException)
        {
            var httpEx = ex as HttpException;
            statusCode = httpEx.GetHttpCode();

            switch (httpEx.GetHttpCode())
            {
                case 400:
                    action = "BadRequest";
                    break;

                case 401:
                    action = "Unauthorized";
                    break;

                case 403:
                    action = "Forbidden";
                    break;

                case 404:
                    action = "PageNotFound";
                    break;

                case 500:
                    action = "CustomError";
                    break;

                default:
                    action = "CustomError";
                    break;
            }
        }
        else if (ex is AuthenticationException)
        {
            action = "Forbidden";
            statusCode = 403;
        }

        httpContext.ClearError();
        httpContext.Response.Clear();
        httpContext.Response.StatusCode = statusCode;
        httpContext.Response.TrySkipIisCustomErrors = true;
        routeData.Values["controller"] = "Error";
        routeData.Values["action"] = action;

        controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction);
        ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
    }

}

ErrorController.cs

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult CustomError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        return View();
    }
}

This is all I have. No HandleErrorAttribute registered.

I found this approach less confusing and simple to extend. Hope this helps someone.

like image 71
SBirthare Avatar answered Oct 08 '22 23:10

SBirthare