Saturday, April 2, 2016

NLog Custom Target With Constructor Dependency Injection by using Autofac

Recently, I have decided to switch to NLog from log4net. Personally, I do prefer to implement my own  class(  appender/target  or whatever you call it) to  do the actual logging  and  i prefer to log to database with some session variables. After switching to NLog  I've implemented my own custom target as below;



[Target("AS.Custom.NLogTarget")]
public class NLogCustomTarget : Target
{
   private readonly ISessionContext _sessionContext;
   private readonly IDbContext _dbContext;

   public NLogCustomTarget(ISessionContext sessionContext, IDbContext dbContext)
   {
      this._sessionContext = sessionContext;
      this._dbContext = dbContext;
   }
   protected override void Write(LogEventInfo logEvent)
   {
      AppLog log = new AppLog();
      log.AppDomain = AppDomain.CurrentDomain.FriendlyName;
      log.ClientIP = this._sessionContext.ClientIP;
      log.Level = logEvent.Level.Name;
      log.LoggerName = logEvent.LoggerName;
      log.MachineName = Environment.MachineName;
      log.Message = logEvent.FormattedMessage;
      log.Username = this._sessionContext.UserName;

      this._dbContext.Set().Add(log);
      this._dbContext.SaveChanges();
   }
}

Here the registration ;


IContainer _container;
ContainerBuilder builder = new ContainerBuilder();
_container = builder.Build();
builder = new ContainerBuilder();
builder.RegisterType()
       .As()
       .InstancePerRequest();
builder.RegisterType()
       .As()
       .InstancePerRequest();
builder.RegisterType()
       .AsSelf()
       .InstancePerLifetimeScope();
builder.Update(_container);
....
As you can guess ISessionContext    is an interface to current HttpContext wrapper and IDbContext is interface to my current EntityFramework DbContext class. I will not get in detail to those since it is not the subject of this post.


Unfortunately , this configuration is not enough to get constructor injection working on  custom NLog target. The reason is , target class is created by NLog factory which has no information about the type registrations you have done or any information about your container. Luckily , NLog provides a delegate to setup your own instance creating method.

With  a simple dependency resolver interface and implementation over the great Autofac   IOC, i got it working.
Here is  my implementation ;


IDependencyResolver :



/// 
/// Simple interface to create a dependency resolver
/// 
public interface IDependencyResolver
{
    object GetService(Type serviceType);
}

My Custom Dependency Resolver Class:



public class ASAutofacDependencyResolver : IDependencyResolver
{
   private readonly IContainer _container;

   public ASAutofacDependencyResolver(IContainer container)
   {
      this._container = container;
   }

   public object GetService(Type serviceType)
   {
      return this.Scope().Resolve(serviceType);
   }
   private ILifetimeScope Scope()
   {
      try
      {
         if (HttpContext.Current != null)
            return AutofacDependencyResolver.Current.RequestLifetimeScope;
         return _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
      }
      catch (Exception)
      {
         return _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
      }
   }
}

Configuration of logger:



public static class LoggingConfigurator
{
   private static readonly string NLogDefaultFileName = "NLog.config";
   private static IDependencyResolver _resolver;

   public static void SetResolver(IDependencyResolver resolver)
   {
      _resolver = resolver;
   }
   public static void ConfigureNLog()
   {
       ConfigurationItemFactory.Default.CreateInstance = (Type type) => _resolver.GetService(type);
       string path = AppDomain.CurrentDomain.BaseDirectory + NLogDefaultFileName;
       LogManager.Configuration = new XmlLoggingConfiguration(path, false);
   }
}
And we setup up the dependency resolver as below;


LoggingConfigurator.SetResolver(new ASAutofacDependencyResolver(_container));


That is it!

No comments:

Post a Comment