How can I properly handle 404 in ASP.NET MVC? - asp.net-mvc

I am using RC2
Using URL Routing:
routes.MapRoute(
"Error",
"{*url}",
new { controller = "Errors", action = "NotFound" } // 404s
);
The above seems to take care of requests like this (assuming default route tables setup by initial MVC project): "/blah/blah/blah/blah"
Overriding HandleUnknownAction() in the controller itself:
// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
ViewData["actionName"] = actionName;
View("NotFound").ExecuteResult(this.ControllerContext);
}
However the previous strategies do not handle a request to a Bad/Unknown controller. For example, I do not have a "/IDoNotExist", if I request this I get the generic 404 page from the web server and not my 404 if I use routing + override.
So finally, my question is: Is there any way to catch this type of request using a route or something else in the MVC framework itself?
OR should I just default to using Web.Config customErrors as my 404 handler and forget all this? I assume if I go with customErrors I'll have to store the generic 404 page outside of /Views due to the Web.Config restrictions on direct access.

The code is taken from http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx and works in ASP.net MVC 1.0 as well
Here's how I handle http exceptions:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
// Log the exception.
ILogger logger = Container.Resolve<ILogger>();
logger.Error(exception);
Response.Clear();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
if (httpException == null)
{
routeData.Values.Add("action", "Index");
}
else //It's an Http Exception, Let's handle it.
{
switch (httpException.GetHttpCode())
{
case 404:
// Page not found.
routeData.Values.Add("action", "HttpError404");
break;
case 500:
// Server error.
routeData.Values.Add("action", "HttpError500");
break;
// Here you can handle Views to other error codes.
// I choose a General error template
default:
routeData.Values.Add("action", "General");
break;
}
}
// Pass exception details to the target error View.
routeData.Values.Add("error", exception);
// Clear the error on server.
Server.ClearError();
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
// Call target Controller and pass the routeData.
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(
new HttpContextWrapper(Context), routeData));
}

Requirements for 404
The following are my requirements for a 404 solution and below i show how i implement it:
I want to handle matched routes with bad actions
I want to handle matched routes with bad controllers
I want to handle un-matched routes (arbitrary urls that my app can't understand) - i don't want these bubbling up to the Global.asax or IIS because then i can't redirect back into my MVC app properly
I want a way to handle in the same manner as above, custom 404s - like when an ID is submitted for an object that does not exist (maybe deleted)
I want all my 404s to return an MVC view (not a static page) to which i can pump more data later if necessary (good 404 designs) and they must return the HTTP 404 status code
Solution
I think you should save Application_Error in the Global.asax for higher things, like unhandled exceptions and logging (like Shay Jacoby's answer shows) but not 404 handling. This is why my suggestion keeps the 404 stuff out of the Global.asax file.
Step 1: Have a common place for 404-error logic
This is a good idea for maintainability. Use an ErrorController so that future improvements to your well designed 404 page can adapt easily. Also, make sure your response has the 404 code!
public class ErrorController : MyController
{
#region Http404
public ActionResult Http404(string url)
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
var model = new NotFoundViewModel();
// If the url is relative ('NotFound' route) then replace with Requested path
model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
Request.Url.OriginalString : url;
// Dont get the user stuck in a 'retry loop' by
// allowing the Referrer to be the same as the Request
model.ReferrerUrl = Request.UrlReferrer != null &&
Request.UrlReferrer.OriginalString != model.RequestedUrl ?
Request.UrlReferrer.OriginalString : null;
// TODO: insert ILogger here
return View("NotFound", model);
}
public class NotFoundViewModel
{
public string RequestedUrl { get; set; }
public string ReferrerUrl { get; set; }
}
#endregion
}
Step 2: Use a base Controller class so you can easily invoke your custom 404 action and wire up HandleUnknownAction
404s in ASP.NET MVC need to be caught at a number of places. The first is HandleUnknownAction.
The InvokeHttp404 method creates a common place for re-routing to the ErrorController and our new Http404 action. Think DRY!
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
// If controller is ErrorController dont 'nest' exceptions
if (this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
errorController.Execute(new RequestContext(
httpContext, errorRoute));
return new EmptyResult();
}
#endregion
}
Step 3: Use Dependency Injection in your Controller Factory and wire up 404 HttpExceptions
Like so (it doesn't have to be StructureMap):
MVC1.0 example:
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
try
{
if (controllerType == null)
return base.GetControllerInstance(controllerType);
}
catch (HttpException ex)
{
if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);
return errorController;
}
else
throw ex;
}
return ObjectFactory.GetInstance(controllerType) as Controller;
}
}
MVC2.0 example:
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
try
{
if (controllerType == null)
return base.GetControllerInstance(requestContext, controllerType);
}
catch (HttpException ex)
{
if (ex.GetHttpCode() == 404)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);
return errorController;
}
else
throw ex;
}
return ObjectFactory.GetInstance(controllerType) as Controller;
}
I think its better to catch errors closer to where they originate. This is why i prefer the above to the Application_Error handler.
This is the second place to catch 404s.
Step 4: Add a NotFound route to Global.asax for urls that fail to be parsed into your app
This route should point to our Http404 action. Notice the url param will be a relative url because the routing engine is stripping the domain part here? That is why we have all that conditional url logic in Step 1.
routes.MapRoute("NotFound", "{*url}",
new { controller = "Error", action = "Http404" });
This is the third and final place to catch 404s in an MVC app that you don't invoke yourself. If you don't catch unmatched routes here then MVC will pass the problem up to ASP.NET (Global.asax) and you don't really want that in this situation.
Step 5: Finally, invoke 404s when your app can't find something
Like when a bad ID is submitted to my Loans controller (derives from MyController):
//
// GET: /Detail/ID
public ActionResult Detail(int ID)
{
Loan loan = this._svc.GetLoans().WithID(ID);
if (loan == null)
return this.InvokeHttp404(HttpContext);
else
return View(loan);
}
It would be nice if all this could be hooked up in fewer places with less code but i think this solution is more maintainable, more testable and fairly pragmatic.
Thanks for the feedback so far. I'd love to get more.
NOTE: This has been edited significantly from my original answer but the purpose/requirements are the same - this is why i have not added a new answer

ASP.NET MVC doesn't support custom 404 pages very well. Custom controller factory, catch-all route, base controller class with HandleUnknownAction - argh!
IIS custom error pages are better alternative so far:
web.config
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
</httpErrors>
</system.webServer>
ErrorController
public class ErrorController : Controller
{
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View();
}
}
Sample Project
Test404 on GitHub
Live website

