I am new to ASP.NET MVC , Kendo UI (razor) , Jquery. I have an application that throws 3 kinds of errors
Unhanded exceptions (400, 403,500 503 etc) - throw generic exceptions
Expected exceptions/ errors (custom exceptions) - e.g. Trying to create a contact that already exist in the system - The system needs to throw "Contact_duplicated_exception" and show this to the user as "Contact was previously created. This action could not proceed".
Model state errors (UI). Errors that I add to modelstate to showup on the page using #Html.ValidationSummary(true)
What is the best standard way of handling the above throughout the application ?
I need to send these messages back to the user using Jquery Ajax [POST].
I have used the following concepts but I need to implement a standard way of dealing with the above
1. I have used ELMAH (for unhanded exceptions)
2. Application_Error(object sender, EventArgs e) in global.asax.cs
3. Custom HandleErrorArrtibute
public class HandleErrorWithAjaxFilter : HandleErrorAttribute, IExceptionFilter
Thanks!
For #1 and #3, you're looking good, IMHO. However, I think your weak-point is #2 and here is why:
If the exception is expected it should not be allowed to fall to Application_Error; because, well... it's expected, it's workflow, not an exception.
Therefore as a reaction to user input and part of workflow, it should be handled as part of #3.
So, in your shoes, I would go about in the specific instance of finding duplicates adding a validation attribute onto the potentially duplicate class like so:
[AttributeUsage(AttributeTargets.Class)]
public class UniqueContactAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
bool isValid = true;
Contact contact = value as Contact;
if(contact != null)
{
// check for your duplicate in the database and set isValid to false if you find one.
}
return isValid;
}
}
Usage for your metadata class:
[UniqueContact(ErrorMessage = "Contact was previously created. This action could not proceed.")]
public class Contact_Validation
{
}
Related
I have the following requirements for a web app, that consist of MVC 5 pages and WebApi 2 services:
All errors (404, 500, "A potentially dangerous Request.Form value was detected from the client") should produce corresponding error pages in place (no browser redirect, keep the URL the user entered in the address bar).
All error pages should be rendered using dynamic MVC views and contain a unique ID, that the user can give to phone support.
All errors must be logged, the log entries should contain the Id given to the user.
Produce different error pages for different exceptions, e.g. /error/NoSignal for NoSignalException.
Is this doable?
I would override exception hangling method - in each controller or in base (depends on your decision).
protected override void OnException(ExceptionContext filterContext)
{
filterContext.Exception.ToString(); // - make any checks here
// If method is not implemented
if (filterContext.Exception.GetType() == typeof(NotImplementedException))
{
filterContext.Result = new ViewResult { ViewName = "NotImplemented" };
filterContext.ExceptionHandled = true;
}
}
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.
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
I am trying to use ELMAH to log errors in my ASP.NET MVC application, however when I use the [HandleError] attribute on my controllers ELMAH doesn't log any errors when they occur.
As I am guessing its because ELMAH only logs unhandled errors and the [HandleError] attribute is handling the error so thus no need to log it.
How do I modify or how would I go about modifying the attribute so ELMAH can know that there was an error and log it..
Edit: Let me make sure everyone understands, I know I can modify the attribute thats not the question I'm asking... ELMAH gets bypassed when using the handleerror attribute meaning it won't see that there was an error because it was handled already by the attribute... What I am asking is there a way to make ELMAH see the error and log it even though the attribute handled it...I searched around and don't see any methods to call to force it to log the error....
You can subclass HandleErrorAttribute and override its OnException member (no need to copy) so that it logs the exception with ELMAH and only if the base implementation handles it. The minimal amount of code you need is as follows:
using System.Web.Mvc;
using Elmah;
public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);
if (!context.ExceptionHandled)
return;
var httpContext = context.HttpContext.ApplicationInstance.Context;
var signal = ErrorSignal.FromContext(httpContext);
signal.Raise(context.Exception, httpContext);
}
}
The base implementation is invoked first, giving it a chance to mark the exception as being handled. Only then is the exception signaled. The above code is simple and may cause issues if used in an environment where the HttpContext may not be available, such as testing. As a result, you will want code that is that is more defensive (at the cost of being slightly longer):
using System.Web;
using System.Web.Mvc;
using Elmah;
public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);
if (!context.ExceptionHandled // if unhandled, will be logged anyhow
|| TryRaiseErrorSignal(context) // prefer signaling, if possible
|| IsFiltered(context)) // filtered?
return;
LogException(context);
}
private static bool TryRaiseErrorSignal(ExceptionContext context)
{
var httpContext = GetHttpContextImpl(context.HttpContext);
if (httpContext == null)
return false;
var signal = ErrorSignal.FromContext(httpContext);
if (signal == null)
return false;
signal.Raise(context.Exception, httpContext);
return true;
}
private static bool IsFiltered(ExceptionContext context)
{
var config = context.HttpContext.GetSection("elmah/errorFilter")
as ErrorFilterConfiguration;
if (config == null)
return false;
var testContext = new ErrorFilterModule.AssertionHelperContext(
context.Exception,
GetHttpContextImpl(context.HttpContext));
return config.Assertion.Test(testContext);
}
private static void LogException(ExceptionContext context)
{
var httpContext = GetHttpContextImpl(context.HttpContext);
var error = new Error(context.Exception, httpContext);
ErrorLog.GetDefault(httpContext).Log(error);
}
private static HttpContext GetHttpContextImpl(HttpContextBase context)
{
return context.ApplicationInstance.Context;
}
}
This second version will try to use error signaling from ELMAH first, which involves the fully configured pipeline like logging, mailing, filtering and what have you. Failing that, it attempts to see whether the error should be filtered. If not, the error is simply logged. This implementation does not handle mail notifications. If the exception can be signaled then a mail will be sent if configured to do so.
You may also have to take care that if multiple HandleErrorAttribute instances are in effect then duplicate logging does not occur, but the above two examples should get your started.
Sorry, but I think the accepted answer is an overkill. All you need to do is this:
public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
public void OnException (ExceptionContext context)
{
// Log only handled exceptions, because all other will be caught by ELMAH anyway.
if (context.ExceptionHandled)
ErrorSignal.FromCurrentContext().Raise(context.Exception);
}
}
and then register it (order is important) in Global.asax.cs:
public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
filters.Add(new ElmahHandledErrorLoggerFilter());
filters.Add(new HandleErrorAttribute());
}
There is now an ELMAH.MVC package in NuGet that includes an improved solution by Atif and also a controller that handles the elmah interface within MVC routing (no need to use that axd anymore)
The problem with that solution (and with all the ones here) is that one way or another the elmah error handler is actually handling the error, ignoring what you might want to set up as a customError tag or through ErrorHandler or your own error handler
The best solution IMHO is to create a filter that will act at the end of all the other filters and log the events that have been handled already. The elmah module should take care of loging the other errors that are unhandled by the application. This will also allow you to use the health monitor and all the other modules that can be added to asp.net to look at error events
I wrote this looking with reflector at the ErrorHandler inside elmah.mvc
public class ElmahMVCErrorFilter : IExceptionFilter
{
private static ErrorFilterConfiguration _config;
public void OnException(ExceptionContext context)
{
if (context.ExceptionHandled) //The unhandled ones will be picked by the elmah module
{
var e = context.Exception;
var context2 = context.HttpContext.ApplicationInstance.Context;
//TODO: Add additional variables to context.HttpContext.Request.ServerVariables for both handled and unhandled exceptions
if ((context2 == null) || (!_RaiseErrorSignal(e, context2) && !_IsFiltered(e, context2)))
{
_LogException(e, context2);
}
}
}
private static bool _IsFiltered(System.Exception e, System.Web.HttpContext context)
{
if (_config == null)
{
_config = (context.GetSection("elmah/errorFilter") as ErrorFilterConfiguration) ?? new ErrorFilterConfiguration();
}
var context2 = new ErrorFilterModule.AssertionHelperContext((System.Exception)e, context);
return _config.Assertion.Test(context2);
}
private static void _LogException(System.Exception e, System.Web.HttpContext context)
{
ErrorLog.GetDefault((System.Web.HttpContext)context).Log(new Elmah.Error((System.Exception)e, (System.Web.HttpContext)context));
}
private static bool _RaiseErrorSignal(System.Exception e, System.Web.HttpContext context)
{
var signal = ErrorSignal.FromContext((System.Web.HttpContext)context);
if (signal == null)
{
return false;
}
signal.Raise((System.Exception)e, (System.Web.HttpContext)context);
return true;
}
}
Now, in your filter config you want to do something like this:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//These filters should go at the end of the pipeline, add all error handlers before
filters.Add(new ElmahMVCErrorFilter());
}
Notice that I left a comment there to remind people that if they want to add a global filter that will actually handle the exception it should go BEFORE this last filter, otherwise you run into the case where the unhandled exception will be ignored by the ElmahMVCErrorFilter because it hasn't been handled and it should be loged by the Elmah module but then the next filter marks the exception as handled and the module ignores it, resulting on the exception never making it into elmah.
Now, make sure the appsettings for elmah in your webconfig look something like this:
<add key="elmah.mvc.disableHandler" value="false" /> <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" /> <!-- This uses the default filter for elmah, set to disabled to use our own -->
<add key="elmah.mvc.requiresAuthentication" value="false" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.allowedRoles" value="*" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.route" value="errortracking" /> <!-- Base route for elmah pages -->
The important one here is "elmah.mvc.disableHandleErrorFilter", if this is false it will use the handler inside elmah.mvc that will actually handle the exception by using the default HandleErrorHandler that will ignore your customError settings
This setup allows you to set your own ErrorHandler tags in classes and views, while still loging those errors through the ElmahMVCErrorFilter, adding a customError configuration to your web.config through the elmah module, even writing your own Error Handlers. The only thing you need to do is remember to not add any filters that will actually handle the error before the elmah filter we've written. And I forgot to mention: no duplicates in elmah.
You can take the code above and go one step further by introducing a custom controller factory that injects the HandleErrorWithElmah attribute into every controller.
For more infomation check out my blog series on logging in MVC. The first article covers getting Elmah set up and running for MVC.
There is a link to downloadable code at the end of the article. Hope that helps.
http://dotnetdarren.wordpress.com/
I'm new in ASP.NET MVC. I faced the same problem, the following is my workable in my Erorr.vbhtml (it work if you only need to log the error using Elmah log)
#ModelType System.Web.Mvc.HandleErrorInfo
#Code
ViewData("Title") = "Error"
Dim item As HandleErrorInfo = CType(Model, HandleErrorInfo)
//To log error with Elmah
Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(New Elmah.Error(Model.Exception, HttpContext.Current))
End Code
<h2>
Sorry, an error occurred while processing your request.<br />
#item.ActionName<br />
#item.ControllerName<br />
#item.Exception.Message
</h2>
It is simply!
A completely alternative solution is to not use the MVC HandleErrorAttribute, and instead rely on ASP.Net error handling, which Elmah is designed to work with.
You need to remove the default global HandleErrorAttribute from App_Start\FilterConfig (or Global.asax), and then set up an error page in your Web.config:
<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />
Note, this can be an MVC routed URL, so the above would redirect to the ErrorController.Index action when an error occurs.
For me it was very important to get email logging working. After some time I discover that this need only 2 lines of code more in Atif example.
public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();
public override void OnException(ExceptionContext context)
{
error_mail_log.Init(HttpContext.Current.ApplicationInstance);
[...]
}
[...]
}
I hope this will help someone :)
This is exactly what I needed for my MVC site configuration!
I added a little modification to the OnException method to handle multiple HandleErrorAttribute instances, as suggested by Atif Aziz:
bear in mind that you may have to take care that if multiple HandleErrorAttribute instances are in effect then duplicate logging does not occur.
I simply check context.ExceptionHandled before invoking the base class, just to know if someone else handled the exception before current handler.
It works for me and I post the code in case someone else needs it and to ask if anyone knows if I overlooked anything.
Hope it is useful:
public override void OnException(ExceptionContext context)
{
bool exceptionHandledByPreviousHandler = context.ExceptionHandled;
base.OnException(context);
Exception e = context.Exception;
if (exceptionHandledByPreviousHandler
|| !context.ExceptionHandled // if unhandled, will be logged anyhow
|| RaiseErrorSignal(e) // prefer signaling, if possible
|| IsFiltered(context)) // filtered?
return;
LogException(e);
}