How would I delete / clear / wipe / overwrite a log file being written through EL6 logging. I am using a logwriter instance to write to a log file that needs to be overwritten every cycle as my program runs in a continuous loop. I will be writing values then overwriting as new values come through.
There may be other/better ways to attack this, but I was able to clear the log file by temporarily repointing the static LogWriter to a temp file, clearing the log with simple File I/O, and then reconnecting the original LogWriter.
I wrote up a simple C# Console App to demonstrate. There are some hard-coded references to the log file configuration in App.config that could probably be cleaned up by using the ConfigurationSourceBuilder, but hopefully this can get you started.
Programs.cs:
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using System;
using System.Diagnostics;
using System.IO;
namespace LogFileClearance
{
public static class Marker
{
public static LogWriter customLogWriter { get; set; }
}
class Program
{
private static object _syncEventId = new object();
private static object locker = new object();
private static int _eventId = 0;
private const string INFO_CATEGORY = "All Events";
static void Main( string [] args )
{
InitializeLogger();
Console.WriteLine( "Enter some input, <Enter> or q to quit, c to clear the log file" );
string input = Console.ReadLine().ToUpper();
while ( ! string.IsNullOrEmpty(input) && input != "Q" )
{
Console.WriteLine( "You entered {0}", input );
if ( input == "C" )
{
ClearLog();
}
else
{
WriteLog( input );
}
Console.WriteLine( "Enter some input, <Enter> or q to quit, c to clear the log file" );
input = Console.ReadLine().ToUpper();
}
}
private static int GetNextEventId()
{
lock ( _syncEventId )
{
return _eventId++;
}
}
public static void InitializeLogger()
{
try
{
lock ( locker )
{
if ( Marker.customLogWriter == null )
{
var writer = new LogWriterFactory().Create();
Logger.SetLogWriter( writer, false );
}
else
{
Logger.SetLogWriter( Marker.customLogWriter, false );
}
}
}
catch ( Exception ex )
{
Debug.WriteLine( "An error occurred in InitializeLogger: " + ex.Message );
}
}
static internal void WriteLog( string message )
{
LogEntry logEntry = new LogEntry();
logEntry.EventId = GetNextEventId();
logEntry.Severity = TraceEventType.Information;
logEntry.Priority = 2;
logEntry.Message = message;
logEntry.Categories.Add( INFO_CATEGORY );
// Always attach the version and username to the log message.
// The writeLog stored procedure will parse these fields.
Logger.Write( logEntry );
}
static internal void ClearLog()
{
string originalFileName = string.Format(#"C:\Logs\LogFileClearance.log");
string tempFileName = originalFileName.Replace( ".log", "(TEMP).log" );
var textFormatter = new FormatterBuilder()
.TextFormatterNamed( "Custom Timestamped Text Formatter" )
.UsingTemplate("{timestamp(local:MM/dd/yy hh:mm:ss.fff tt)} tid={win32ThreadId}: {message}");
#region Set the Logging LogWriter to use the temp file
var builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.LogToCategoryNamed( INFO_CATEGORY ).WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile( "Flat File Trace Listener" )
.ToFile(tempFileName);
using ( DictionaryConfigurationSource configSource = new DictionaryConfigurationSource() )
{
builder.UpdateConfigurationWithReplace(configSource);
Marker.customLogWriter = new LogWriterFactory(configSource).Create();
}
InitializeLogger();
#endregion
#region Clear the original log file
if ( File.Exists(originalFileName) )
{
File.WriteAllText(originalFileName, string.Empty);
}
#endregion
#region Re-connect the original file to the log writer
builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions.DoNotRevertImpersonation()
.LogToCategoryNamed( INFO_CATEGORY ).WithOptions.SetAsDefaultCategory()
.SendTo.RollingFile("Rolling Flat File Trace Listener")
.RollAfterSize(1000)
.FormatWith(textFormatter).WithHeader("").WithFooter("")
.ToFile(originalFileName);
using ( DictionaryConfigurationSource configSource = new DictionaryConfigurationSource() )
{
builder.UpdateConfigurationWithReplace( configSource );
Marker.customLogWriter = new LogWriterFactory( configSource ).Create();
}
InitializeLogger();
#endregion
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<loggingConfiguration name="Logging Application Block" tracingEnabled="true" defaultCategory="" logWarningsWhenNoCategoriesMatch="true">
<listeners>
<add fileName="C:\Logs\LogFileClearance.log" footer="" formatter="Timestamped Text Formatter"
header="" rollFileExistsBehavior="Increment"
rollInterval="None" rollSizeKB="1000" timeStampPattern="yyyy-MM-dd"
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" traceOutputOptions="None" filter="All" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Log File Listener" />
</listeners>
<formatters>
<add template="{timestamp(local:MM/dd/yy hh:mm:ss tt)} tid={win32ThreadId}: {message}" type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="Timestamped Text Formatter" />
</formatters>
<categorySources>
<add switchValue="Information" name="All Events">
<listeners>
<add name="Log File Listener" />
</listeners>
</add>
<add switchValue="Error" name="Logging Errors & Warnings">
<listeners>
<add name="Log File Listener" />
</listeners>
</add>
</categorySources>
<specialSources>
<allEvents switchValue="Information" name="All Events" />
<notProcessed switchValue="All" name="Unprocessed Category" />
<errors switchValue="All" name="Logging Errors & Warnings" />
</specialSources>
</loggingConfiguration>
</configuration>
I have the following console program in which I attempt to apply the SOLID principles, dependency injection with Unity and Prism 6 modularity:
Main program:
UnityContainer container = new UnityContainer();
container.RegisterInstance<IServiceLocator>(new UnityServiceLocator(container));
container.RegisterType<IModuleInitializer, ModuleInitializer>();
TextLogger logger = new TextLogger();
container.RegisterInstance<ILoggerFacade>(logger);
ConfigurationModuleCatalog catalog = new ConfigurationModuleCatalog();
container.RegisterInstance<IModuleCatalog>(catalog);
container.RegisterType<IModuleManager, ModuleManager>();
IModuleManager manager = container.Resolve<IModuleManager>();
manager.Run();
App config file:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
</configSections>
<modules>
<module assemblyFile="CalcCommandParsingLib.dll" moduleType="CalcCommandParsingLib.CalculatorCommandParsingModule, CalcCommandParsingLib, Version=1.0.0.0, Culture = neutral, PublicKeyToken=null" moduleName="Parsing" />
<module assemblyFile="CalculatorLibrary.dll" moduleType="CalculatorLibrary.CalculatorModule, CalculatorLibrary, Version=1.0.0.0, Culture = neutral, PublicKeyToken=null" moduleName="Calculator" />
<module assemblyFile="InputOutputLibrary.dll" moduleType="InputOutputLibrary.InputOutputModule, InputOutputLibrary, Version=1.0.0.0, Culture = neutral, PublicKeyToken=null" moduleName="InputOutput" />
<module assemblyFile="MainModuleLibrary.dll" moduleType="MainModuleLibrary.MainModule, MainModuleLibrary, Version=1.0.0.0, Culture = neutral, PublicKeyToken=null" moduleName="Main" >
<dependencies>
<dependency moduleName="Calculator" />
<dependency moduleName="Parsing" />
<dependency moduleName="InputOutput" />
</dependencies>
</module>
</modules>
</configuration>
And one of the four modules which causes the error:
public class MainModule : IModule
{
IServiceLocator serviceLocator;
public MainModule(IServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void Initialize()
{
ICalculatorReplLoop loop = serviceLocator.GetInstance<ICalculatorReplLoop>();
loop.Run();
}
}
The error occurs on this line:
ICalculatorReplLoop loop = serviceLocator.GetInstance<ICalculatorReplLoop>();
and it is:
An exception of type
'Microsoft.Practices.ServiceLocation.ActivationException' occurred in
Microsoft.Practices.ServiceLocation.dll but was not handled in user
code
Additional information: Activation error occurred while trying to get
instance of type ICalculatorReplLoop, key ""
Can somebody help me with this please?
I think that there are at least 2 things to change:
You should also register ICalculatorReplLoop
container.RegisterType<ICalculatorReplLoop, CalculatorReplLoop>();
It would make more sense for me to inject directly the needed interface, then to resolve it via serviceLocator so:
public MainModule(ICalculatorReplLoop calc)
{
this.calc= calc;
}
if (_cacheSessionFactory == null)
{
Configuration config = null;
_cacheSessionFactory = ReadOrBuildConfiguration().ExposeConfiguration(
cfg =>
{
SetSessionContext(cfg);
cfg.SetListener(ListenerType.Flush, new PostFlushFixEventListener());
ConfigureValidator(cfg);
if (EnversConfiguration != null)
{
cfg.SetEnversProperty(ConfigurationKey.StoreDataAtDelete, true);
cfg.IntegrateWithEnvers(EnversConfiguration);
}
}).ExposeConfiguration(cfg => { config = cfg; }).BuildSessionFactory();
using (_cacheSessionFactory.OpenSession())
{
BuildSchema(config);
}
}
UPDATED stack trace
FluentNHibernate.Cfg.FluentConfigurationException was unhandled by user code
HResult=-2146233088
Message=An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
* Database was not configured through Database method.
* No mappings were configured through the Mappings method.
Source=FluentNHibernate
StackTrace:
at FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory()
at NhibDataAccess.NhibernateSetup.CreateSessionFactory() in d:\Jorell\PROJECTS\NhibDataAccess\NhibernateSetup.cs:line 105
at NhibDataAccess.NhibernateSetup.get_SessionFactory() in d:\Jorell\PROJECTS\NhibDataAccess\NhibernateSetup.cs:line 67
at NhibDataAccess.UnitOfWork.LongRunningUnitOfWork.get_SessionFactory() in d:\Jorell\PROJECTS\NhibDataAccess\UnitOfWork\LongRunningUnitOfWork.cs:line 63
at Web.Global.Application_EndRequest(Object sender, EventArgs e) in d:\Jorell\PROJECTS\Web\Global.asax.cs:line 54
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
InnerException: System.NullReferenceException
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=NHibernate.Validator
StackTrace:
at NHibernate.Validator.Engine.SystemTypeExtensions.ShouldNeedValidation(Type clazz)
at NHibernate.Validator.Engine.ValidatorEngine.GetClassValidator(Type entityType)
at NHibernate.Validator.Engine.ValidatorEngine.AddValidator(Type entityType, IValidatableSubElementsInspector inspector)
at NHibernate.Validator.Event.ValidatePreInsertEventListener.Initialize(Configuration cfg)
at NHibernate.Event.EventListeners.InitializeListeners(Configuration cfg, Object[] list)
at NHibernate.Event.EventListeners.InitializeListeners(Configuration cfg)
at NHibernate.Cfg.Configuration.GetInitializedEventListeners()
at NHibernate.Cfg.Configuration.BuildSessionFactory()
at FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory()
InnerException:
when i try to add any Audit<> in EnversConfiguration this exception is thrown. i have a similar application and this doesn't happen. any clue or information as to why this problem occurs?
the problem in my code was already addressed before i even knew it.
please visit the site for more information
https://nhibernate.jira.com/browse/NHV-117
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 the try 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"];
}
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="test#test.com" to="test#test.com"
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.
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.
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();
}
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").
Why don't you create model that contains your required Error Information and bind data to model when you need to? It will also allow you to create/return view from it
Global error catching with special information can you make with customer exceptions who contains the needed informations (id, tablesname etc.).
In HandleErrorAttribute you "only" have httpContext/ExceptionContext and othe static informations.
I have the following unity construct method:
public static IUnityContainer CreateContainer()
{
UnityContainer container = new UnityContainer();
container.LoadConfiguration();
.......
}
Then an interface, and an implementation class:
namespace MyCompany.Web.Areas.MyApp.Common
{
public interface ISession
{
}
}
namespace MyCompany.Web.Areas.MyApp.Common
{
public class SessionHandler : ISession
{
}
}
Instead of doing this:
Container.RegisterType<ISession, SessionHandler>(new ContainerControlledLifetimeManager()) ;
I want to use container.LoadConfiguration() to load the above RegisterType config from the web.config. BUT it doesn't seem to work
web.config:
<unity>
<containers>
<container>
<types>
<type type="MyCompany.Web.Areas.MyApp.Common.ISession" mapTo="MyCompany.Web.Areas.MyApp.Common.SessionHandler">
<lifetime type="singleton"/>
</type>
</types>
</container>
</containers>
</unity>
Exception:
The type name or alias MyProject.Web.Areas.MyApp.Common.ISession could not be resolved. Please check your configuration file and verify this type name.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The type name or alias MyProject.Web.Areas.MyApp.Common.ISession could not be resolved. Please check your configuration file and verify this type name.
Source Error:
Line 33: {
Line 34: UnityContainer container = new UnityContainer();
Line 35: container.LoadConfiguration();
Line 36:
Line 37: //container.RegisterType(new ContainerControlledLifetimeManager());
Stack Trace:
[InvalidOperationException: The type name or alias MyProject.Web.Areas.MyApp.Common.ISession could not be resolved. Please check your configuration file and verify this type name.]
Microsoft.Practices.Unity.Configuration.ConfigurationHelpers.TypeResolverImpl.ResolveType(String typeNameOrAlias, Boolean throwIfResolveFails) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\ConfigurationHelpers\TypeResolverImpl.cs:110
Microsoft.Practices.Unity.Configuration.ConfigurationHelpers.TypeResolver.ResolveType(String typeNameOrAlias) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\ConfigurationHelpers\TypeResolver.cs:47
Microsoft.Practices.Unity.Configuration.RegisterElement.GetRegisteringType() in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\RegisterElement.cs:121
Microsoft.Practices.Unity.Configuration.RegisterElement.ConfigureContainer(IUnityContainer container) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\RegisterElement.cs:88
Microsoft.Practices.Unity.Configuration.ContainerConfiguringElement.ConfigureContainerInternal(IUnityContainer container) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\ContainerConfiguringElement.cs:43
Microsoft.Practices.Unity.Configuration.<>c__DisplayClass1.b__0(ContainerConfiguringElement element) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\ContainerElement.cs:114
Microsoft.Practices.ObjectBuilder2.EnumerableExtensions.ForEach(IEnumerable1 sequence, Action1 action) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Utility\EnumerableExtensions.cs:36
Microsoft.Practices.Unity.Configuration.ContainerElement.ConfigureContainer(IUnityContainer container) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\ContainerElement.cs:110
Microsoft.Practices.Unity.Configuration.UnityConfigurationSection.Configure(IUnityContainer container, String configuredContainerName) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\UnityConfigurationSection.cs:151
Microsoft.Practices.Unity.Configuration.UnityContainerExtensions.LoadConfiguration(IUnityContainer container, UnityConfigurationSection section, String containerName) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\UnityContainerExtensions.cs:37
Microsoft.Practices.Unity.Configuration.UnityContainerExtensions.LoadConfiguration(IUnityContainer container) in e:\Builds\Unity\UnityTemp\Compile\Unity\Unity.Configuration\Src\UnityContainerExtensions.cs:64
MyProject.Web.ApplicationContainer.CreateContainer() in C:\Workspace\MCDev\MyApp\MyApp\MyProject.Web\ApplicationContainer.cs:35
MyProject.Web.ApplicationContainer.Initialize(HttpContext context) in C:\Workspace\MCDev\MyApp\MyApp\MyProject.Web\ApplicationContainer.cs:53
MyProject.Web.MvcApplication.Application_Start() in C:\Workspace\MCDev\MyApp\MyApp\MyProject.Web\Global.asax.cs:41
I don't think it knows what dll to look in:
<unity>
<containers>
<container>
<types>
<type type="MyCompany.Web.Areas.MyApp.Common.ISession, MyCompany" mapTo="MyCompany.Web.Areas.MyApp.Common.SessionHandler, MyCompany">
<lifetime type="singleton"/>
</type>
</types>
</container>
</containers>
</unity>