Quick Answer / TL;DR
For the lazy people out there:
Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0
Then remove this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
And this is only for IIS7+ and IIS Express.
If you're using Cassini .. well .. um .. er.. awkward ...
Long, explained answer
I know this has been answered. But the answer is REALLY SIMPLE (cheers to David Fowler and Damian Edwards for really answering this).
There is no need to do anything custom.
For ASP.NET MVC3, all the bits and pieces are there.
Step 1 -> Update your web.config in TWO spots.
<system.web>
<customErrors mode="On" defaultRedirect="/ServerError">
<error statusCode="404" redirect="/NotFound" />
</customErrors>
and
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
</httpErrors>
...
<system.webServer>
...
</system.web>
Now take careful note of the ROUTES I've decided to use. You can use anything, but my routes are
/NotFound <- for a 404 not found, error page.
/ServerError <- for any other error, include errors that happen in my code. this is a 500 Internal Server Error
See how the first section in <system.web> only has one custom entry? The statusCode="404" entry? I've only listed one status code because all other errors, including the 500 Server Error (ie. those pesky error that happens when your code has a bug and crashes the user's request) .. all the other errors are handled by the setting defaultRedirect="/ServerError" .. which says, if you are not a 404 page not found, then please goto the route /ServerError.
Ok. that's out of the way.. now to my routes listed in global.asax
Step 2 - Creating the routes in Global.asax
Here's my full route section..
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new {favicon = #"(.*/)?favicon.ico(/.*)?"});
routes.MapRoute(
"Error - 404",
"NotFound",
new { controller = "Error", action = "NotFound" }
);
routes.MapRoute(
"Error - 500",
"ServerError",
new { controller = "Error", action = "ServerError"}
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional}
);
}
That lists two ignore routes -> axd's and favicons (ooo! bonus ignore route, for you!)
Then (and the order is IMPERATIVE HERE), I have my two explicit error handling routes .. followed by any other routes. In this case, the default one. Of course, I have more, but that's special to my web site. Just make sure the error routes are at the top of the list. Order is imperative.
Finally, while we are inside our global.asax file, we do NOT globally register the HandleError attribute. No, no, no sir. Nadda. Nope. Nien. Negative. Noooooooooo...
Remove this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Step 3 - Create the controller with the action methods
Now .. we add a controller with two action methods ...
public class ErrorController : Controller
{
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// Todo: Pass the exception into the view model, which you can make.
// That's an exercise, dear reader, for -you-.
// In case u want to pass it to the view, if you're admin, etc.
// if (User.IsAdmin) // <-- I just made that up :) U get the idea...
// {
// var exception = Server.GetLastError();
// // etc..
// }
return View();
}
// Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
public ActionResult ThrowError()
{
throw new NotImplementedException("Pew ^ Pew");
}
}
Ok, lets check this out. First of all, there is NO [HandleError] attribute here. Why? Because the built in ASP.NET framework is already handling errors AND we have specified all the shit we need to do to handle an error :) It's in this method!
Next, I have the two action methods. Nothing tough there. If u wish to show any exception info, then u can use Server.GetLastError() to get that info.
Bonus WTF: Yes, I made a third action method, to test error handling.
Step 4 - Create the Views
And finally, create two views. Put em in the normal view spot, for this controller.
Bonus comments
You don't need an Application_Error(object sender, EventArgs e)
The above steps all work 100% perfectly with Elmah. Elmah fraking wroxs!
And that, my friends, should be it.
Now, congrats for reading this much and have a Unicorn as a prize!

I've investigated A LOT on how to properly manage 404s in MVC (specifically MVC3), and this, IMHO is the best solution I've come up with:
In global.asax:
public class MvcApplication : HttpApplication
{
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 404)
{
Response.Clear();
var rd = new RouteData();
rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
rd.Values["controller"] = "Errors";
rd.Values["action"] = "NotFound";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
}
}
ErrorsController:
public sealed class ErrorsController : Controller
{
public ActionResult NotFound()
{
ActionResult result;
object model = Request.Url.PathAndQuery;
if (!Request.IsAjaxRequest())
result = View(model);
else
result = PartialView("_NotFound", model);
return result;
}
}
(Optional)
Explanation:
AFAIK, there are 6 different cases that an ASP.NET MVC3 apps can generate 404s.
(Automatically generated by ASP.NET Framework:)
(1) An URL does not find a match in the route table.
(Automatically generated by ASP.NET MVC Framework:)
(2) An URL finds a match in the route table, but specifies a non-existent controller.
(3) An URL finds a match in the route table, but specifies a non-existant action.
(Manually generated:)
(4) An action returns an HttpNotFoundResult by using the method HttpNotFound().
(5) An action throws an HttpException with the status code 404.
(6) An actions manually modifies the Response.StatusCode property to 404.
Normally, you want to accomplish 3 objectives:
(1) Show a custom 404 error page to the user.
(2) Maintain the 404 status code on the client response (specially important for SEO).
(3) Send the response directly, without involving a 302 redirection.
There are various ways to try to accomplish this:
(1)
<system.web>
<customErrors mode="On">
<error statusCode="404" redirect="~/Errors/NotFound"/>
</customError>
</system.web>
Problems with this solution:
Does not comply with objective (1) in cases (1), (4), (6).
Does not comply with objective (2) automatically. It must be programmed manually.
Does not comply with objective (3).
(2)
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
Problems with this solution:
Only works on IIS 7+.
Does not comply with objective (1) in cases (2), (3), (5).
Does not comply with objective (2) automatically. It must be programmed manually.
(3)
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
Problems with this solution:
Only works on IIS 7+.
Does not comply with objective (2) automatically. It must be programmed manually.
It obscures application level http exceptions. E.g. can't use customErrors section, System.Web.Mvc.HandleErrorAttribute, etc. It can't only show generic error pages.
(4)
<system.web>
<customErrors mode="On">
<error statusCode="404" redirect="~/Errors/NotFound"/>
</customError>
</system.web>
and
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
Problems with this solution:
Only works on IIS 7+.
Does not comply with objective (2) automatically. It must be programmed manually.
Does not comply with objective (3) in cases (2), (3), (5).
People that have troubled with this before even tried to create their own libraries (see http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html). But the previous solution seems to cover all the cases without the complexity of using an external library.

I really like cottsaks solution and think its very clearly explained. my only addition was to alter step 2 as follows
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
//if controller is ErrorController dont 'nest' exceptions
if(this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
errorController.Execute(new RequestContext(
httpContext, errorRoute));
return new EmptyResult();
}
#endregion
}
Basically this stops urls containing invalid actions AND controllers from triggering the exception routine twice. eg for urls such as asdfsdf/dfgdfgd

The only way I could get #cottsak's method to work for invalid controllers was to modify the existing route request in the CustomControllerFactory, like so:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
try
{
if (controllerType == null)
return base.GetControllerInstance(requestContext, controllerType);
else
return ObjectFactory.GetInstance(controllerType) as Controller;
}
catch (HttpException ex)
{
if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
{
requestContext.RouteData.Values["controller"] = "Error";
requestContext.RouteData.Values["action"] = "Http404";
requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);
return ObjectFactory.GetInstance<ErrorController>();
}
else
throw ex;
}
}
}
I should mention I'm using MVC 2.0.

Here is another method using MVC tools which you can handle requests to bad controller names, bad route names, and any other criteria you see fit inside of an Action method. Personally, I prefer to avoid as many web.config settings as possible, because they do the 302 / 200 redirect and do not support ResponseRewrite (Server.Transfer) using Razor views. I'd prefer to return a 404 with a custom error page for SEO reasons.
Some of this is new take on cottsak's technique above.
This solution also uses minimal web.config settings favoring the MVC 3 Error Filters instead.
Usage
Just throw a HttpException from an action or custom ActionFilterAttribute.
Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")
Step 1
Add the following setting to your web.config. This is required to use MVC's HandleErrorAttribute.
<customErrors mode="On" redirectMode="ResponseRedirect" />
Step 2
Add a custom HandleHttpErrorAttribute similar to the MVC framework's HandleErrorAttribute, except for HTTP errors:
<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
Inherits FilterAttribute
Implements IExceptionFilter
Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"
Private m_HttpCode As HttpStatusCode
Private m_Master As String
Private m_View As String
Public Property HttpCode As HttpStatusCode
Get
If m_HttpCode = 0 Then
Return HttpStatusCode.NotFound
End If
Return m_HttpCode
End Get
Set(value As HttpStatusCode)
m_HttpCode = value
End Set
End Property
Public Property Master As String
Get
Return If(m_Master, String.Empty)
End Get
Set(value As String)
m_Master = value
End Set
End Property
Public Property View As String
Get
If String.IsNullOrEmpty(m_View) Then
Return String.Format(m_DefaultViewFormat, Me.HttpCode)
End If
Return m_View
End Get
Set(value As String)
m_View = value
End Set
End Property
Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
If filterContext Is Nothing Then Throw New ArgumentException("filterContext")
If filterContext.IsChildAction Then
Return
End If
If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
Return
End If
Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
Return
End If
If ex.GetHttpCode <> Me.HttpCode Then
Return
End If
Dim controllerName As String = filterContext.RouteData.Values("controller")
Dim actionName As String = filterContext.RouteData.Values("action")
Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)
filterContext.Result = New ViewResult With {
.ViewName = Me.View,
.MasterName = Me.Master,
.ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
.TempData = filterContext.Controller.TempData
}
filterContext.ExceptionHandled = True
filterContext.HttpContext.Response.Clear()
filterContext.HttpContext.Response.StatusCode = Me.HttpCode
filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
End Sub
End Class
Step 3
Add Filters to the GlobalFilterCollection (GlobalFilters.Filters) in Global.asax. This example will route all InternalServerError (500) errors to the Error shared view (Views/Shared/Error.vbhtml). NotFound (404) errors will be sent to ErrorHttp404.vbhtml in the shared views as well. I've added a 401 error here to show you how this can be extended for additional HTTP error codes. Note that these must be shared views, and they all use the System.Web.Mvc.HandleErrorInfo object as a the model.
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})
Step 4
Create a base controller class and inherit from it in your controllers. This step allows us to handle unknown action names and raise the HTTP 404 error to our HandleHttpErrorAttribute.
Public Class BaseController
Inherits System.Web.Mvc.Controller
Protected Overrides Sub HandleUnknownAction(actionName As String)
Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
End Sub
Public Function Unknown() As ActionResult
Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
Return New EmptyResult
End Function
End Class
Step 5
Create a ControllerFactory override, and override it in your Global.asax file in Application_Start. This step allows us to raise the HTTP 404 exception when an invalid controller name has been specified.
Public Class MyControllerFactory
Inherits DefaultControllerFactory
Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
Try
Return MyBase.GetControllerInstance(requestContext, controllerType)
Catch ex As HttpException
Return DependencyResolver.Current.GetService(Of BaseController)()
End Try
End Function
End Class
'In Global.asax.vb Application_Start:
controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)
Step 6
Include a special route in your RoutTable.Routes for the BaseController Unknown action. This will help us raise a 404 in the case where a user accesses an unknown controller, or unknown action.
'BaseController
routes.MapRoute( _
"Unknown", "BaseController/{action}/{id}", _
New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)
Summary
This example demonstrated how one can use the MVC framework to return 404 Http Error Codes to the browser without a redirect using filter attributes and shared error views. It also demonstrates showing the same custom error page when invalid controller names and action names are specified.
I'll add a screenshot of an invalid controller name, action name, and a custom 404 raised from the Home/TriggerNotFound action if I get enough votes to post one =). Fiddler returns a 404 message when I access the following URLs using this solution:
/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound
cottsak's post above and these articles were good references.
Problems using CustomErrors redirectMode=ResponseRewrite
Elmah + MVC HandleErrorAttribute

My shortened solution that works with unhandled areas, controllers and actions:
Create a view 404.cshtml.
Create a base class for your controllers:
public class Controller : System.Web.Mvc.Controller
{
protected override void HandleUnknownAction(string actionName)
{
Http404().ExecuteResult(ControllerContext);
}
protected virtual ViewResult Http404()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View("404");
}
}
Create a custom controller factory returning your base controller as a fallback:
public class ControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
return base.GetControllerInstance(requestContext, controllerType);
return new Controller();
}
}
Add to Application_Start() the following line:
ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));

