Tuesday, February 26, 2013

Advanced constructor injection with Autofac

Autofac is a great dependency injection framework where lots of features get on with ease of use. However configuring constructor parameters sometimes is not trivial.

Lets imagine following requirement: we need to inject NLog logger into our component and set logger's name according to our component type. Simple but arguable solution is to inject implementation of service locator pattern, namely Nlog's LogFactory class, but it introduces repeatable code and clogs our components with extra responsibility of creating loggers for themselves.

Good news is that it is possible to group functionality for creating loggers into a single place and inject right logger straight into constructor. Consider following component class:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class TypedLoggerAttribute : Attribute
{
}
[TypedLogger]
public class Worker
{
private readonly Logger logger_;
public Worker(Logger logger)
{
logger_ = logger;
}
public void Start()
{
logger_.Info("Starting work");
}
}
view raw container.cs hosted with ❤ by GitHub
It is marked by custom attribute of TypedLoggerAttribute type which means that logger should be injected into constructor. Configuration of injected logger is implemented as an Autofac module:
public class TypedLoggersInjector : Module
{
private readonly LogFactory logFactory_;
public TypedLoggersInjector(LogFactory logFactory)
{
logFactory_ = logFactory;
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
var reflectionActivator = registration.Activator as ReflectionActivator;
if (reflectionActivator != null && reflectionActivator.LimitType.GetCustomAttributes(typeof(TypedLoggerAttribute), false).Length > 0)
{
var targetType = reflectionActivator.LimitType;
var namedParameters = reflectionActivator.ConstructorFinder.FindConstructors(targetType)
.SelectMany(c => c.GetParameters().Where(p => p.ParameterType == typeof(Logger)))
.Select(p => new NamedParameter(p.Name, logFactory_.GetLogger(targetType.FullName))).ToList();
if (namedParameters.Count > 0)
{
registration.Preparing += (sender, args) =>
{
args.Parameters = args.Parameters.Concat(namedParameters);
};
}
}
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
view raw autof_module.cs hosted with ❤ by GitHub
This module will prepare preconfigured loggers for all constructors of target components. Use following code to see how it works:
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<Worker>();
builder.RegisterModule(new TypedLoggersInjector(new LogFactory()));
return builder.Build();
}
public static void TestInjection()
{
var container = ConfigureContainer();
var worker = container.Resolve<Worker>();
worker.Start();
}
view raw autof_usage.cs hosted with ❤ by GitHub