bling.github.io

This blog has relocated to bling.github.io.

Monday, September 7, 2009

Member injection module for Autofac

Inevitably as a project gets more complicated, you will need to start using more features of your IoC container. Autofac has built-in support for property injection by hooking into the OnActivating or OnActivated events, which basically set all public properties (or only those which are unset). However, I didn't really like this because once you start using properties, it's not as clear cut as constructors that you are using injection. It becomes hard to manage later on when you have many properties which some should be injected and others should be manually set in code. Autofac's inject all or only-null approach doesn't fit the bill when a class gets a little more complicated. I set up to fix this by writing a custom module, and it turned out to be very very simple. With attributes, we can mimic functionality that's used in Ninject. Here's the module code:
public class MemberInjectionModule : IModule
{
  public void Configure(IContainer container)
  {
    container.ComponentRegistered += (oo, ee) =>
      ee.ComponentRegistration.Activated += (o, e) =>
    {
      const BindingFlags flags = BindingFlags.Instance |
                                 BindingFlags.NonPublic |
                                 BindingFlags.Public;
      var type = e.Instance.GetType();
      foreach (var field in type.GetFields(flags))
      {
        if (field.GetCustomAttributes(typeof(InjectedAttribute), true).Length > 0)
          field.SetValue(e.Instance, e.Context.Resolve(field.FieldType));
      }
      foreach (var prop in type.GetProperties(flags))
      {
        if (prop.GetCustomAttributes(typeof(InjectedAttribute), true).Length > 0)
        {
          if (prop.GetIndexParameters().Length > 0)
            throw new InvalidOperationException("Indexed properties cannot be injected.");

          prop.SetValue(e.Instance, e.Context.Resolve(prop.PropertyType), null);
        }
      }
    };
  }
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class InjectedAttribute : Attribute { }
And that's it! Pretty basic reflection here stuff here, but as you can see...iterate through all fields and properties (including privates), and try to resolve them. Now, you can just RegisterModule(new MemberInjectionModule()), and inside your services you simply annotate your properties/fields with the [Injected] attribute, like so:
public class SomeService : ISomeService
{
  [Injected]
  protected ISomeOtherService SomeOtherService { get; set; }
}
And now, it's a very clear cut way of explicitly defining which properties (and fields) should be injected. Also, it's also a simple string property away defined in the attribute if you want to specify a named service, which I'll leave the the reader to implement :)

No comments: