Injecting an ILogger property with Autofac
autofac, IoC, programming October 25th, 2009I’ve always liked the Castle.Core ILogger and ILoggerFactory abstraction. I don’t know why but I’ve never been a fan of log4net so I like to keep direct dependencies to it at arm’s length.
Looking into Autofac recently, I was trying to find a way of getting a logger property assigned as a component is created from the container in the same way as Windsor’s logging facility. The Autofac container has a Module/IModule extensibility which works well for the tasks a facility would have done.
Here’s the type of code I’m used to seeing from logging.
using Castle.Core.Logging;
namespace LoggingStudy {
public interface IFoo {
void Bar();
}
public class Foo : IFoo {
public Foo() {
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Bar() {
Logger.Info("Bar called");
}
}
}
And here’s how you’d add a logging module to an Autofac builder.
using Autofac.Builder;
namespace LoggingStudy {
class Program {
static void Main(string[] args) {
var builder = new ContainerBuilder();
builder.RegisterModule(new LoggingModule());
builder.Register<Foo>().As<IFoo>();
var container = builder.Build();
using (var scope = container.CreateInnerContainer()) {
var foo = scope.Resolve<IFoo>();
foo.Bar();
}
}
}
}
And here’s an implementation of a LoggingModule that will use Castle’s ILoggerFactory to assign the ILogger typed public properties. Some of the code is based on a few threads in the discussion group.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Builder;
using Castle.Core.Logging;
namespace LoggingStudy {
public class LoggingModule : Autofac.Builder.Module {
protected override void Load(ContainerBuilder moduleBuilder) {
// by default, use Castle's TraceSource based logger factory
moduleBuilder.Register<TraceLoggerFactory>().As<ILoggerFactory>().ContainerScoped();
// call CreateLogger in response to the request for an ILogger implementation
moduleBuilder.Register((ctx, ps) => CreateLogger(ctx, ps)).As<ILogger>().FactoryScoped();
}
protected override void AttachToComponentRegistration(IContainer container, IComponentRegistration registration) {
var implementationType = registration.Descriptor.BestKnownImplementationType;
// build an array of actions on this type to assign loggers to member properties
var injectors = BuildLoggerInjectors(implementationType).ToArray();
// if there are no logger properties, there's no reason to hook the activated event
if (!injectors.Any())
return;
// otherwise, whan an instance of this component is activated, inject the loggers on the instance
registration.Activated += (s, e) => {
foreach (var injector in injectors)
injector(e.Context, e.Instance);
};
}
private static IEnumerable<Action<IContext, object>> BuildLoggerInjectors(Type componentType) {
// Look for settable properties of type "ILogger"
var loggerProperties = componentType
.GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)
.Select(p => new {
PropertyInfo = p,
p.PropertyType,
IndexParameters = p.GetIndexParameters(),
Accessors = p.GetAccessors(false)
})
.Where(x => x.PropertyType == typeof(ILogger)) // must be a logger
.Where(x => x.IndexParameters.Count() == 0) // must not be an indexer
.Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set
// Return an array of actions that resolve a logger and assign the property
foreach (var entry in loggerProperties) {
var propertyInfo = entry.PropertyInfo;
yield return (ctx, instance) => {
var propertyValue = ctx.Resolve<ILogger>(new TypedParameter(typeof(Type), componentType));
propertyInfo.SetValue(instance, propertyValue, null);
};
}
}
private static ILogger CreateLogger(IContext context, IEnumerable<Parameter> parameters) {
// return an ILogger in response to Resolve<ILogger>(componentTypeParameter)
var loggerFactory = context.Resolve<ILoggerFactory>();
var containingType = parameters.TypedAs<Type>();
return loggerFactory.Create(containingType);
}
}
}
The TraceLoggerFactory is added as a default ILoggerFactory. It’s my favorite one and because it’s based on System.Diagnostics TraceSource it doesn’t require additional assemblies.
If you expect to Resolve
October 26th, 2009 at 3:40 pm
How are you liking Autofac? Did it beat the competition in your recent DI face-off? ;)
November 14th, 2009 at 10:08 pm
Autofac is working quite well. There were some very specific needs it satisfied, not the least of which is an efficient per-request child container. Kind of a safety net in the face of potentially mismatched resolve/release calls.
November 15th, 2009 at 10:40 am
Yes, that is a sweet feature and having grabbed the Orchard source last night I can see why you were checking it out. Sounds like Nicholas may use Orchard as the demo app for Autofac 2 too. That would be pretty cool all round.
November 15th, 2009 at 3:00 pm
Ah yes, I did see him mention something on Twitter…
http://twitter.com/nblumhardt/statuses/5670189074
@loudej I’m looking for a project to use as the first app on #Autofac 2… just had an idea… after looking at the #Orchard source ;)
On the other hand it could mean he was inspired enough to decide to start another open source CMS. :)
November 30th, 2009 at 5:06 pm
Saw the mention of Orchard CMS in the comments. I am interested in the CMS business on .NET so checked out the source. Any plans to use Spark with the project at some time?, or are you guys happy with using web forms engine?
February 25th, 2010 at 2:42 pm
Very nice. I was flopping about trying to do something similar (actually without the Castle integration), and yours was the first complete post that I saw that I understood.
Well, understood except for one thing.
In LoggingModule, line 48 is
.Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set
Why have this clause? Shouldn’t the BindingFlags.SetProperty have limited us to properties with sets anyhow?
Thanks.