In MVC4 WebAPI 404 can be handle in the following way,
COURSES APICONTROLLER
// GET /api/courses/5
public HttpResponseMessage<Courses> Get(int id)
{
HttpResponseMessage<Courses> resp = null;
var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();
resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);
return resp;
}
HOME CONTROLLER
public ActionResult Course(int id)
{
return View(id);
}
VIEW
<div id="course"></div>
<script type="text/javascript">
var id = #Model;
var course = $('#course');
$.ajax({
url: '/api/courses/' + id,
success: function (data) {
course.text(data.Name);
},
statusCode: {
404: function()
{
course.text('Course not available!');
}
}
});
</script>
GLOBAL
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
RESULTS

Dealing with errors in ASP.NET MVC is just a pain in the butt. I tried a whole lot of suggestions on this page and on other questions and sites and nothing works good. One suggestion was to handle errors on web.config inside system.webserver but that just returns blank pages.
My goal when coming up with this solution was to;
NOT REDIRECT
Return PROPER STATUS CODES not 200/Ok like the default error handling
Here is my solution.
1.Add the following to system.web section
<system.web>
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/Error/404.aspx" />
<error statusCode="500" redirect="~/Error/500.aspx" />
</customErrors>
<system.web>
The above handles any urls not handled by routes.config and unhandled exceptions especially those encountered on the views. Notice I used aspx not html. This is so I can add a response code on the code behind.
2. Create a folder called Error (or whatever you prefer) at the root of your project and add the two webforms. Below is my 404 page;
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title >Page Not found</title>
<link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
<div class="top-nav">
<a runat="server" class="company-logo" href="~/"></a>
</div>
<div>
<h1>404 - Page Not found</h1>
<p>The page you are looking for cannot be found.</p>
<hr />
<footer></footer>
</div>
</body>
</html>
And on the code behind I set the response code
protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 404;
}
Do the same for the 500 page
3.To handle errors within the controllers. There's many ways to do it. This is what worked for me. All my controllers inherit from a base controller. In the base controller, I have the following methods
protected ActionResult ShowNotFound()
{
return ShowNotFound("Page not found....");
}
protected ActionResult ShowNotFound(string message)
{
return ShowCustomError(HttpStatusCode.NotFound, message);
}
protected ActionResult ShowServerError()
{
return ShowServerError("Application error....");
}
protected ActionResult ShowServerError(string message)
{
return ShowCustomError(HttpStatusCode.InternalServerError, message);
}
protected ActionResult ShowNotAuthorized()
{
return ShowNotAuthorized("You are not allowed ....");
}
protected ActionResult ShowNotAuthorized(string message)
{
return ShowCustomError(HttpStatusCode.Forbidden, message);
}
protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
Response.StatusCode = (int)statusCode;
string title = "";
switch (statusCode)
{
case HttpStatusCode.NotFound:
title = "404 - Not found";
break;
case HttpStatusCode.Forbidden:
title = "403 - Access Denied";
break;
default:
title = "500 - Application Error";
break;
}
ViewBag.Title = title;
ViewBag.Message = message;
return View("CustomError");
}
4.Add the CustomError.cshtml to your Shared views folder. Below is mine;
<h1>#ViewBag.Title</h1>
<br />
<p>#ViewBag.Message</p>
Now in your application controller you can do something like this;
public class WidgetsController : ControllerBase
{
[HttpGet]
public ActionResult Edit(int id)
{
Try
{
var widget = db.getWidgetById(id);
if(widget == null)
return ShowNotFound();
//or return ShowNotFound("Invalid widget!");
return View(widget);
}
catch(Exception ex)
{
//log error
logger.Error(ex)
return ShowServerError();
}
}
}
Now for the caveat.
It won't handle static file errors. So if you have a route such as example.com/widgets and the user changes it to example.com/widgets.html, they will get the IIS default error page so you have to handle IIS level errors some other way.

Try NotFoundMVC on nuget. It works , no setup.

My solution, in case someone finds it useful.
In Web.config:
<system.web>
<customErrors mode="On" defaultRedirect="Error" >
<error statusCode="404" redirect="~/Error/PageNotFound"/>
</customErrors>
...
</system.web>
In Controllers/ErrorController.cs:
public class ErrorController : Controller
{
public ActionResult PageNotFound()
{
if(Request.IsAjaxRequest()) {
Response.StatusCode = (int)HttpStatusCode.NotFound;
return Content("Not Found", "text/plain");
}
return View();
}
}
Add a PageNotFound.cshtml in the Shared folder, and that's it.

It seems to me that the standard CustomErrors configuration should just work however, due to the reliance on Server.Transfer it seems that the internal implementation of ResponseRewrite isn't compatible with MVC.
This feels like a glaring functionality hole to me, so I decided to re-implement this feature using a HTTP module. The solution below allows you to handle any HTTP status code (including 404) by redirecting to any valid MVC route just as you would do normally.
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="404.aspx" />
<error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>
This has been tested on the following platforms;
MVC4 in Integrated Pipeline Mode (IIS Express 8)
MVC4 in Classic Mode (VS Development Server, Cassini)
MVC4 in Classic Mode (IIS6)
Benefits
Generic solution which can be dropped into any MVC project
Enables support for traditional custom errors configuration
Works in both Integrated Pipeline and Classic modes
The Solution
namespace Foo.Bar.Modules {
/// <summary>
/// Enables support for CustomErrors ResponseRewrite mode in MVC.
/// </summary>
public class ErrorHandler : IHttpModule {
private HttpContext HttpContext { get { return HttpContext.Current; } }
private CustomErrorsSection CustomErrors { get; set; }
public void Init(HttpApplication application) {
System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");
application.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(object sender, EventArgs e) {
// only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {
int statusCode = HttpContext.Response.StatusCode;
// if this request has thrown an exception then find the real status code
Exception exception = HttpContext.Error;
if (exception != null) {
// set default error status code for application exceptions
statusCode = (int)HttpStatusCode.InternalServerError;
}
HttpException httpException = exception as HttpException;
if (httpException != null) {
statusCode = httpException.GetHttpCode();
}
if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {
Dictionary<int, string> errorPaths = new Dictionary<int, string>();
foreach (CustomError error in CustomErrors.Errors) {
errorPaths.Add(error.StatusCode, error.Redirect);
}
// find a custom error path for this status code
if (errorPaths.Keys.Contains(statusCode)) {
string url = errorPaths[statusCode];
// avoid circular redirects
if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {
HttpContext.Response.Clear();
HttpContext.Response.TrySkipIisCustomErrors = true;
HttpContext.Server.ClearError();
// do the redirect here
if (HttpRuntime.UsingIntegratedPipeline) {
HttpContext.Server.TransferRequest(url, true);
}
else {
HttpContext.RewritePath(url, false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext);
}
// return the original status code to the client
// (this won't work in integrated pipleline mode)
HttpContext.Response.StatusCode = statusCode;
}
}
}
}
}
public void Dispose() {
}
}
}
Usage
Include this as the final HTTP module in your web.config
<system.web>
<httpModules>
<add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
</httpModules>
</system.web>
<!-- IIS7+ -->
<system.webServer>
<modules>
<add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
</modules>
</system.webServer>
For those of you paying attention you will notice that in Integrated Pipeline mode this will always respond with HTTP 200 due to the way Server.TransferRequest works. To return the proper error code I use the following error controller.
public class ErrorController : Controller {
public ErrorController() { }
public ActionResult Index(int id) {
// pass real error code to client
HttpContext.Response.StatusCode = id;
HttpContext.Response.TrySkipIisCustomErrors = true;
return View("Errors/" + id.ToString());
}
}

