I am looking for a simple solution to do Exception Logging combined with Error Handling in my ASP.Net MVC 1.0 application.
I've read lots of articles, including Questions posted here on StackOverflow, which all provide varying solutions for different situations. I am still unable to come up with a solution that suits my needs.
Here are my requirements:
To be able to use the [HandleError] attribute (or something equivalent) on my Controller, to handle all exceptions that could be thrown from any of the Actions or Views. This should handle all exceptions that were not handled specifically on any of the Actions (as described in point 2). I would like to be able to specify which View a user must be redirected to in error cases, for all actions in the Controller.
I want to be able to specify the [HandleError] attribute (or something equivalent) at the top of specific Actions to catch specific exceptions and redirect users to a View appropriate to the exception. All other exceptions must still be handled by the [HandleError] attribute on the Controller.
In both cases above, I want the exceptions to be logged using log4net (or any other logging library).
How do I go about achieving the above? I've read about making all my Controllers inherit from a base controller which overrides the OnException method, and wherein I do my logging. However this will mess around with redirecting users to the appropriate Views, or make it messy.
I've read about writing my own Filter Action which implements IExceptionFilter to handle this, but this will conflict with the [HandleError] attribute.
So far, my thoughts are that the best solution is to write my own attribute that inherits from HandleErrorAttribute. That way I get all the functionality of [HandleError], and can add my own log4net logging. The solution is as follows:
public class HandleErrorsAttribute: HandleErrorAttribute {
private log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception != null)
{
log.Error("Error in Controller", filterContext.Exception);
}
base.OnException(filterContext);
}
}
Will the above code work for my requirements? If not, what solution does fulfill my requirements?
Another way to handle controller level exceptions is by overriding the OnException() method in the controller class. This method handles all your unhandled errors with error code 500. It allows you to log an exception and redirect to the specific view. It does not require to enable the <customErrors> config in web.
HandleError Attribute This filter handles all the exceptions raised by controller actions, filters, and views. To use this feature, first of all turn on the customErrors section in web. config.
Exception class. An exception is thrown from an area of code where a problem has occurred. The exception is passed up the call stack to a place where the application provides code to handle the exception. If the application does not handle the exception, the browser is forced to display the error details.
I'm still a bit confused with all the different solutions out there, and how attributes can interfere with each other, but I went with this solution:
public class LogErrorsAttribute: FilterAttribute, IExceptionFilter { #region IExceptionFilter Members void IExceptionFilter.OnException(ExceptionContext filterContext) { if (filterContext != null && filterContext.Exception != null) { string controller = filterContext.RouteData.Values["controller"].ToString(); string action = filterContext.RouteData.Values["action"].ToString(); string loggerName = string.Format("{0}Controller.{1}", controller, action); log4net.LogManager.GetLogger(loggerName).Error(string.Empty, filterContext.Exception); } } #endregion }
I still use the [HandleError] attribute as explained in the original question, and I just decorate each controller with a [LogErrors] attribute.
This works for me, as it keeps the error logging in one place and doesn't cause duplicate exceptions to be logged multiple times (which will happen if I extend [HandleError] and use the attribute in multiple places).
I don't think it will be possible to combine both the Exception Logging and Error Handling into one atrribute or class, without it becoming very tedious and complex, or affecting the use of [HandleError]
But this works for me since I decorate each controller only once, with the [LogErrors] attribute, and decorate Controllers and Actions with [HandleError] exactly how I want to, without them interfering with each other.
Update:
Here is an example of How I use it:
[LogErrors(Order = 0)] [HandleError(Order = 99)] public class ContactController : Controller { public ActionResult Index() { return View(Views.Index); } public ActionResult Directions() { return View(Views.Directions); } public ActionResult ContactForm() { FormContactMessage formContactMessage = new FormContactMessage(); return View(Views.ContactForm,formContactMessage); } [HandleError(ExceptionType = typeof(SmtpException), View = "MessageFailed", Order = 1)] [AcceptVerbs(HttpVerbs.Post)] public ActionResult ContactForm(FormContactMessage formContactMessage) { if (ModelState.IsValid) { if (formContactMessage.IsValid) { SmtpClient client = new SmtpClient(); MailAddress recipientAddress = new MailAddress(Properties.Settings.Default.ContactFormRecipientEmailAddress); MailAddress senderAddress = new MailAddress(Properties.Settings.Default.ContactFormSenderEmailAddress); MailMessage mailMessage = formContactMessage.ToMailMessage(recipientAddress, senderAddress); client.Send(mailMessage); return View("MessageSent"); } else { ModelState.AddRuleViolations(formContactMessage.GetRuleViolations()); } } return View(Views.ContactForm, formContactMessage); } private static class Views { public static string Index { get { return "Index"; } } public static string Directions { get { return "Directions"; } } public static string ContactForm { get { return "ContactForm"; } } } }
In the above code, SmtpExceptions in the ContactForm
action overload are handled in a very specific way - the user is presented with a ViewPage specific to failed sent messages, in this case it is called "MessageFailed" . All other exceptions are handled by the default behaviour of [HandleError]. Also note that logging of errors occurs first, followed by handling of errors. This is indicated by the following:
[LogErrors(Order = 0)] [HandleError(Order = 99)]
Update:
There is an alternative solution to this, with a very good explanantion. I recommend reading through it to get a better understanding of the issues involved.
ASP.NET MVC HandleError Attribute, Custom Error Pages and Logging Exceptions (Thanks to Scott Shepherd below, who provided the link in an answer below).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With