Is there a built in or a proper way to handle errors in asp.net mvc 3?
This is what I want to do:
If the application crashes, or throws an error, it goes to a specific error page.
I can throw my own error from the controller action. (and it goes to an error page).
I found the following ways:
I see there is a long way to do it
here. (for v1 and v2 but also
applies to v3).
Using errorhandle attribute here.
How do I handle this the proper way?
If the solution is similar or is like #1 in the list above, I am using ninject and I have not created a base class. How do I still do this?
For Global Error Handling
All you have to do is change the customErrors mode="On" in web.config page
Error will be displayed through Error.cshtml resides in shared folder.
Make sure that Error.cshtml Layout is not null.
[It sould be something like: #{ Layout = "~/Views/Shared/_Layout.cshtml"; }
Or remove Layout=null code block]
A sample markup for Error.cshtml:-
#{ Layout = "~/Views/Shared/_Layout.cshtml"; }
#model System.Web.Mvc.HandleErrorInfo
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h2>
Sorry, an error occurred while processing your request.
</h2>
<p>Controller Name: #Model.ControllerName</p>
<p>Action Name : #Model.ActionName</p>
<p>Message: #Model.Exception.Message</p>
</body>
</html>
For Specific Error Handling
Add HandleError attribute to specific action in controller class. Provide 'View' and 'ExceptionType' for that specific error.
A sample NotImplemented Exception Handler:
public class MyController: Controller
{
[HandleError(View = "NotImplErrorView", ExceptionType=typeof(NotImplementedException))]
public ActionResult Index()
{
throw new NotImplementedException("This method is not implemented.");
return View();
}
}
I would suggest implementing a custom HandleErrorAttribute action filter.
See this link for more details:
http://msdn.microsoft.com/en-us/library/dd410203%28v=vs.90%29.aspx
Setting up a HandleErrorAttribute action filter gives you complete control over which actions are handled by the filter, and it's easy to set at the controller level, or even at the site level by setting it up on a custom base controller, and having all of your controllers inherit from the base controller.
Something else I do with this, is I have a separate HandleJsonErrorAttribute that responds to Ajax calls by returning a Json response, rather than the custom page.
UPDATE:
Per some questions below, here is an example of a HandleJsonErrorAttribute that I use:
public class HandleJsonErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
var serviceException = filterContext.Exception as ServiceException;
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.Result = new JsonResult { Data = new { message = serviceException == null ? "There was a problem with that request." : serviceException.Message } };
filterContext.ExceptionHandled = true;
}
}
And here is the jQuery that I use to handle these unhanded exceptions:
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
showPopdown($.parseJSON(jqXHR.responseText).message);
});
This allows my Ajax methods to be very lightweight -- they just handle returning normal Json, and in the event of an unhanded exception, a message w/ an error status code gets wrapped in Json and returned.
Also, in my implementation, I have a custom ServiceException that I throw from services, and this sends the message from the service layer instead of a generic message.
The easiest way I think you can do that is using the elmah library.
Take a look at this: http://code.google.com/p/elmah/wiki/MVC and this
http://www.hanselman.com/blog/ELMAHErrorLoggingModulesAndHandlersForASPNETAndMVCToo.aspx
I think the easiest way is using ExceptionHandler attribute since it's ready to use anytime you create a new ASP.NET MVC 3 project. You can still configure Web.config to use a custom error page and handling exceptions in global Application_Error method as usual but when an exception occurs the URL is not displayed as nice as the new MVC 3's way.
you can create custom exception in MVC if you want to customize a way of exception handling.
you can find useful post here .
http://www.professionals-helpdesk.com/2012/07/creating-custom-exception-filter-in-mvc.html
Related
I have an MVC 4 app, using a custom HandleErrorAttribute to handle only custom exceptions. I would like to intercept the default 404 and other non-500 error pages and replace them with something more attractive. To that end, I added the following to my Web.config:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error/Index" />
...
</ system.web>
I have an Error controller with an Index method and corresponding view, but still I get the default 404 error page. I have also tried setting my defaultRedirect to a static html file to no avail. I have tried adding error handling specific to 404's inside <customErrors>, and I even tried modifying the routes programattically, all with no results. What am I missing? Why is ASP ignoring my default error handling?
Note: I noticed earlier that I cannot test my CustomHandleErrorAttribute locally, even with <customErrors mode="On". It does work when I hit it on my server from my dev box though... not sure if that is related. This guy had the same problem.
This should work :
1. Web.Config
<customErrors mode="On"
defaultRedirect="~/Views/Shared/Error.cshtml">
<error statusCode="403"
redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
<error statusCode="404"
redirect="~/Views/Shared/FileNotFound.cshtml" />
</customErrors>
2. Registered HandleErrorAttribute as a global action filter in the FilterConfig class as follows
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomHandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
If that dont work then, Try to make yourself transfer the response by checking status codes like the Following in the Global.asax: at least it must work.
void Application_EndRequest(object sender, EventArgs e)
{
if (Response.StatusCode == 401)
{
Response.ClearContent();
Server.Transfer("~/Views/Shared/UnauthorizedAccess.cshtml");
}
}
I am going little off topic. I thought this is bit important to explain.
If you pay attention to the above highlighted part. I have specified the order of the Action Filter. This basically describes the order of execution of Action Filter. This is a situation when you have multiple Action Filters implemented over Controller/Action Method
This picture just indicates that let's say you have two Action Filters. OnActionExecution will start to execute on Priority and OnActionExecuted will start from bottom to Top. That means in case of OnActionExecuted Action Filter having highest order will execute first and in case of OnActionExecuting Action Filter having lowest order will execute first. Example below.
public class Filter1 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will start here - 1
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 5
base.OnActionExecuted(filterContext);
}
}
public class Filter2 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will move here - 2
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 4
base.OnActionExecuted(filterContext);
}
}
[HandleError]
public class HomeController : Controller
{
[Filter1(Order = 1)]
[Filter2(Order = 2)]
public ActionResult Index()
{
//Execution will move here - 3
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
}
You may already aware that there are different types of filters within MVC framework. They are listed below.
Authorization filters
Action filters
Response/Result filters
Exception filters
Within each filter, you can specify the Order property. This basically describes the order of execution of the Action Filters.
Back to the original Query
This works for me. This is very easy and no need to consider any change in Web.Config or Register the Action Filter in Global.asax file.
ok. So, First I am creating a simple Action Filter. This will handle Ajax and Non Ajax requests.
public class MyCustomErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var debugModeMsg = filterContext.HttpContext.IsDebuggingEnabled
? filterContext.Exception.Message +
"\n" +
filterContext.Exception.StackTrace
: "Your error message";
//This is the case when you need to handle Ajax requests
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = debugModeMsg
}
};
}
//This is the case when you handle Non Ajax request
else
{
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Error";
routeData.DataTokens["area"] = "app";
routeData.Values["exception"] = debugModeMsg;
IController errorsController = new ErrorController();
var exception = HttpContext.Current.Server.GetLastError();
var httpException = exception as HttpException;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (System.Web.HttpContext.Current.Response.StatusCode)
{
case 404:
routeData.Values["action"] = "Http404";
break;
}
}
var rc = new RequestContext
(
new HttpContextWrapper(HttpContext.Current),
routeData
);
errorsController.Execute(rc);
}
base.OnException(filterContext);
}
}
Now you can implement this Action Filter on Controller as well as on the Action only.Example:
Hope this should help you.
I want to share my knowledge after investigating this problem. Any comments that help improve my statements are welcomed.
In ASP.NET MVC, there are three layers that handle HTTP requests in the following order (response is transferred in reverse order):
IIS (HTTP Layer)
ASP.NET (Server Layer)
Controller (MVC Layer)
All of these layers have error handling, but each layer does it differently. I'll start with IIS.
IIS Layer
The simplest example of how IIS handles an error is to request a non existing .html file from your server, using the browser. The address should look like:
http://localhost:50123/this_does_not_exist.html
Notice the title of the browser tab, for example: IIS 10.0 Detailed Error - 404.0 - Not Found.
ASP.NET Layer
When IIS receives a HTTP request, if the URL ends with .aspx, it forwards it to ASP.NET since it is registered to handle this extension. The simplest example of how ASP.NET handles an error is to request a non existing .aspx file from your server, using the browser. The address should look like:
http://localhost:50123/this_does_not_exist.aspx
Notice the Version Information displayed at the bottom of the page, indicating the version of ASP.NET.
The customErrors tag was originally created for ASP.NET. It has effect only when the response is created by ASP.NET internal code. This means that it does not affect responses created from application code. In addition, if the response returned by ASP.NET has no content and has an error status code (4xx or 5xx), then IIS will replace the response according to the status code. I'll provide some examples.
If the Page_Load method contains Response.StatusCode = 404, then the content is displayed normally. If additional code Response.SuppressContent = true is added, then IIS intervenes and handles 404 error in the same way as when requesting "this_does_not_exist.html". An ASP.NET response with no content and status code 2xx is not affected.
When ASP.NET is unable to complete the request using application code, it will handle it using internal code. See the following examples.
If an URL cannot be resolved, ASP.NET generates a response by itself. By default, it creates a 404 response with HTML body containing details about the problem. The customErrors can be used to create a 302 (Redirect) response instead. However, accessing a valid URL that returns 404 response from application code does not trigger the redirect specified by customErrors.
The same happens when ASP.NET catches an exception from application code. By default, it creates a 500 response with HTML body containing details about the source code that caused the exception. Again, the customErrors can be used to generate a 302 (Redirect) response instead. However, creating a 500 response from application code does not trigger the redirect specified by customErrors.
The defaultRedirect and error tags are pretty straight-forth to understand considering what I just said. The error tag is used to specify a redirect for a specific status code. If there is no corresponding error tag, then the defaultRedirect will be used. The redirect URL can point to anything that the server can handle, including controller action.
MVC Layer
With ASP.NET MVC things get more complicated. Firstly, there may be two "Web.config" files, one in the root and one in the Views folder. I want to note that the default "Web.config" from Views does two things of interest to this thread:
It disables handling URLs to .cshtml files (webpages:Enabled set to false)
It prevents direct access to any content inside the Views folder (BlockViewHandler)
In the case of ASP.NET MVC, the HandleErrorAttribute may be added to GlobalFilters, which also takes into account the value of mode attribute of the customErrors tag from the root "Web.config". More specifically, when the setting is On, it enables error handling at MVC Layer for uncaught exceptions in controller/action code. Rather than forwarding them to ASP.NET, it renders Views/Shared/Error.cshtml by default. This can be changed by setting the View property of HandleErrorAttribute.
Error handling at MVC Layer starts after the controller/action is resolved, based on the Request URL. For example, a request that doesn't fulfill the action's parameters is handled at MVC Layer. However, if a POST request has no matching controller/action that can handle POST, then the error is handled at ASP.NET Layer.
I have used ASP.NET MVC 5 for testing. There seems to be no difference between IIS and IIS Express regarding error handling.
Answer
The only reason I could think of why customErrors is not considered is because they are created with HttpStatusCodeResponse from application code. In this case, the response is not altered by ASP.NET or IIS. At this point configuring an alternative page is pointless. Here is an example code that reproduces this behavior:
public ActionResult Unhandled404Error()
{
return new HttpStatusCodeResult(HttpStatusCode.NotFound);
}
In such scenario, I recommend implementing an ActionFilterAttribute that will override OnResultExecuted and do something like the following:
int statusCode = filterContext.HttpContext.Response.StatusCode;
if(statusCode >= 400)
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("/Home/Index");
}
The implemented ActionFilterAttribute should be added to GlobalFilters.
Create a Controller ErrorController.
public class ErrorController : Controller
{
//
// GET: /Error/
public ActionResult Index()
{
return View();
}
}
Create the Index view for the action.
in Web.config
<customErrors mode="On">
<error statusCode="404" redirect="Error/Index"/>
</customErrors>
When you are handling errors in your code/logic
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start application.";
return View("Index2");
}
}
[HandleError] attribute - will redirected to the Error.cshtml page inside shared folder.
I am not sure this answer will help you but this a simple way... I placed error.html in / and turned mode to on for custom errors in web config and this works perfectly...
<system.web>
<customErrors defaultRedirect="~/Error.html" mode="On" />
</system.web>
this error.html is a basic html page with head and body..
To me, it works deleting the default Error.cshtml file, now it is taking the custom Error defaultRedirect page in Web.config.
I have enabled the global error handling for an application by applying the HandleError attribute within the filterConfig registration.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
I am then using the custom errors (web.config) to hopefully display a friendly error message for each server error.
<customErrors mode="On" ></customErrors>
This seemed to be working fine for most exceptions and I was getting the expected behaviour in that the custom error page View (Error.cshtml in the shared view folder) was being displayed.
However I have recently noticed that this is not the behaviour I see if the error thrown is an UnauthorizedAccessException.
I am a bit stumped with this, as looking in fiddler I see that this UnauthorizedAccessException exception returns a plain 500 internal server error as a standard exception does.
So how come the standard exception abides by my customError setup but the UnauthorizedAccessException does not?
ANd how can I get them to behave the same, as they are both essentially an error which I want to prevent the end user from seeing.
This blog post provided me with the overview of exception handling to enable me to decide how to handle the unauthorizedAccessException, which essentially means handling them within the Application_OnStart.
http://prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc
For my purposes there doesn't seem much point in handling the errors with the HandleErrorAttribute and in the global Application_OnStart so for my purposes I decided it was best to handle everything in the Application_OnSTart,
If you just want to force 'unhandled' exceptions like UnauthorizedAccessException to go through the normal custom-error page then you can override the controller's OnException method similar to the following:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if (!filterContext.ExceptionHandled && filterContext.RequestContext.HttpContext.IsCustomErrorEnabled)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
filterContext.Result = View("Error",
new HandleErrorInfo(filterContext.Exception, filterContext.GetCurrentControllerName(), filterContext.GetCurrentActionName()));
}
}
The article that you referenced is an excellent resource for a more thorough explanation of error-handling techniques, though, and should be considered as well.
I have read articles on exception handling in ASP.NET MVC. I want to make sure I am doing right by presenting it briefly. Could anyone please comment.
Catch the exceptions in controller actions, if necessary.
[HttpPost]
public ActionResult Insert()
{
try
{
}
catch
{
//ModelState.Error -> display error msg to the user.
}
}
Override the "OnException" method of controller in basecontroller and "log" the exceptions raised in step 1 and other MVC exceptions
Logged the global exceptions in application_onerror.
I would definitely recommend ELMaH instead of writing this code yourself, and also over Log4Net for your MVC apps. I personally avoid any exception handling, unless I have a specific functional response to it. In this way, I don't "eat" any of the errors that an application-wide tool such as ELMaH will handle gracefully for me.
ELMaH also has nice built-in web reporting, and there are third-party tools specifically for ELMaH that can give you statistics, e.g. the most frequent errors.
You might start with a custom error redirect...
<customErrors defaultRedirect="~/site/error" mode="RemoteOnly">
<error statusCode="404" redirect="~/site/notfound" />
</customErrors>
...to a controller that is aware you are using ELMaH...
public virtual ActionResult Error() {
System.Collections.IList errorList = new System.Collections.ArrayList();
ErrorLog.GetDefault(System.Web.HttpContext.Current).GetErrors(0, 1, errorList);
ErrorLogEntry entry = null;
if (errorList.Count > 0) {
entry = errorList[0] as Elmah.ErrorLogEntry;
}
return View(entry);
}
...backed by a view that helps the visitor get the specific error ID to you:
#model Elmah.ErrorLogEntry
#if (Context.User.Identity.IsAuthenticated) {
<p>Since you are signed in, we've noted your contact information,
and may follow up regarding this to help improve our product.</p>
} else {
<p>Since you aren't signed in, we won't contact you regarding this.</p>
}
<p>Error ID: #Model.Id</p>
I also notice this is an HttpPost in this example. If you are doing AJAX, then you'll want to handle errors for those in a unique way. Pick a standard response you can send to browsers that all of your AJAX code handles gracefully. Perhaps by displaying the ELMaH error ID in a javascript alert (as a simple example).
I also handle a few special types of AJAX errors via Global.asax:
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302 &&
Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
HandleErrorAttribute is a nice feature, but it is well-known that there is extra work to use it in conjunction with ELMaH. How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?
If you want handle exceptions in your Actions you can override "OnException" in your Controller like so:
protected override void OnException(ExceptionContext filterContext)
{
logging or user notification code here
}
You can put it in your BaseController class to prevent duplication
try and catch are for expected exceptions ie your user has entered a file name and it might not exist so you want to catch the FileNotFoundException.
For unexpected exceptions use either the Error event in the MvcApplication object e.g.
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
this.Error += MvcApplication_Error;
// Other code
}
private void MvcApplication_Error(object sender, EventArgs e)
{
Exception exception = this.Server.GetLastError();
// Do logging here.
}
}
or as Dima suggested you have controller level execption handling using
protected override void OnException(ExceptionContext filterContext)
{
// Do logging here.
}
Keep the trys and catches on code where you want to catch something expected and can handle.
"Generic" error handling just obfuscates the underlying problem, which you will have to dig for later.
I want to handle uncaught exceptions in my ASP.NET MVC 3 application, so that I may communicate the error to the user via the application's error view. How do I intercept uncaught exceptions? I'd like to be able to do this globally, not for each controller (although I wouldn't mind knowing how to do this as well).
You can set up a global error filter in Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
The above sets up a default error handler which directs all exceptions to the standard error View. The error view is typed to a System.Web.Mvc.HandleErrorInfo model object which exposes the exception details.
You also need to turn on custom errors in the web.config to see this on your local machine.
<customErrors mode="On"/>
You can also define multiple filters for specific error types:
filters.Add(new HandleErrorAttribute
{
ExceptionType = typeof(SqlException),
View = "DatabaseError",
Order = 1
});
/* ...other error type handlers here */
filters.Add(new HandleErrorAttribute()); // default handler
Note that HandleErrorAttribute will only handle errors that happen inside of the MVC pipeline (i.e. 500 errors).
you can use HandleErrorAttribute filters,
[ErrorHandler(ExceptionType = typeof(Exception), View = "UnhandledError", Order = 1)]
public abstract class BaseController : Controller
{
}
basically you can have this on top of a base controller and define the UnhandledError.cshtml in the Shared views folder.
And if you want to log the unhandled errors before you show the error message then you can extend the HandleErrorAttribute class and put the logic to do the logging inside the OnException method.
public class MyErrorHandlerAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext exceptionContext)
{
Logger.Error(exceptionContext.Exception.Message,exceptionContext.Exception);
base.OnException(exceptionContext);
}
}
For completeness sake, there is also the Application_Error handler in Global.asax.
Global Error Handling
Add in web.config
<customErrors mode="On"/>
Error will be displayed on Error.cshtml which is resides in shared
folder
Change in Error.cshtml
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Error"; }
<h2>
<p>Sorry, an error occurred while processing your request.</p>
<p>Controller Name: #Model.ControllerName</p>
<p>Action Name : #Model.ActionName</p>
<p>Message: #Model.Exception.Message</p> </h2>
in order to make this work I followed the following blog post and then make the following addition to both Web.config files (the root one and the one in the Views folder) inside the <system.web> node:
...
<system.web>
<customErrors mode="On"/>
...
Hope it helps...
All I need would be just the error message in plain text. But ASP.NET is doing some HTML report output from every error.
I have a jquery ajax call and when an error is thrown I'm getting all that crap over to the client side.
I've created a filter attribute but didn't helped.
public class ClientErrorHandler : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
var responce = filterContext.RequestContext.HttpContext.Response;
responce.Write(filterContext.Exception.Message);
responce.ContentType = MediaTypeNames.Text.Plain;
filterContext.ExceptionHandled = true;
}
}
EDIT
I'm seeing this
and I'd like to see just what is in here filterContext.Exception.Message
It looks to me like the reason why you cannot correctly handle the exception is because it happens outside of the MVC pipeline. If you look at the stack trace in the code you posted there is no reference to System.Web.Mvc code (the firing of exception filters when an exception occurs is called from ControllerActionInvoker.InvokeAction).
The stack trace indicates that the exception happens late in the ASP.NET pipeline (OnEndRequest) and that it's coming through the Autofac component.
To capture this error you would have to subscribe to the HttpApplication's Error event. See the following article on creating a global error handler: http://msdn.microsoft.com/en-us/library/994a1482.aspx . In this event you can handle the error and redirect to a custom error page.
you need to return a ContentResult
ContentResult result = new ContentResult();
result.Content = filterContext.Exception.Message;
result.ContentType = MediaTypeNames.Text.Plain;
filterContext.Result = result;
filterContext.ExceptionHandled = true;
Since you're using JQuery and WCF (by the details of your error), you might want to take a look at this article on how to handle service faults elegantly between jQuery and WCF - you might have to rework your service if you are able to do so.