Posting an answer since my comment was too long...
It's both a comment and questions to the unicorn post/answer:
https://stackoverflow.com/a/7499406/687549
I prefer this answer over the others for it's simplicity and the fact that apparently some folks at Microsoft were consulted. I got three questions however and if they can be answered then I will call this answer the holy grail of all 404/500 error answers on the interwebs for an ASP.NET MVC (x) app.
#Pure.Krome
Can you update your answer with the SEO stuff from the comments pointed out by GWB (there was never any mentioning of this in your answer) - <customErrors mode="On" redirectMode="ResponseRewrite"> and <httpErrors errorMode="Custom" existingResponse="Replace">?
Can you ask your ASP.NET team friends if it is okay to do it like that - would be nice to have some confirmation - maybe it's a big no-no to change redirectMode and existingResponse in this way to be able to play nicely with SEO?!
Can you add some clarification surrounding all that stuff (customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", REMOVE customErrors COMPLETELY as someone suggested) after talking to your friends at Microsoft?
As I was saying; it would be supernice if we could make your answer more complete as this seem to be a fairly popular question with 54 000+ views.
Update: Unicorn answer does a 302 Found and a 200 OK and cannot be changed to only return 404 using a route. It has to be a physical file which is not very MVC:ish. So moving on to another solution. Too bad because this seemed to be the ultimate MVC:ish answer this far.

Adding my solution, which is almost identical to Herman Kan's, with a small wrinkle to allow it to work for my project.
Create a custom error controller:
public class Error404Controller : BaseController
{
[HttpGet]
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View("404");
}
}
Then create a custom controller factory:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
}
}
Finally, add an override to the custom error controller:
protected override void HandleUnknownAction(string actionName)
{
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error404");
errorRoute.Values.Add("action", "PageNotFound");
new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}
And that's it. No need for Web.config changes.

1) Make abstract Controller class.
public abstract class MyController:Controller
{
public ActionResult NotFound()
{
Response.StatusCode = 404;
return View("NotFound");
}
protected override void HandleUnknownAction(string actionName)
{
this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
}
protected override void OnAuthorization(AuthorizationContext filterContext) { }
}
2) Make inheritence from this abstract class in your all controllers
public class HomeController : MyController
{}
3) And add a view named "NotFound" in you View-Shared folder.

I went through most of the solutions posted on this thread. While this question might be old, it is still very applicable to new projects even now, so I spent quite a lot of time reading up on the answers presented here as well as else where.
As #Marco pointed out the different cases under which a 404 can happen, I checked the solution I compiled together against that list. In addition to his list of requirements, I also added one more.
The solution should be able to handle MVC as well as AJAX/WebAPI calls in the most appropriate manner. (i.e. if 404 happens in MVC, it should show the Not Found page and if 404 happens in WebAPI, it should not hijack the XML/JSON response so that the consuming Javascript can parse it easily).
This solution is 2 fold:
First part of it comes from #Guillaume at https://stackoverflow.com/a/27354140/2310818. Their solution takes care of any 404 that were caused due to invalid route, invalid controller and invalid action.
The idea is to create a WebForm and then make it call the NotFound action of your MVC Errors Controller. It does all of this without any redirect so you will not see a single 302 in Fiddler. The original URL is also preserved, which makes this solution fantastic!
Second part of it comes from #Germán at https://stackoverflow.com/a/5536676/2310818. Their solution takes care of any 404 returned by your actions in the form of HttpNotFoundResult() or throw new HttpException()!
The idea is to have a filter look at the response as well as the exception thrown by your MVC controllers and to call the appropriate action in your Errors Controller. Again this solution works without any redirect and the original url is preserved!
As you can see, both of these solutions together offer a very robust error handling mechanism and they achieve all the requirements listed by #Marco as well as my requirements. If you would like to see a working sample or a demo of this solution, please leave in the comments and I would be happy to put it together.

I have gone through all articles but nothing works for me:
My requirement user type anything in your url custom 404 page should show.I thought it is very straight forward.But you should understand handling of 404 properly:
<system.web>
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/PageNotFound.aspx"/>
</customErrors>
</system.web>
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
I found this article very helpfull.should be read at once.Custome error page-Ben Foster

Related

Correctly handling custom errors in ASP.NET MVC?

