This is the set-up that I have. It will not redirect and it handles both application and some configured IIS errors in the same place. You can also pass in any information you want to your Error controller.
In Web.config:
<system.web>
<customErrors mode="Off" />
...
</system.web>
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Auto">
<remove statusCode="403" />
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="403" responseMode="ExecuteURL" path="/Error/Display/403" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/Display/404" />
<error statusCode="500" responseMode="ExecuteURL" path="/Error/Display/500" />
</httpErrors>
...
</system.webServer>
In ErrorController (showing method signatures only for brevity):
// This one gets called from Application_Error
// You can add additional parameters to this action if needed
public ActionResult Index(Exception exception)
{
...
}
// This one gets called by IIS (see Web.config)
public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode)
{
...
}
Additionally, I have an ErrorViewModel
and an Index
view.
In Application_Error:
protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();
var httpContext = new HttpContextWrapper(Context);
httpContext.ClearError();
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Index";
routeData.Values["exception"] = exception;
// Here you can add additional route values as necessary.
// Make sure you add them as parameters to the action you're executing
IController errorController = DependencyResolver.Current.GetService<ErrorController>();
var context = new RequestContext(httpContext, routeData);
errorController.Execute(context);
}
So far, this is the basic set-up that I have. This will not perform a redirect (the error controller action is executed from Application_Error), and it handles controller exceptions as well as, for example, IIS 404s (such as yourwebsite.com/blah.html).
From this point on, whatever happens inside your ErrorController
would be based on your needs.
As an example, I will add some extra details of my implementation. As I was saying, I have an ErrorViewModel
.
My ErrorViewModel:
public class ErrorViewModel
{
public string Title { get; set; }
public string Text { get; set; }
// This is only relevant to my business needs
public string ContentResourceKey { get; set; }
// I am including the actual exception in here so that in the view,
// when the request is local, I am displaying the exception for
// debugging purposes.
public Exception Exception { get; set; }
}
My ErrorController (relevant parts):
public ActionResult Index(Exception exception)
{
ErrorViewModel model;
var statusCode = HttpStatusCode.InternalServerError;
if (exception is HttpException)
{
statusCode = (HttpStatusCode)(exception as HttpException).GetHttpCode();
// More details on this below
if (exception is DisplayableException)
{
model = CreateErrorModel(exception as DisplayableException);
}
else
{
model = CreateErrorModel(statusCode);
model.Exception = exception;
}
}
else
{
model = new ErrorViewModel { Exception = exception };
}
return ErrorResult(model, statusCode);
}
public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode)
{
var model = CreateErrorModel(statusCode);
return ErrorResult(model, statusCode);
}
private ErrorViewModel CreateErrorModel(HttpStatusCode statusCode)
{
var model = new ErrorViewModel();
switch (statusCode)
{
case HttpStatusCode.NotFound:
// Again, this is only relevant to my business logic.
// You can do whatever you want here
model.ContentResourceKey = "error-page-404";
break;
case HttpStatusCode.Forbidden:
model.Title = "Unauthorised.";
model.Text = "Your are not authorised to access this resource.";
break;
// etc...
}
return model;
}
private ErrorViewModel CreateErrorModel(DisplayableException exception)
{
if (exception == null)
{
return new ErrorViewModel();
}
return new ErrorViewModel
{
Title = exception.DisplayTitle,
Text = exception.DisplayDescription,
Exception = exception.InnerException
};
}
private ActionResult ErrorResult(ErrorViewModel model, HttpStatusCode statusCode)
{
HttpContext.Response.Clear();
HttpContext.Response.StatusCode = (int)statusCode;
HttpContext.Response.TrySkipIisCustomErrors = true;
return View("Index", model);
}
In some cases I need to display a custom message when an error happens. I have a custom exception for this purpose:
[Serializable]
public class DisplayableException : HttpException
{
public string DisplayTitle { get; set; }
public string DisplayDescription { get; set; }
public DisplayableException(string title, string description)
: this(title, description, HttpStatusCode.InternalServerError, null, null)
{
}
public DisplayableException(string title, string description, Exception exception)
: this(title, description, HttpStatusCode.InternalServerError, null, exception)
{
}
public DisplayableException(string title, string description, string message, Exception exception)
: this(title, description, HttpStatusCode.InternalServerError, message, exception)
{
}
public DisplayableException(string title, string description, HttpStatusCode statusCode, string message, Exception inner)
: base((int)statusCode, message, inner)
{
DisplayTitle = title;
DisplayDescription = description;
}
}
Then I use it like this:
catch(SomeException ex)
{
throw new DisplayableException("My Title", "My custom display message", "An error occurred and I must display something", ex)
}
In my ErrorController
I handle this exception separately, setting the ErrorViewModel
's Title
and Text
properties from this DisplayableException
.