When an error occurs in my ASP.NET MVC 4 application, I would like to customize a view for the user depending on the type of error. For example, page not found or an exception has occurred (with some user-friendly details about the exception). I've checked other examples of how to do this on StackOverflow and other online sources, but none of the answers are working for me.
The basic [HandleError] attribute does not seem to be working in VS2012 with an MVC 4 application targeting .NET 4.5. Here is the code I have in my home controller:
[HandleError]
public ActionResult Index()
{
Response.TrySkipIisCustomErrors = true; //doesn't work with or without this
throw new NullReferenceException("Uh oh, something broke.");
}
It is just throwing an exception, and I would expect the default ~/Shared/Error.cshtml view to be returned because of the [HandleError] attribute, but all I get is an HTTP 500 Internal Server Error indicating that the page could not be displayed. I checked my web.config, and different configurations seem to be behaving weird. In the section, it currently contains:
<customErrors mode="On" />
(I've tried adding defaultRedirect and with customErrors mode="Off" as well but that didn't have any effect... neither the shared Error view or the CustomError view I have is being rendered. If I change customErrors mode to off, then I can see the exception details as expected, so it is throwing the "Uh oh, something broke" exception properly.
I've also tried adding an OnException handler to the HomeController, and although I can debug through and see that the OnException event is being raised, it doesn't make any difference:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
filterContext.ExceptionHandled = true;
if (filterContext == null)
{
filterContext.Result = View("CustomError");
return;
}
Exception e = filterContext.Exception;
// TODO: Log the exception here
ViewData["Exception"] = e; // pass the exception to the view
filterContext.Result = View("CustomError");
}
I have also tried changing [HandleError] to specify a view, but that doesn't seem to do anything either:
[HandleError(View="CustomError")]
Any help would be much appreciated. Please let me know if you need any more details.
I also went on a seamingly endless journey of reading SO answers and assorted blog postings trying to get custom error pages to work. Below is what finally worked for me.
The first step is to use IIS Express for debugging instead of the built-in Cassini web server to "guarantee" that the debug experience will mirror the live environment.
Create a controller to handle application errors and add an action for each custom error you will handle. The action should do any logging, set the status code, and return the view.
public class ErrorsController : Controller
{
// 404
[HttpGet]
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
// I also have test actions so that I can verify it's working in production.
[HttpGet]
public ActionResult Throw404()
{
throw new HttpException((int)HttpStatusCode.NotFound, "demo");
}
}
Configure the customErrors section in web.config to redirect to your custom error actions.
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="Errors/InternalServerError">
<error statusCode="400" redirect="Errors/BadRequest" />
<error statusCode="403" redirect="Errors/Forbidden" />
<error statusCode="404" redirect="Errors/NotFound" />
<error statusCode="500" redirect="Errors/InternalServerError" />
</customErrors>
Add the httpErrors section to system.webServer and set the errorMode to Detailed in web.config. Why this works is a mystery to me, but this was the key.
<system.webServer>
<httpErrors errorMode="Detailed" />
Add a catchall route last to the defined routes to direct 404s to the custom page.
// catchall for 404s
routes.MapRoute(
"Error",
"{*url}",
new {controller = "Errors", action = "NotFound"});
You may also want to create a custom HandleErrorAttribute and register it as a global filter to log 500 errors.
These steps worked for me in both development (IIS Express) and production (IIS7) environments. You need to change customErrors mode="On" to see the effect in development.
I seem to recall that you had to call the page from a non-localhost IP address (typically another computer). And it has to be an IIS based server, not the built-in development server (so IIS or IIS Express, but then you have to configure IIS Express for external access, which is a pain).
You can in fact debug it, you have to configure your local server on your debug computer to accept external requests, then call your local server from the remote server.
I faced a similar problem and lost sometime trying to work out what was going on. So just in case any others face a similar problem here is what my problem was.
The error page was trying to use my _Layout page. Just ensure that you Error.cshtml page has
#{
Layout = null;
}
Try adding the following attribute to the customErrors tag:
defaultRedirect="Error"
This explicitly defines to which view MVC should redirect the user when the error is thrown and no view is specified by default in the attribute. This will of course only work if Error.cshtml exists in the Shared views folder.
Related
I'm new to stackOverflow & MVC3 as well!
I've hosted an application and it redirect me to the errorPage (InternalError.htm): as I configured in the config file! But I want to change this settings, in order to show he error message, something like
line 23 : //exception Or Error here
can someone remind me the tag&the attribute to add ? debut=true ??? where ??? etc.
kind regards !
You can use customErrors mode="RemoteOnly" and you will see errors if you are on your local computer
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/system/Error/FriendlyError">
<error statusCode="403" redirect="~/system/Error/accessDenied" />
</customErrors>
</system.web>
I used a custom error handler.
Add the following code to the Global.asax.cs:
protected void Application_Error()
{
// if the debugger is attached, do not show the custom error page
if (System.Diagnostics.Debugger.IsAttached)
{
return;
}
try
{
Exception exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
RouteData routeData = new RouteData();
routeData.Values["controller"] = "MyCustomErrorController";
routeData.Values["action"] = "Index";
routeData.Values["exception"] = exception;
IController errorsController = new MyCustomErrorController();
var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
errorsController.Execute(rc);
}
catch (Exception ex)
{
// if an error occurs within this method, do nothing
// app will automatically show the browser's default error page
}
}
I also added custom code to my error page to show the exception details if the user is a super user. So if I'm not in debug mode or on the live site, I can easily see the error details (if I'm logged in as the admin).
To better understand the code, google or step through it using the debugger.
You are looking for the customErrors attribute (MSDN).
<customErrors defaultRedirect="url"
mode="On|Off|RemoteOnly">
<error. . ./>
</customErrors>
On a local development machine you can set it to Off or RemoteOnly. On your production machine you set it to On or RemoteOnly.
In your case, if it's set to RemoteOnly you can see the exception details when you remote desktop to the production machine. If that's not possible just switch it to Off.
I throw 404 error from application.
Page for 404 error is in the /ErrorPages/Error_404.cshtml
In this file I have only HTML code and it works fine.
But if I add some razor code it throws configuration error in browser.
I add for example layout or some #Html.ActionLink(...
This is error:
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".
This is how I produce 404:
public ActionResult Search(string searchTerm)
{
if (city == null)
{
throw new HttpException(404, "Some description");
}
else
{
return RedirectToAction("Index", "Home", new
{...
}
}
And when there is no razor code in error page it works and if not I get message from above.
When I set in web config 'mode=Off' then I get error message:
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /CityPage/Navigation/Search
This is value from web config
<customErrors mode="Off">
<error statusCode="404" redirect="\ErrorPages\Error_cp404.cshtml" />
</customErrors>
You shouldn't be attempting to render a .cshtml page directly. Those are Razor views. In ASP.NET MVC views are served through controller actions.
So:
<error statusCode="404" redirect="Errors/Error404" />
where you could have an ErrorsController:
public class ErrorsController: Controller
{
public ActionResult Error404()
{
return View();
}
}
You may also checkout the following answer for an alternative way to handle errors.
You might also need to add the following if you are running in IIS7+:
<system.webServer>
...
<httpErrors errorMode="Detailed" />
</system.webServer>
I've been struggling all day to implement error handling in my ASP.NET MVC 2 app. I've looked at a variety of techniques, but none work properly. I'm using MVC2 and .NET 4.0 (started the project before MVC3 was released; we'll upgrade after we deliver our initial release).
At this point, I'll be happy to properly handle 404 and 500 errors -- 403 (authorization required) would be great, too, followed by various other specific responses. Right now, I either get all 404s, all 500s, all 302s before the 404, or all 302s before the 500.
Here are my requirements (which should be pretty close to the basic requirements of HTTP):
If a resource is not found, throw a 404, and display a 404-specific page with the requested URL. DO NOT return an intermediate response code like 302. Ideally, keep the requested URL, rather than showing a new URL like /Error/NotFound -- but if the latter displays, be sure we didn't return a redirect response to get it.
If an internal server error occurred, throw a 500, and display a 500-specific error with some indication of what went wrong. Again, don't return an intermediate response code, and ideally don't change the URL.
Here's what I'd consider a 404:
Static file not found: /Content/non-existent-dir/non-existent-file.txt
Controller not found: /non-existent-controller/Foo/666
Controller found, but Action not found: /Home/non-existent-action/666
Controller and action found, but the action can't find the requested object: /Home/Login/non-existent-id
Here's what I'd consider a 500:
Post a bad value: POST /User/New/new-user-name-too-long-for-db-column-constraint
Non-data-related problem, like a Web Service endpoint not responding
Some of these problems need to be identified by specific controllers or models, and then the controllers should throw the appropriate HttpException. The rest should be handled more generically.
For 404 case #2, I tried to use a custom ControllerFactory to throw a 404 if the controller can't be found.
For 404 case #3, I've tried to use a custom base controller to override HandleUnknownAction and throw a 404.
In both cases, I get a 302 before the 404. And, I never get 500 errors; if I modify Web.config to put a typo in my Web Service endpoint, I still get a 302, then a 404 saying the URL (controller/action) which uses the Web Service can't be found.
I also get the requested URL as a(n unwanted) querystring param: /Error/NotFound?aspxerrorpath=/Home/non-existent-action
Both of these techniques came from http://www.niksmit.com/wp/?p=17 (How to get normal 404 (Page not found) error pages using ASP.Net MVC), pointed to from http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/
If in Web.config I have <customErrors mode="On" defaultRedirect="~/Error/Unknown" redirectMode="ResponseRedirect" />, I get the appropriate response code, but my Error controller never gets called. Taking out the redirectMode attribute gets me the MVC error views, but with an intervening 302 and a changed URL -- and always the same controller (Unknown = 500; if I change it to NotFound everything looks like a 404).
Here are some of the other things I've read and tried to implement:
http://www.davidjuth.com/asp-net-mvc-error-handler.aspx
http://sanjayuttam.com/wordpress/index.php/c-sharp/c-sharp-code-examples/error-handling-in-asp-net-mvc-1-part-2-of-2/
http://blog.hebbink.com/post/2010/12/14/NET-custom-404-error-page-returns-302-for-http-status.aspx
http://blog.dantup.com/2009/04/aspnet-mvc-handleerror-attribute-custom.html
http://weblogs.asp.net/scottgu/archive/2008/07/14/asp-net-mvc-preview-4-release-part-1.aspx
.. along with a bunch of StackOverflow posts.
Seems to me this sort of error handling is pretty basic to Web apps, and the MVC framework ought to have defaults that do this out of the box, and let people extend it to work otherwise. Perhaps they'll do it in a future release. In the meantime, can someone give me comprehensive details on how to implement proper HTTP responses?
Here's one technique you could use. Define an ErrorsController which will serve the error pages:
public class ErrorsController : Controller
{
public ActionResult Http404()
{
Response.StatusCode = 404;
return Content("404", "text/plain");
}
public ActionResult Http500()
{
Response.StatusCode = 500;
return Content("500", "text/plain");
}
public ActionResult Http403()
{
Response.StatusCode = 403;
return Content("403", "text/plain");
}
}
and then in Global.asax you could subscribe for the Application_Error event where you could log the exception and execute the corresponding action of the ErrorsController:
protected void Application_Error(object sender, EventArgs e)
{
var app = (MvcApplication)sender;
var context = app.Context;
var ex = app.Server.GetLastError();
context.Response.Clear();
context.ClearError();
var httpException = ex as HttpException;
var routeData = new RouteData();
routeData.Values["controller"] = "errors";
routeData.Values["exception"] = ex;
routeData.Values["action"] = "http500";
if (httpException != null)
{
switch (httpException.GetHttpCode())
{
case 404:
routeData.Values["action"] = "http404";
break;
case 403:
routeData.Values["action"] = "http403";
break;
case 500:
routeData.Values["action"] = "http500";
break;
}
}
IController controller = new ErrorsController();
controller.Execute(new RequestContext(new HttpContextWrapper(context), routeData));
}
And now all that's left is to start throwing proper exceptions:
public class HomeController : Controller
{
public ActionResult Index()
{
throw new HttpException(404, "NotFound");
}
}
For HTTP 404 errors (without redirects) take a look at my blog post on the subject. This might give you some good ideas:
http://hectorcorrea.com/blog/returning-http-404-in-asp-net-mvc/16
This doesn't answer your question, but it is important to note that HTTP status 500 indicates that something went wrong on the server, so your example:
POST /User/New/new-user-name-too-long-for-db-column-constraint
Is not valid grounds to throw a 500, its a data validation issue and should be handled by MVC data annotations or a jQuery validation framework or etc. Just showing an error message next to the TextBox saying "User Name too long" is much better.
This is a very old question. but I thought It's worth it if I introduce you to a much much cleaner way to handle Http Exceptions that I saw in dear "Jesse Webb's answer".
The solution is to use the httpErrors element of the system.webServer section:
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
<error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>
You also can log all exceptions in this way. "Read the "Jesse Webb's answer"".
This really feels much cleaner and also works as well as every other solution (without redirect).
Note: This only works work in IIS 7 and and newer. (Because of the httpErrors element which was recently added.
Here's how I have it defined (locally, on my development machine):
<customErrors mode="On" defaultRedirect="Error.aspx">
<error statusCode="404" redirect="NotFound.aspx" />
</customErrors>
And I have the [HandleError] attribute:
[Authorize]
[HandleError]
public class HomeController : Controller
{ // etc.
Yet when I type in http://localhost:1986/blah, I get the following error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /NotFound.aspx
The URL it's trying to go to is as you would expect:
http://localhost:1986/NotFound.aspx?aspxerrorpath=/blah
So it IS attempting to go to the custom error file -- however it can't find it. I do have NotFound.aspx in the Shared directory -- same place as the Error.aspx supplied by Microsoft as a default. Why can't it find it?
If the Error.aspx and NotFound.aspx are in the shared directory is there a controller wired to served them? If you do not have some sort of controller route configured to serve the files then the fact that they are in the shared folder is irrelevant.
You have a few options, you could create an ErrorController which will handle the requests for those views and define routes pointing to those controller actions:
[OutputCache(CacheProfile = "Default", VaryByParam = "none")]
public class ErrorController : DefaultAreaBaseController
{
public ViewResult ServiceUnavailable() {
Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
return View("ServiceUnavailable");
}
public ViewResult ServerError() {
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("ServerError");
}
public new ViewResult NotFound() {
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View("NotFound");
}
}
Or as an alternative, you can create ignore routes pointing at the physical files and place the error pages somewhere other than the Views folder (like your root directory):
routes.IgnoreRoute("Error.aspx/{*pathInfo}");
routes.IgnoreRoute("NotFound.aspx/{*pathInfo}");
Either of these solutions is viable however depending on your configuration using an IgnoreRoute() may be more ideal as it will forgo the need to pipe the request to MVC only to serve a static error page.
Option One:
is to build an Errors Controller with a "NotFound" view along with a "Unknown" view. This will take anything that is a 500 Server error or a 404 NotFound error and redirect you to the appropriate URL. I don't totally love this solution as the visitor is always redirected to an error page.
http://example.com/Error/Unknown
<customErrors mode="On" defaultRedirect="Error/Unknown">
<error statusCode="404" redirect="Error/NotFound" />
<error statusCode="500" redirect="Error/Unknown" />
</customErrors>
wwwroot/
Controllers
Error.cs
Views/
Error/
NotFound.aspx
Unknown.aspx
Option Two:
I Definitely don't prefer this method (as it is basically reverting back to web forms, The second option is to simply have a static Error.aspx page and ignore the route in MVC), but it works none the less. What you're doing here is ignoring a "Static" directory, placing your physical Error pages in there, and skirting around MVC.
routes.IgnoreRoute("/Static/{*pathInfo}"); //This will ignore everything in the "Static" directory
wwwroot/
Controllers/
Static/
Error.aspx
Views/
Option Three:
The third option (THIS IS MY FAVORITE) is to return an Error View from whatever view is catching the error. This would require you to code up Try/Catch blocks along the way for "known" errors and then you can use HandleError for the unknown errors that might creep up. What this will do is preserve the originally requested URL but return the ERROR view.
EXAMPLE:
http://example.com/Products/1234 will show a details page for ProductID 1234
http://example.com/Products/9999 will show a NotFound error page because ProductID 9999 doesn't exist
http://example.com/Errors/NotFound "should" never be shown because you handle those errors individually in your controllers.
Web.Config
<customErrors mode="On">
</customErrors>
Controller
// Use as many or as few of these as you need
[HandleError(ExceptionType = typeof(SqlException), View = "SqlError")]
[HandleError(ExceptionType = typeof(NullReferenceException), View = "NullError")]
[HandleError(ExceptionType = typeof(SecurityException), View = "SecurityError")]
[HandleError(ExceptionType = typeof(ResourceNotFoundException), View = "NotFound")]
Public Class ProductController: Controller{
public ViewResult Item(string itemID)
{
try
{
Item item = ItemRepository.GetItem(itemID);
return View(item);
}
catch()
{
return View("NotFound");
}
}
}
Folder Structure
wwwroot/
Controllers/
Shared/
NotFound.aspx
NullError.aspx
SecurityError.aspx
SqlError.aspx
Views/
Option Four:
The last option would be that you build your own custom filter for things like ResourceNotFoundException and attach it to your controller class. This will do the exact same thing as above but with the added benefit of sending the error code down the line to the client as well.
Richard Dingwall talks about it on his blog.
Your mixing web forms and MVC concepts here. Turn custom errors off in the web.config. Then in the HandleError attribute optionally specify the type and view, by default error.aspx is searched for in views\CurrentController then views\shared. Whilst you can get 404 handling working with the HandleError filter you probably want to create a filter just for 404 handling, the reasons and how to are explained in detail here:
http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/
I have the following HandleError filter on my controller:
[HandleError(ExceptionType = typeof(ArgumentException), View = "DestinationError")]
I've set-up the Web.Config so that customErrors are on. The problem I'm having is that the HandleError filter is working fine, when I run the app locally out of Visual Studio, but when I deploy it to the server all I get is a 500 Internal Server Error, indicating the Error view cannot be found.
Has anyone come across this before, I'm suspicious that routing may be the root cause of the problem (hoho). The site gets deployed into a directory in the web root, rather than into the wwwroot itself, so perhaps IIS cannot locate the error file.
To answer my own question the magic is to turn off HTTP Errors in IIS. I'm not delighted in this workaround, so if anyone has any better ideas, I'd love to hear them.
Otherwise you can use the Web.Config configuration and set it to the expected controller's actions.
Like this:
<customErrors mode="On" defaultRedirect="/Error">
<error statusCode="404" redirect="/Error/NotFound"/>
</customErrors>
Then imagine you have an Error controlller (/Error) which points out to an index action
public class ErrorController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("Index");
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View("NotFound");
}
}
What if you try the following?
Response.TrySkipIisCustomErrors = true;
I had the same problem after migrating to MVC 3 RC. Managed to get around it by adding the layout / master page.
#inherits System.Web.Mvc.WebViewPage<System.Web.Mvc.HandleErrorInfo>
#{
View.Title = "Error";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Now the internal server error is gone, but I think it's a bug somewhere.