|
ComponentCreation
The container provides several built in options to create components
IntroductionComponents 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. ReflectionAutofac 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.) ExpressionsReflection 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 ParametersConstructor 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 InjectionUse 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 ValueOne 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.Get<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 NamedValue("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 InstancesWhen 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 TypesAutofac 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 RegistrationsIf 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.) |
Sign in to add a comment
builder.Register<CreditCard>((c, p) => {
The code above doesn't work: p does not have method Get anymore..
Should use 'Named' instead of 'Get' in the example above.