Custom error pages on asp.net MVC3

asp.net Mvc-3Error Handling

asp.net Mvc-3 Problem Overview


I'm developing a MVC3 base website and I am looking for a solution for handling errors and Render custom Views for each kind of error. So imagine that I have a "Error" Controller where his main action is "Index" (generic error page) and this controller will have a couple more actions for the errors that may appear to the user like "Handle500" or "HandleActionNotFound".

So every error that may happen on the website may be handled by this "Error" Controller (examples: "Controller" or "Action" not found, 500, 404, dbException, etc).

I am using Sitemap file to define website paths (and not route).

This question was already answered, this is a reply to Gweebz

My final applicaiton_error method is the following:

protected void Application_Error() {
//while my project is running in debug mode
if (HttpContext.Current.IsDebuggingEnabled && WebConfigurationManager.AppSettings["EnableCustomErrorPage"].Equals("false"))
{
	Log.Logger.Error("unhandled exception: ", Server.GetLastError());
}
else
{
	try
	{
		var exception = Server.GetLastError();

		Log.Logger.Error("unhandled exception: ", exception);

		Response.Clear();
		Server.ClearError();
		var routeData = new RouteData();
		routeData.Values["controller"] = "Errors";
		routeData.Values["action"] = "General";
		routeData.Values["exception"] = exception;

		IController errorsController = new ErrorsController();
		var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
		errorsController.Execute(rc);
	}
	catch (Exception e)
	{
		//if Error controller failed for same reason, we will display static HTML error page
		Log.Logger.Fatal("failed to display error page, fallback to HTML error: ", e);
		Response.TransmitFile("~/error.html");
	}
}
}

asp.net Mvc-3 Solutions


Solution 1 - asp.net Mvc-3

Here's an example of how I handle custom errors. I define an ErrorsController with actions handling different HTTP errors:

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        return Content("General failure", "text/plain");
    }

    public ActionResult Http404()
    {
        return Content("Not found", "text/plain");
    }

    public ActionResult Http403()
    {
        return Content("Forbidden", "text/plain");
    }
}

and then I subscribe for the Application_Error in Global.asax and invoke this controller:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();
    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}

Solution 2 - asp.net Mvc-3

Here is more articles How to create custom error pages with MVC http://kitsula.com/Article/MVC-Custom-Error-Pages.

Solution 3 - asp.net Mvc-3

You can also do this in the Web.Config File. Here is an example that works in IIS 7.5.

     <system.webServer>
          <httpErrors errorMode="DetailedLocalOnly" defaultResponseMode="File">
                <remove statusCode="502" subStatusCode="-1" />
                <remove statusCode="501" subStatusCode="-1" />
                <remove statusCode="412" subStatusCode="-1" />
                <remove statusCode="406" subStatusCode="-1" />
                <remove statusCode="405" subStatusCode="-1" />
                <remove statusCode="404" subStatusCode="-1" />
                <remove statusCode="403" subStatusCode="-1" />
                <remove statusCode="401" subStatusCode="-1" />
                <remove statusCode="500" subStatusCode="-1" />
                <error statusCode="500" path="/notfound.html" responseMode="ExecuteURL" />
                <error statusCode="401" prefixLanguageFilePath="" path="/500.html" responseMode="ExecuteURL" />
                <error statusCode="403" prefixLanguageFilePath="" path="/403.html" responseMode="ExecuteURL" />
                <error statusCode="404" prefixLanguageFilePath="" path="/404.html" responseMode="ExecuteURL" />
                <error statusCode="405" prefixLanguageFilePath="" path="/405.html" responseMode="ExecuteURL" />
                <error statusCode="406" prefixLanguageFilePath="" path="/406.html" responseMode="ExecuteURL" />
                <error statusCode="412" prefixLanguageFilePath="" path="/412.html" responseMode="ExecuteURL" />
                <error statusCode="501" prefixLanguageFilePath="" path="/501.html" responseMode="ExecuteURL" />
                <error statusCode="502" prefixLanguageFilePath="" path="/genericerror.html" responseMode="ExecuteURL" />
           </httpErrors>
</system.webServer>

Solution 4 - asp.net Mvc-3

I see you added a config value for EnableCustomErrorPage and you're also checking IsDebuggingEnabled to determine whether or not to run your error handling.

Since there's already a <customErrors/> configuration in ASP.NET (which is meant exactly for this purpose) it's easiest to just say :

    protected void Application_Error()
    {
        if (HttpContext.Current == null) 
        {
                // errors in Application_Start will end up here                
        }
        else if (HttpContext.Current.IsCustomErrorEnabled)
        {
                // custom exception handling
        }
    }

Then in the config you'd put <customErrors mode="RemoteOnly" /> which is safe to deploy like that, and when you need to test your custom error page you'd set it to <customErrors mode="On" /> so you can verify that it works.

Note you also need to check if HttpContext.Current is null because an exception in Application_Start will still his this method although there won't be an active context.

Solution 5 - asp.net Mvc-3

You can display a user-friendly error page with the correct http status code by implementing Jeff Atwood's User Friendly Exception Handling module with a slight modification for the http status code. It works without any redirects. Although the code is from 2004(!), it works well with MVC. It can be configured entirely in your web.config, with no MVC project source code changes at all.

The modification required to return the original HTTP status rather than a 200 status is described in this related forum post.

Basically, in Handler.vb, you can add something like:

' In the header...
Private _exHttpEx As HttpException = Nothing

' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
    _exHttpEx = CType(ex, HttpException)
    HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If

Solution 6 - asp.net Mvc-3

I'm using MVC 4.5 and I was having issues with Darin's solution. Note: Darin's solution is excellent and I used it to come up with my solution. Here's my modified solution:

protected void Application_Error(object sender, EventArgs e)
{           
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.StatusCode = httpException.GetHttpCode();

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

if (httpException != null)
{
    var httpContext = HttpContext.Current;

    httpContext.RewritePath("/Errors/InternalError", false);

    // MVC 3 running on IIS 7+
    if (HttpRuntime.UsingIntegratedPipeline)
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.Server.TransferRequest("/Errors/Http403", true);
                break;
            case 404:
                httpContext.Server.TransferRequest("/Errors/Http404", true);
                break;
            default:
                httpContext.Server.TransferRequest("/Errors/InternalError", true);
                break;
        }
    }
    else
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.RewritePath(string.Format("/Errors/Http403", true));
                break;
            case 404:
                httpContext.RewritePath(string.Format("/Errors/Http404", true));
                break;
            default:
                httpContext.RewritePath(string.Format("/Errors/InternalError", true));
                break;
        }

        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest(httpContext);
    }
}
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJohn LourosView Question on Stackoverflow
Solution 1 - asp.net Mvc-3Darin DimitrovView Answer on Stackoverflow
Solution 2 - asp.net Mvc-3IgorView Answer on Stackoverflow
Solution 3 - asp.net Mvc-3Brett AllredView Answer on Stackoverflow
Solution 4 - asp.net Mvc-3Simon_WeaverView Answer on Stackoverflow
Solution 5 - asp.net Mvc-3Martin_WView Answer on Stackoverflow
Solution 6 - asp.net Mvc-3MVCdragonView Answer on Stackoverflow