Inject Primitive Dependencies by Convention

Mark Seemann, author of the book Dependency Injection in .NET, has a nice article about the injection of Primitive Dependencies.

From a technical perspective I find the part where he configures Castle Windsor to use his custom conventions most interesting. When I come across such a feature that exists in another DI container but is not present in Unity I try to port that feature. And I have yet to find some piece of functionality that cannot be implemented easily with a few lines of code. Adding support for parameter conventions takes about ~250 LoC. Due to the differences in their respective architecture the Unity implementation is two-part: The core part of the convention implements the custom interface IDependencyResolverConvention the part that performs the actual work implements IDependencyResolverPolicy which comes as part of the Unity infrastructure.

These are the interfaces:

public interface IDependencyResolverConvention
{
  bool CanCreateResolver(IBuilderContext context, DependencyInfo dependency);
  IDependencyResolverPolicy CreateResolver(IBuilderContext context, DependencyInfo dependency);
}

public interface IDependencyResolverPolicy : IBuilderPolicy
{
  object Resolve(IBuilderContext context);
}

Using that newly created infrastructure (and a second convention that works like the ConnectionStringConvention described in the article) you can resolve classes that look like these:

public class TakesPrimitiveParameter
{
  public int Abc { get; set; }
  public TakesPrimitiveParameter(int abc)
  {
    this.Abc = abc;
  }
}
public class TakesConnectionStringParameter
{
  public string AbcConnectionString { get; set; }
  public TakesConnectionStringParameter(string abcConnectionString)
  {
    this.AbcConnectionString = abcConnectionString;
  }
}

The integer value abc will be read from the appSettings section of your config file. The ConnectionString abc will be read from the connectionStrings section.

public class AppSettingsConvention : IDependencyResolverConvention
{
  public bool CanCreateResolver(IBuilderContext context, DependencyInfo dependency)
  {
    return dependency.DependencyType == typeof(int);
  }
  public IDependencyResolverPolicy CreateResolver(IBuilderContext context, DependencyInfo dependency)
  {
    return new AppSettingsResolverPolicy(dependency.DependencyName, dependency.DependencyType);
  }
}

public class AppSettingsResolverPolicy : IDependencyResolverPolicy
{
  private readonly string name;
  private readonly Type targetType;
  public AppSettingsResolverPolicy(string name, Type targetType)
  {
    this.name = name;
    this.targetType = targetType;
  }
  public object Resolve(IBuilderContext context)
  {
    string setting = ConfigurationManager.AppSettings[this.name];
    return Convert.ChangeType(setting, this.targetType);
  }
}

Setting up the registrations is as easy as this:

var container = new UnityContainer().WithDefaultConventionsForLiteralParameters();
var foo = container.Resolve<TakesPrimitiveParameter>();

Or if you prefer to have more fine granular control over the used conventions you can add them one by one:

var container = new UnityContainer()
                 .WithConventionsForLiteralParameters(
                   new ConnectionStringConvention(),
                   new AppSettingsConvention());
var foo = container.Resolve<TakesPrimitiveParameter>();

Neat and as Mark puts it: If you stick to your conventions it will just keep working when your code base grows.

Get the source code for the convention based injection here (project TecX.Unity folder Literals and the test suite that shows how to use it in TecX.Unity.Test).

Advertisements