I've spent the day going through a dozen or more sites, blogs, and SO answers, all claiming to provide the "right" way to implement custom error handling, in ASP.NET MVC.
None of them agree with each other, and most of them don't seem to work.
What I want:
Custom pages for the standard HTTP errors
That have the same look-and-feel as the standard pages of my site
Without having to completely redo everything I have in my Shared/_Layout.cspx
That work whether the user is authenticated or not
But that don't provide access to anything the user should be required to be authenticated to access
That should properly return the URL requested with the appropriate HTTP error code, rather than redirecting to a different page that returns HTTP 200
That redirects anything that isn't a valid route to the 404 page, whether it is a missing action, a missing controller, or a missing route
That allows for handling any sub-codes, like 404.3, that we need handled separately
That works the same in IIS7, IIS Express, and in Casini
That works in MVC5, but will continue to work in MVC6, MVC7, and whatever.
That doesn't involve separate code being added to each and every page
And, I want this to be the canonical answer. Not just "this is what I do", but "this is what Microsoft's developers intended and expected to be done".
Any ideas?
I'm not sure if this is the best way but it works for me and its seo friendly:
web.config
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound" />
</httpErrors>
</system.webServer>
you can define sub status codes too:
<error statusCode="404" subStatusCode="2" responseMode="ExecuteURL" path="/Error/NotFound" />
and this is how my error controller looks like:
public class ErrorController : Controller
{
//
// GET: /Error/
public ActionResult NotFound()
{
// you have to set your error codes manually
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
}
The following solution meets most of your requirements except:
But that don't provide access to anything the user should be required to be authenticated to access This is very dependent on your architecture of your application, however this method does not add any risk of this happening
That works the same in IIS7, IIS Express, and in Casini not used Casini so can't guarantee this
That works in MVC5, but will continue to work in MVC6, MVC7, and whatever. Haven't tried this in MVC6
That allows for handling any sub-codes, like 404.3, that we need handled separately It depends what you mean by this - you can return a Response.SubStatusCode to the client in your action results. If you mean to handle it within the application, then provided it is in the exception object thrown, you can access it.
Create an ErrorController - this allows you to tailor your end-user error pages and status codes.:
[AllowAnonymous]
public class ErrorController : Controller
{
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = 500;
return View();
}
public ActionResult UnauthorisedRequest()
{
Response.StatusCode = 403;
return View();
}
//Any other errors you want to specifically handle here.
public ActionResult CatchAllUrls()
{
//throwing an exception here pushes the error through the Application_Error method for centralised handling/logging
throw new HttpException(404, "The requested url " + Request.Url.ToString() + " was not found");
}
}
Add a route to catch all urls to the end of your route config - this captures all 404's that are not already caught by matching existing routes:
routes.MapRoute("CatchAllUrls", "{*url}", new { controller = "Error", action = "CatchAllUrls" });
In your global.asax:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
//Error logging omitted
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
IController errorController = new Controllers.ErrorController();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("area", "");
routeData.Values.Add("ex", exception);
if (httpException != null)
{
//this is a basic exampe of how you can choose to handle your errors based on http status codes.
switch (httpException.GetHttpCode())
{
case 404:
Response.Clear();
// page not found
routeData.Values.Add("action", "PageNotFound");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 500:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 403:
// server error
routeData.Values.Add("action", "UnauthorisedRequest");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
//add cases for other http errors you want to handle, otherwise HTTP500 will be returned as the default.
default:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
}
}
//All other exceptions should result in a 500 error as they are issues with unhandled exceptions in the code
else
{
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
In Summary:
Custom pages for the standard HTTP errors You have full control of the page returned to the user
That have the same look-and-feel as the standard pages of my site You can use your existign layout if you want to for the custom pages
Without having to completely redo everything I have in my Shared/_Layout.cspx as above
That work whether the user is authenticated or not This is partly down to your architecture but there is nothing in the proposed method that will interfere with user authentication
That should properly return the URL requested with the appropriate HTTP error code, rather than -redirecting to a different page that returns HTTP 200 There is no redirect to the error page so the correct http status code is retured
That redirects anything that isn't a valid route to the 404 page, whether it is a missing action, a missing controller, or a missing route
That doesn't involve separate code being added to each and every page.
There are 2 types of error handling.
Page-level errors
Application-level erros
For page-level errors you can use both web.config or you can create base controller and override OnException event.
protected override void OnException(ExceptionContext filterContext) { ... }
For application-level errors you can use global.asax Application_Error event
void Application_Error(object sender, EventArgs e) { ... }
Also you can check this link: https://www.simple-talk.com/dotnet/asp.net/handling-errors-effectively-in-asp.net-mvc/
One Way could be:
ErrorFilter:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
HandleErrorAttribute attribute = new HandleErrorAttribute();
filters.Add(attribute);
}
Override OnException method of controller
protected override void OnException(ExceptionContext filterContext)
{
EventLog.WriteEntry("YourProjectEventName", filterContext.Exception.ToString(), EventLogEntryType.Error);
base.OnException(filterContext);
}
Ref link:
http://www.prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc
http://www.codeproject.com/Articles/422572/Exception-Handling-in-ASP-NET-MVC
Hope this helps.

ASP.NET MVC 5 Custom Error Page

