Generate generic factories with Autofac

One of the features I like the most in Autofac is the ability to register a factory in the container. A factory in this context is “some method that knows how to build and return an instance of some type”.

Factories are useful when you need fine-grained control over when dependencies should be resolved. Also, if you have dependencies that requires data input (e.g. a constructor parameter) and you need some way of passing that data into the dependency, a factory is your friend.

With Autofac it gets even better. Using C# I can declare a delegate that represents the signature of such a factory, register the delegate in my container and voila! Autofac generates the implementation based on the signature.

So I could declare a delegate like this:

public delegate ICustomerService CustomerServiceFactory();

and have it injected in, say, one of my controller classes, like this:

public class CustomerController
{
    private CustomerServiceFactory _customerServiceFactory;
    public CustomerController(CustomerServiceFactory customerServiceFactory)
    {
        _customerServiceFactory = customerServiceFactory;
    }
}

My controller now have the ability to decide when to create the service instance and still not need to know how.

To make the sample complete (thus far) I’ll show how the Autofac container setup looks like at this point:

var builder = new ContainerBuilder();
builder.Register<CustomerService>();
builder.RegisterGeneratedFactory<CustomerServiceFactory>();
builder.Register<CustomerController>();

This works great. And was fairly easy too don’t think? Now imagine that these classes and delegates was a small part of a real system, with hundreds of controllers and services. Having to declare a factory delegate for each service becomes a pain, let aside registering them in the container.

Wouldn’t it be nice if we could declare only one factory that could build all service types? Turns out you can. Think about the following generic delegate:

public delegate T ServiceFactory<T>() where T:class;

Our updated controller dependency now looks like this:

public class CustomerController
{
    private ServiceFactory<CustomerService> _customerServiceFactory;
    public CustomerController
        (ServiceFactory<CustomerService> customerServiceFactory)
    {
        _customerServiceFactory = customerServiceFactory;
    }
}

Registering such a delegate in the container becomes a bit tricky, since we don’t know T at registration time. Even more so, we cannot generate the factory delegate at registration time! The solution is to register the open generic type coupled with a method that will generate the factory when the dependency is asked for. The Autofac setup should now look like this:

var builder = new ContainerBuilder();
builder.Register<CustomerService>().As();
builder
  .RegisterGeneratedFactoryFromOpenType(typeof(ServiceFactory<>));
builder.Register<CustomerController>();

The RegisterGeneratedFactoryFromOpenType extension method does two things:

  1. Register the open generic type using builder.RegisterGeneric
  2. Attach factory generation code to the OnPreparing event.

The OnPreparing event will be fired when Autofac tries to resolve a closed version of the delegate, in our case the ServiceFactory delegate. Since the event argument contains this type we have everything necessary for generating the factory implementation. And here’s the implementation of RegisterGeneratedFactoryFromOpenType:

public static IGenericRegistrar
    RegisterGeneratedFactoryFromOpenType
    (this ContainerBuilder builder, Type openFactoryType)
{
    return builder.RegisterGeneric(openFactoryType)
        .OnPreparing(Prepare);
}

static void Prepare(object sender, PreparingEventArgs args)
{
    var factoryType = args.Component
        .Descriptor.BestKnownImplementationType;
    var serviceType = factoryType
        .GetMethod("Invoke").ReturnType; 

    var service = new TypedService(serviceType);
    var factory = new FactoryGenerator(factoryType, service); 

    args.Instance = factory
        .GenerateFactory(args.Context, args.Parameters);
}

Note: this code is based on the latest release of Autofac 1.

Share
  • Trackback are closed
  • Comments (3)
    • blu
    • December 24th, 2009

    Good post. I think
    public delegate T ServiceFactory() where T:class;

    is supposed to be
    public delegate T ServiceFactory() where T:class;

    Have you looked at the performance impact of this approach. Does Autofac do something similar under the hood?

    • blu
    • December 24th, 2009

    @blu

    Oh I see, < > aren’t being encoded in your post.

    • Peter
    • December 24th, 2009

    Hey, @blu , thanks for pointing this out. Man, I fixed it for some of the other sample parts but failed to spot the generic delegate!

    Well, performance wise this approach is equal to registering non-generic delegates. The difference is with non-generics, the delegate is generated at register time. With generics, the delegate is generated at resolve time.

    Either way, the delegate will only have to be generated once. Next time the delegate is asked for, it will already be available in the container.

Comment are closed.