My favorites | Sign in
Logo
                
Search
for
Updated Aug 05, 2009 by nicholas.blumhardt
Labels: Featured
ComponentCreation  
The container provides several built in options to create components

Introduction

Components can be created using lambda expressions, by reflection, or by providing a ready-made instance.

ContainerBuilder provides overloads of Register() to set this up.

Components are wired to services using the As() methods on ContainerBuilder - where no services are explicitly listed, a default service is provided based on the creation method.

Reflection

Autofac is capable of inspecting a type, choosing an appropriate constructor, and creating an instance through reflection. The Register<T>() and Register(Type) methods set a component up this way:

builder.Register<A>();  // Create A using reflection
builder.Register(typeof(B)); // Non-generic version

The process of choosing a constructor and instantiating dependencies is discussed further in Autowiring.

Default Service

The default service for reflection-created components is the implementation type (A above will provide the service A.)

Expressions

Reflection is a pretty good default choice for component creation. Things get messy though when component creation logic goes beyond a simple constructor call.

Autofac can accept a delegate or lambda expression to be used as a component creator:

builder.Register(c => new A(c.Resolve<B>()));

The parameter c provided to the expression is the container in which the component is being created. It is important to use this rather than a closure to access the container so that deterministic disposal and nested containers can be supported correctly.

Additional dependencies can be satisfied using this parameter - in the example, A requires a constructor parameter of type B.

Below are examples of some requirements met poorly by reflective component creation.

Complex Parameters

Constructor parameters can't always be declared with simple constant values. Rather than puzzling over how to construct a value of a certain type using an XML configuration syntax, use C#:

builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

(Of course, session expiry is probably something you'd want to specify in a configuration file - but you get the gist ;))

Property Injection

Use property initialisers for this. The IContext.ResolveOptional() method can be handy:

builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });

Alternatively, IContext provides InjectProperties():

builder.Register(c => c.InjectProperties(new A()));

Selection of an Implementer based on a Parameter Value

One of the great benefits of isolating component creation is that the concrete type can be varied. This is often done at runtime, not just configuration time:

builder.Register<CreditCard>((c, p) => {
    var accountId = p.Named<string>("accountId");
    if (accountId.StartsWith("9"))
      return new GoldCard(accountId);
    else
      return new StandardCard(accountId);
  })
  .FactoryScoped();

In this example, CreditCard is implemented by two classes, GoldCard and StandardCard - which class is instantiated depends on the account id provided at runtime.

Parameters are provided to the creation function through an optional second parameter named p in this example, of type IActivationParameters.

Using this registration would look like:

var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));

A cleaner, type-safe syntax can be achieved if a delegate to create CreditCards is declared and a delegate factory is used.

Default Service

The default service provided by an expression-created component is the inferred return type of the expression.

Provided Instances

When integrating Autofac into an existing application there will sometimes be cases where a singleton instance already exists and needs to be used by components in the container. Rather than tying those components directly to the singleton, it can be registered with the container as an instance:

builder.Register(MySingleton.Instance).ExternallyOwned();

This ensures that the static singleton can eventually be eliminated and replaced with a container-managed one.

Default Service

The default service exposed by an instance is the concrete type of the instance.

Open Generic Types

Autofac supports open generic types. Use the RegisterGeneric() builder method:

builder.RegisterGeneric(typeof(NHibernateRepository<>))
  .As(typeof(IRepository<>))
  .ContainerScoped();

When a matching service type is requested from the container, Autofac will map this to an equivalent closed version of the implementation type:

// Autofac will return an NHibernateRepository<Task>
var tasks = container.Resolve<IRepository<Task>>();

Registration of a specialised service type (e.g. IRepository<Person>) will override the open generic version.

Default Registrations

If more than one component exposes the same service, Autofac will use the last registered component as the default provider of that service.

To override this behaviour, use the DefaultOnly() modifier:

builder.Register<X1>().As<IX>();
builder.Register<X2>().As<IX>().DefaultOnly();

In this scenario, X1 will be the default for IX (without the DefaultOnly() modifier, X2 would be the default because it is registered last.)


Comment by szabelin, Jan 22, 2009

builder.Register<CreditCard>((c, p) => {

var accountId = p.Get<string>("accountId");

The code above doesn't work: p does not have method Get anymore..

Comment by szabelin, Jan 22, 2009

Should use 'Named' instead of 'Get' in the example above.

Comment by carl.hoerberg, Jul 15, 2009

Note that there's a vast difference between builder.Register(new MyDataContext?()); and builder.Register(c => new MyDataContext?());. In the first case the object is instantiated when the line is executed, but in the second case the object is instantiated when the datatype is resolved (requested by some other object).

Comment by nicholas.blumhardt, Aug 05, 2009

@szabelin Thanks, fixed now!


Sign in to add a comment
Hosted by Google Code