I am using a custom authorize attribute in a ASP.NET MVC 5 application like following:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
if (context.HttpContext.Request.IsAuthenticated)
{
context.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(context);
}
}
}
In system.web section of my web.config I mentioned error paths like:
<system.web>
<customErrors mode="On" defaultRedirect="/Error/Error">
<error statusCode="403" redirect="/Error/NoPermissions"/>
</customErrors>
</system.web>
But I am never redirected to my custom error page at /Error/NoPermissions. Instead the browser display the general error page saying "HTTP Error 403.0 - Forbidden".
[1]: Remove all 'customErrors' & 'httpErrors' from Web.config
[2]: Check 'App_Start/FilterConfig.cs' looks like this:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
[3]: in 'Global.asax' add this method:
public void Application_Error(Object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Server.ClearError();
var routeData = new RouteData();
routeData.Values.Add("controller", "ErrorPage");
routeData.Values.Add("action", "Error");
routeData.Values.Add("exception", exception);
if (exception.GetType() == typeof(HttpException))
{
routeData.Values.Add("statusCode", ((HttpException)exception).GetHttpCode());
}
else
{
routeData.Values.Add("statusCode", 500);
}
Response.TrySkipIisCustomErrors = true;
IController controller = new ErrorPageController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Response.End();
}
[4]: Add 'Controllers/ErrorPageController.cs'
public class ErrorPageController : Controller
{
public ActionResult Error(int statusCode, Exception exception)
{
Response.StatusCode = statusCode;
ViewBag.StatusCode = statusCode + " Error";
return View();
}
}
[5]: in 'Views/Shared/Error.cshtml'
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = (!String.IsNullOrEmpty(ViewBag.StatusCode)) ? ViewBag.StatusCode : "500 Error";
}
<h1 class="error">#(!String.IsNullOrEmpty(ViewBag.StatusCode) ? ViewBag.StatusCode : "500 Error"):</h1>
//#Model.ActionName
//#Model.ControllerName
//#Model.Exception.Message
//#Model.Exception.StackTrace
:D
Thanks everyone, but problem is not with 403 code. Actually the problem was with the way i was trying to return 403. I just changed my code to throw an HttpException instead of returning the HttpStatusCodeResult and every things works now. I can return any HTTP status code by throwing HttpException exception and my customErrors configuration catches all of them. May be HttpStatusCodeResult is not doing the exact job I expected it to do.
I just replaced
context.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
with
throw new HttpException((int)System.Net.HttpStatusCode.Forbidden, "Forbidden");
That's it.
Happy coding.
I also had this issue. Code in the OP’s question is perfectly working except the custom error code in <system.web> section in the web.config file. To fix the issue what I need to do was add the following code to <system.webServer>. Note that ‘webserver’ instead of ‘web’.
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" />
<error statusCode="403" responseMode="ExecuteURL" path="/Error/UnAuthorized" />
</httpErrors>
If someone is using following environment, here is the complete solution:
The Environment:
Visual Studio 2013 Update 4
Microsoft .NET Framework 4.5.1 with ASP.NET MVC 5
Project: ASP.NET Web Application with MVC & Authentication: Individual User Account template
Custom Attribute class:
Add the following class to your web site’s default namespace. The reason explained here in the accepted answer Stack Overflow question: Why does AuthorizeAttribute redirect to the login page for authentication and authorization failures?
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Then add the following code the web.config file
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" />
<error statusCode="403" responseMode="ExecuteURL" path="/Error/UnAuthorized" />
</httpErrors>
</system.webServer>
Following article explain more about this: ASP.NET MVC: Improving the Authorize Attribute (403 Forbidden)
And httpErrors in web.config section in this article: Demystifying ASP.NET MVC 5 Error Pages and Error Logging
Then add the ErrorController.cs to Controllers folder
public class ErrorController : Controller
{
// GET: UnAuthorized
public ActionResult UnAuthorized()
{
return View();
}
public ActionResult Error()
{
return View();
}
}
Then add a UnAuthorized.cshtml to View/Shared folder
#{
ViewBag.Title = "Your Request Unauthorized !"; //Customise as required
}
<h2>#ViewBag.Title.</h2>
This will show customised error page instead of browser generated error page.
Also note that for the above environment, it is not required to comment the code inside RegisterGlobalFilters method added by the template as suggested in one of the answers.
Please note that I just cut and paste code from my working project therefore I used Unauthorized instead OP’s NoPermissions in the above code.
These seem to be complicated workarounds. Here is basically all you need to do to get your custom error pages (CEP) working:
Add an Error Controller in your controllers folder.
Inside your Error Controller annotate the class with [HandleError].
For each error you want to display a CEP for, create an ActionResult method.
Create a View for each CEP inside ~/Views/Shared/Error folder, and customize it how you like. (The Error folder should have been created when you created your controller. If it did not, you will need to create the Error folder first.)
Open the web.config file at the root level *Note: there are two (2) web.config files. One in your Views folder, the other at the Root level of your application.
Inside <system.web>, add <customError mode="On" defaultRedirect="~/Error/Error">. Any statusCode you don't have a CEP for will be handled by the defaultRedirect.
For each error code you have a CEP for; add <error statusCode="[StatusCode]" redirect="~/Error/[CEP Name]">. You can leave off the file extension.
Controller Example:
namespace NAMESPACE_Name.Controllers
{
[HandleError]
public class ErrorController : Controller
{
// GET: Error
public ActionResult BadRequest()
{
return View();
}
public ActionResult Error()
{
return View();
}
public ActionResult Forbidden()
{
return View();
}
public ActionResult InternalServerError()
{
return View();
}
public ActionResult NotFound()
{
return View();
}
public ActionResult NotImplemented()
{
return View();
}
public ActionResult ServerBusyOrDown()
{
return View();
}
public ActionResult ServerUnavailable()
{
return View();
}
public ActionResult Timeout()
{
return View();
}
public ActionResult Unauthorized()
{
return View();
}
}
}
View Example:
#{
Layout = "~/Views/Shared/_FullWidthLayout.cshtml";
ViewBag.Title = "404 Error";
}
<div class="opensans margin-sides text-center">
<div class="text-center">
<h1 class="text-normal">Uh oh! Something went wrong!</h1>
<div class="img-container text-center">
<div class="centered">
<h1 class="bold">404 - Not Found</h1>
</div>
<img class="img text-center" src="~/Images/BackgroundImg.png" style="opacity: 0.15;" />
</div>
<p class="text-left">
This is usually the result of a broken link, a web page that has been moved or deleted, or a mistyped URL.
<ol class="text-left">
<li>Check the URL you entered in the address bar for typos,</li>
<li>If the address you entered is correct, the problem is on our end.</li>
<li>Please check back later as the resource you requested could be getting worked on,</li>
<li>However, if this continues for the resource you requested, please submit a trouble ticket.</li>
</ol>
</p>
</div>
</div>
Web.Config Example:
<customErrors mode="On" defaultRedirect="~/Error/Error">
<!--The defaultRedirect page will display for any error not listed below.-->
<error statusCode="400" redirect="~/Error/BadRequest"/>
<error statusCode="401" redirect="~/Error/Unauthorized"/>
<error statusCode="403" redirect="~/Error/Forbidden"/>
<error statusCode="404" redirect="~/Error/NotFound"/>
<error statusCode="408" redirect="~/Error/Timeout"/>
<error statusCode="500" redirect="~/Error/InternalServerError"/>
<error statusCode="501" redirect="~/Error/NotImplemented"/>
<error statusCode="502" redirect="~/Error/ServerUnavailable"/>
<error statusCode="503" redirect="~/Error/ServerBusyOrDown"/>
</customErrors>
That's it! Step-by-step solution to a problem that really shouldn't be a problem!
Again, any statusCode you don't have a CEP for, will be handled by the defaultRedirect page.
since I ran into a very similar issue I wanted to shed more light on it.
customErrors will only capture actual http exceptions thrown in your ASP.NET application. The HttpStatusCodeResult doesn't throw an exception though. It just writes a response with the according status code, which makes more sense in your example.
If you are running on IIS 7.0 or higher you should be using httpErrors now, as this will show you custom error pages in all cases. This is an IIS level setting.
I wrote a whole blog post about this to explain the differences:
http://dusted.codes/demystifying-aspnet-mvc-5-error-pages-and-error-logging
Update
You only need to do that special redirect for 403 errors. All other 500 errors should take effect through your defaultRedirect="/Error/Error" setting in customErrors. However, you need to remove or comment out the HandleErrorAttribute registration in the App_Start/FilterConfig.cs file for custom errors to actually work. Otherwise, that attribute will redirect all errors to the Error.cshtml file in the Views/Shared directory.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// remove this line below
//filters.Add(new HandleErrorAttribute());
}
}
Original Answer
As far as I know, you cannot use customErrors in the web.config to handle 403 errors for some reason. I feel your pain as it seems like something that should be as simple as the code you already have, but apparently 403 errors are treated as a web server concern.
What you can do instead is just redirect the user to your desired "NoPermissions" page like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
if (context.HttpContext.Request.IsAuthenticated)
{
context.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
action = "NoPermissions",
controller = "Error",
area = ""
}));
}
else
{
base.HandleUnauthorizedRequest(context);
}
}
}
The request will have a 200 status code instead of a 403, but if you can live with that, this is an easy workaround.
Here is a similar SO question for more info: Returning custom errors.
Also, this article explains how to go the IIS route: http://kitsula.com/Article/MVC-Custom-Error-Pages

