MVC Error Handle with custom error Messages
Solution 1
Your requirement best fit with Elmah
. Very good plugin for logging errors.
ELMAH
stands for Error Logging Modules And Handlers
ELMAH
provides such a high degree of plugability that even Installation of ELMAH does not require compilation of your application.
ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.
Reference from the blog of SCOTT HANSELMAN
Just need to copy binary of ELMAH to bin folder of your application and edit web.config file. That's it!
you need to add following to your web.config and make some other changes described in the following links.
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
</sectionGroup>
For example to set up mail account.
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
</sectionGroup>
</configSections>
<elmah>
<errorMail from="[email protected]" to="[email protected]"
subject="Application Exception" async="false"
smtpPort="25" smtpServer="***"
userName="***" password="***">
</errorMail>
</elmah>
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="CustomError.aspx">
<error statusCode="403" redirect="NotAuthorized.aspx" />
<!--<error statusCode="404" redirect="FileNotFound.htm" />-->
</customErrors>
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>
</system.web>
</configuration>
Here is some good reference link (that contains detailed reference to installation of ELMAH to your project) for your reference.
https://msdn.microsoft.com/en-us/library/aa479332.aspx
https://code.google.com/p/elmah/wiki/MVC
Update
Add to the detail custom information (for example the Id of the record I'm trying to read)
You can build your own custom exception that derives from Exception.
public class MyException : Exception
{
public MyException(string message, Exception ex) : base(ex.Message, ex)
{
}
}
and then using it like
public virtual ActionResult Index()
{
try
{
return View();
}
catch (Exception e)
{
throw new MyException("detailed exception", e);
}
}
in this way main exception would be wrapped inside the myexception and you can add your detailed custom exception message.
Return to the view custom messages to the user
You just need to add
<system.web>
<customErrors mode="On">
</customErrors>
<sytem.web>
and add Error.cshtml
inside the ~/View/Shared
folder
Then whenever exception is encountered it will find for Error.cshtml inside view/shared folder and render the content. so you can render there your custom message.
Solution 2
Use Elmah as others have also recommended. I am, and haven't looked back!
It meets all your requirements:
- Catches all errors, e.g. 400s, 500s...
- Logs to a file, and any other data store you can think of, e.g. database, memory, Azure, more file formats(XML, CSV), RSS feed...
- Emails errors: Enable and config mail settings in Web.config - very simple. You can even send emails asynchronously!
- Add custom code - in your case add extra details to the error
- Use your own custom error pages - custom error node (for 400s, 500s) in web.config and your own error controller
Further on the custom code (2nd last point above), AFAIK you have two options:
1. Create a custom error log implementation.
This isn't that difficult. It's what I did!
Override the default error log data store. For example, taking the SQL Server data store:
In Web.config
<elmah>
<errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="myCn" applicationName="myAppName" />
</elmah>
Next, create a class "MySQLServerErrorLog" and derive from Elmah.ErrorLog
All that is then required is to override the Log() method.
public override string Log(Error error)
{
// You have access to all the error details - joy!
= error.HostName,
= error.Type,
= error.Message,
= error.StatusCode
= error.User,
= error.Source,
// Call the base implementation
}
In Web.config, replace the default (above) entry with your implementation:
<elmah>
<errorLog type="myProjectName.MySQLServerErrorLog, myProjectName" />
</elmah>
2. You can programmatically log errors
Using the ErrorSignal class, you may logs errors without having to raise unhandled exceptions.
Syntax: ErrorSignal.FromCurrentContext().Raise(new NotSupportedException());
Example: A custom exception
var customException = new Exception("My error", new NotSupportedException());
ErrorSignal.FromCurrentContext().Raise(customException);
This gives you the option of using your custom logic to programmatically log whatever you require.
I've written functionality for my Elmah instance to logs errors to Azure Cloud Storage Table and Blob (error stack trace details).
FWIW before I used Elmah, I had written my own exception handling mechanism for MVC which used HandleErrorAttribute and Application_Error (in Global.asax). It worked but was too unwieldy IMO.
Solution 3
If it was me, I'd create my own exception handling Attribute which adds required behaviour to the base implementation of HandleErrorAttribute.
I've had quite good results in the past with having attributes "pointed at" various parts of the request that's of interest (am thinking the bit where you say that you want to log specific details) - so you can use these identifiers to pull the request to pieces using reflection:
CustomHandleErrorAttribute(["id", "name", "model.lastUpdatedDate"])
I've used this approach to secure controller actions (making sure that a customer is requesting things that they're allowed to request) - e.g. a parent is requesting info on their children, and not someone else's children.
Or, you could have a configuration set up whereby you'd "chain" handlers together - so lots of little handlers, all doing very specific bits, all working on the same request and request pointers (as above):
ChainedErrorHandling("emailAndLogFile", ["id", "name", "model.lastUpdatedDate"])
Where "emailAndLogFile" creates a chain of error handlers that inherit from FilterAttribute, the last of which, in the chain, is the standard MVC HandleErrorAttribute.
But by far, the simplest approach would be the former of these 2.
HTH
EDITED TO ADD: Example of inheriting custom error handling:
public class CustomErrorAttribute : HandleErrorAttribute
{
public CustomErrorAttribute(string[] requestPropertiesToLog)
{
this.requestPropertiesToLog = requestPropertiesToLog;
}
public string[] requestPropertiesToLog { get; set; }
public override void OnException(ExceptionContext filterContext)
{
var requestDetails = this.GetPropertiesFromRequest(filterContext);
// do custom logging / handling
LogExceptionToEmail(requestDetails, filterContext);
LogExceptionToFile(requestDetails, filterContext);
LogExceptionToElseWhere(requestDetails, filterContext);// you get the idea
// even better - you could use DI (as you're in MVC at this point) to resolve the custom logging and log from there.
//var logger = DependencyResolver.Current.GetService<IMyCustomErrorLoggingHandler>();
// logger.HandleException(requestDetails, filterContext);
// then let the base error handling do it's thang.
base.OnException(filterContext);
}
private IEnumerable<KeyValuePair<string, string>> GetPropertiesFromRequest(ExceptionContext filterContext)
{
// in requestContext is the queryString, form, user, route data - cherry pick bits out using the this.requestPropertiesToLog and some simple mechanism you like
var requestContext = filterContext.RequestContext;
var qs = requestContext.HttpContext.Request.QueryString;
var form = requestContext.HttpContext.Request.Form;
var user = requestContext.HttpContext.User;
var routeDataOfActionThatThrew = requestContext.RouteData;
yield break;// just break as I'm not implementing it.
}
private void LogExceptionToEmail(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
{
// send emails here
}
private void LogExceptionToFile(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
{
// log to files
}
private void LogExceptionToElseWhere(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
{
// send cash to me via paypal everytime you get an exception ;)
}
}
And On the controller action you'd add something like:
[CustomErrorAttribute(new[] { "formProperty1", "formProperty2" })]
public ActionResult Index(){
return View();
}
Solution 4
Firstly, you can define a filter attribute, and you can register it on startup in an MVC application in global.asax
, so you can catch any kind of errors that occur while actions are invoking.
Note: Dependency Resolving is changeable. I'm using Castle Windsor for this story. You can resolve dependencies your own IOC container. For example, ILogger dependency. I used for this property injection while action invoking. Windsor Action Invoker
For Example Filter:
public class ExceptionHandling : FilterAttribute, IExceptionFilter
{
public ILogger Logger { get; set; }
public void OnException(ExceptionContext filterContext)
{
Logger.Log("On Exception !", LogType.Debug, filterContext.Exception);
if (filterContext.Exception is UnauthorizedAccessException)
{
filterContext.Result = UnauthorizedAccessExceptionResult(filterContext);
}
else if (filterContext.Exception is BusinessException)
{
filterContext.Result = BusinessExceptionResult(filterContext);
}
else
{
// Unhandled Exception
Logger.Log("Unhandled Exception ", LogType.Error, filterContext.Exception);
filterContext.Result = UnhandledExceptionResult(filterContext);
}
}
}
This way you can catch everything.
So:
private static ActionResult UnauthorizedAccessExceptionResult(ExceptionContext filterContext)
{
// Send email, fire event, add error messages
// for example handle error messages
// You can seperate the behaviour by: if (filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.Controller.TempData.Add(MessageType.Danger.ToString(), filterContext.Exception.Message);
// So you can show messages using with TempData["Key"] on your action or views
var lRoutes = new RouteValueDictionary(
new
{
action = filterContext.RouteData.Values["action"],
controller = filterContext.RouteData.Values["controller"]
});
return new RedirectToRouteResult(lRoutes);
}
In Global.asax:
protected void Application_Start()
{
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
FilterConfig:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionHandling());
}
BusinessException:
public class BusinessException : Exception, ISerializable
{
public BusinessException(string message)
: base(message)
{
// Add implemenation (if required)
}
}
So you can access the exception message OnException
at ExceptionHandling
class with filterContext.Exception.Message
You should use BusinessException
on the action after any violated control logic this way: throw new BusinessException("Message")
.
Related videos on Youtube
Patrick
Devoting most of my time developing web applications, I realized over the years that the common web developer is too focus on improvements in design, development and implementation of internet applications, often forgetting the most important, the user . With the user in mind, the project was born http://www.izigo.pt . The project aims to provide a search engine to search for services and vehicles in the automotive sector, in a simple, fast and geo referenced ( Research nearest garage service for example), always trying to provide updated information using the latest information technologies available in the market . izigo.pt intends to organize and deliver high quality content in the automotive sector where the automobile market through workshops , booths , business directory , services and dealers will be referenced and accessible . Our partners will have increased visibility in a complex area that requires great trust between suppliers and customers and in which the information is often not easily accessible . Please check my LinkedIn profile for aditional information: http://pt.linkedin.com/in/patricksardinha/
Updated on June 04, 2022Comments
-
Patrick almost 2 years
I'm building a new Web Application using MVC5 and I need the followings:
- Catch errors
- Log the details in a file
- Send them by email
- Add to the detail custom information (for example the
Id
of the record I'm trying to read) - Return to the view custom messages to the user
I have found a lot of information regarding the
HandleErrorAttribute
but none of them allow to add specific details to the error, also I have found information saying that thetry catch
aproach is too heavy for the server.For now, I have:
Controller:
public partial class HomeController : Controller { private static Logger logger = LogManager.GetCurrentClassLogger(); public virtual ActionResult Index() { try { return View(); } catch (Exception e) { logger.Error("Error in Index: " + e); return MVC.Error.Index("Error in Home Controller"); } } }
I have found this Extended
HandleErrorAttribute
that seems complete but don't do everything I need:private bool IsAjax(ExceptionContext filterContext) { return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; } public override void OnException(ExceptionContext filterContext) { if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled) { return; } // if the request is AJAX return JSON else view. if (IsAjax(filterContext)) { //Because its a exception raised after ajax invocation //Lets return Json filterContext.Result = new JsonResult(){Data=filterContext.Exception.Message, JsonRequestBehavior=JsonRequestBehavior.AllowGet}; filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); } else { //Normal Exception //So let it handle by its default ways. base.OnException(filterContext); } // Write error logging code here if you wish. //if want to get different of the request //var currentController = (string)filterContext.RouteData.Values["controller"]; //var currentActionName = (string)filterContext.RouteData.Values["action"]; }
-
Daniel J.G. about 9 yearsHave you looked into Elmah?
-
Patrick about 9 yearsHi thanks Andrew, but I'm having some dificulties understanding the concept. Any example you could give to undertsnd it better?
-
Patrick about 9 yearsHi thanks Thomas, any reference to implement Elmah with custom code to add adicional information in MVC?
-
Patrick about 9 yearsHi thanks Jenish, any example to add custom messages?
-
OpcodePete about 9 yearsBest to use NuGet Elmah.MVC (and dependency Elmah.CoreLibrary) packages. It will copy the binaries and Web.config entries.
-
Andrew Hewitt about 9 yearsHi Patrick. Sorry - on re-read I can see why it's not clear. So - we're saying that the standard HandleErrorAttribute does some, but not all of the required actions - what I propose (and have done in similar circumstances with great success) is creating a new attribute class that inherits from this one, and uses the base implementation once it does it's extra stuff (i.e. logging to file / email whilst gaining details from the request on a case by case basis). I'll add an extra answer with a pseudo example, maybe that will help.
-
Andrew Hewitt about 9 yearsHi Patrick - have edited my answer to give you a start with the attribute. HTH!
-
Andrew Hewitt about 9 yearsHey - np Patrick - good luck! if you get stuck, give me a shout :)
-
Patrick about 9 yearsHi, Thanks! Where do you pass custom information from the Action to the Filter, for example, a ClientId?
-
Oğuzhan Soykan about 9 yearsNo, you can define a Exception class like as
BusinessException
class.(Note: BusinessException class should be derived from System.Exception) So, when you typenew BusinessException("Parameter is invalid");
on the action method, the businessexception transmit toOnException
event, so you can handle this way. -
Patrick about 9 yearsAnd how do you implement it in a common Controller/Action when an error occurs and you want to pass the ClientId that generated the error for example?
-
Oğuzhan Soykan about 9 yearsIf I understood correctly, you show ClientID with Error Messages or you want to Log ClientId. You can pass json string to message with ClientId, and deserialize it OnException event ?
-
Patrick about 9 yearsI want to log ClientId with other informations (other status variables), the place where it hapened (Controller/Action) and to be able to respond properly to the user regarding the call (Post/Get/Json). This way, I can receive the error and check what hapened and send a friendly message to the user.