throwing/returning a 404 actionresult or exception in Asp.net MVC and letting IIS handle it

how do I throw a 404 or FileNotFound exception/result from my action and let IIS use my customErrors config section to show the 404 page?
I've defined my customErrors like so
<customErrors mode="On" defaultRedirect="/trouble">
<error statusCode="404" redirect="/notfound" />
</customErrors>
My first attempt at an actionResult that tries to add this doesnt work.
public class NotFoundResult : ActionResult {
public NotFoundResult() {
}
public override void ExecuteResult(ControllerContext context) {
context.HttpContext.Response.TrySkipIisCustomErrors = false;
context.HttpContext.Response.StatusCode = 404;
}
}
But this just shows a blank page and not my /not-found page
:(
What should I do?
ASP.NET MVC 3 introduced the HttpNotFoundResult action result which should be used in preference to manually throwing the exception with the http status code.
This can also be returned via the Controller.HttpNotFound method on the controller:
public ActionResult MyControllerAction()
{
...
if (someNotFoundCondition)
{
return HttpNotFound();
}
}
Prior to MVC 3 you had to do the following:
throw new HttpException(404, "HTTP/1.1 404 Not Found");
You can call Controller.HttpNotFound
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.httpnotfound(v=vs.98).aspx
if (model == null)
{
return HttpNotFound();
}
This could help as well;
ASP.NET MVC : Throwing 404 Exceptions Manually From Controller When Model is Null

Global error handling (outside of controller) in ASP.NET MVC

Let's say I put the following code somewhere in a Master page in my ASP.NET MVC site:
throw new ApplicationException("TEST");
Even with a [HandleError] attribute placed on my controller, this exception still bubbles up. How can I deal with errors like this? I would like to be able to route to an Error page and still be able to log the exception details.
What is the best way to deal with something like this?
Edit: One solution I was considering would be to add a a new controller: UnhandledErrorController. Can I put in an Application_Error method in Global.asax and then redirect to this controller (where it decides what to do with the exception)?
Note: the defaultRedirect in the customErrors web.config element does not pass along the exception info.
Enable customErrors:
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/Error/Unauthorized" />
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
and redirect to a custom error controller:
[HandleError]
public class ErrorController : BaseController
{
public ErrorController ()
{
}
public ActionResult Index ()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View ("Error");
}
public ActionResult Unauthorized ()
{
Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return View ("Error401");
}
public ActionResult NotFound ()
{
string url = GetStaticRoute (Request.QueryString["aspxerrorpath"] ?? Request.Path);
if (!string.IsNullOrEmpty (url))
{
Notify ("Due to a new web site design the page you were looking for no longer exists.", false);
return new MovedPermanentlyResult (url);
}
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View ("Error404");
}
}
As MVC is built on top of asp.net you should be able to define a global error page in web.config, just like you could in web forms eg.
<customErrors mode="On" defaultRedirect="~/ErrorHandler" />
You can create a Filter that looks for an Exception in the OnActionExecuted method:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class WatchExceptionAttribute : ActionFilterAttribute {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
if (filterContext.Exception != null) {
// do your thing here.
}
}
}
Then you can put [WatchException] on a Controller or Action Method, and it will let log exceptions. If you have a lot of Controllers, that might be tedious, so if you have a common Base Controller you can override OnActionExecuted there and do the same thing. I prefer the filter method.
As far as what page to display, you'll need to create a customErrors section in your web.config and set it up for whatever status codes you want to handle.
Example:
<customErrors defaultRedirect="GenericError.htm" mode="RemoteOnly">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
As far as logging exceptions, I would recommend using ELMAH. It integrates nicely with ASP.NET MVC sites.

Is there a way in ASP.NET MVC to handle different Response.StatusCode values?

By default, the MVC Authorize attribute sets the HttpContext.Response.StatusCode = 401 when a user is not authorized and the section in the web.config routes to the loginUrl property.
I want to do something similar with other response codes. For example, I have an attribute called ActiveAccount which verifies the user's account is currently active and then allows them access to the controller. If they are not active I want to route them to a specific controller and view (to update their account).
I'd like to copy the Authorize attributes way of handling this and set the StatusCode to something like 410 (warning: preceding number pulled out of thin air) and have the user routed to a location defined in the web.config file.
What can I do to implement this behavior? Or is there a simpler method?
Edit: Results
I ended up avoiding the StatusCode and just performing a redirection from within the attribute as this was much simpler. Here is my code in a nutshell:
// using the IAuthorizationFilter allows us to use the base controller's
// built attribute handling. We could have used result as well, but Auth seems
// more appropriate.
public class ActiveAccountAttribute: FilterAttribute, IAuthorizationFilter
{
#region IAuthorizationFilter Members
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
return;
// ... code which determines if our customer account is Active
if (!user.Status.IsActive)
filterContext.Result = new RedirectToRouteResult("Default", new RouteValueDictionary(new {controller = "Account"}));
}
#endregion
}
You could inherit the RedirectToRouteResult class and add a constructor parameter for the status code.
public class StatusRedirectResult : RedirectToRouteResult
private string _status;
public StatusRedirectResult(string action, RouteValueDictionary routeValues, string statusCode)
{
_status = statusCode;
base.RedirectToRouteResult(action, routeValues);
}
public override ExecuteResult(ControllerContext context)
{
context.HttpContext.Current.Response.Status = _status;
base.ExecuteResult(context);
}
}
To use this in a controller action, just
return StatusRedirect("NewAction", new RouteValueDictionary(new { controller = "TheController" }, "410");
it should be two piece of code.
in your controller you return an error code by
Response.StatusCode = (int)HttpStatusCode.NotFound;
then you custom error code to desired route
<customErrors mode="On">
<error statusCode="404" redirect="~/Profile/Update" />
</customErrors>

Resources