|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionAutofac is an open-source dependency injection (DI) or inversion of control (IoC) container developed on Google Code. Autofac differs from many related technologies in that it sticks as close to bare-metal C# programming as possible. It is designed around the premise that it is wasteful to work in such a powerful language as C# but to lose that power to purely reflection-based APIs like those prevalent in the other .NET containers. The result is that Autofac supports a wide array of application designs with very little additional infrastructure or integration code, and with a lower learning curve. That is not to say that it is simplistic; it has most of the features offered by other DI containers, and many subtle features that help with the configuration of your application, managing components' life-cycles, and keeping the dependencies under control. This article uses an example to demonstrate the most basic techniques for creating and wiring together application components, then goes on to discuss the most important features of Autofac. If you're already using a DI container and want to get a feel for how Autofac is different, you may wish to skip ahead briefly and check out the code in the Autofac in Applications section. The Example ApplicationThe application is a console program that checks a list of memos, each with a due date, and notifies the user of the ones that are overdue.
A lot of simplifications have been made in the example code. In the interests of brevity, XML comments, argument checking, and exception handling are elided from all of the code samples. Checking for Overdue MemosAt the core of the application is the // A MemoChecker ...
class MemoChecker
{
IQueryable<Memo> _memos;
IMemoDueNotifier _notifier;
// Construct a memo checker with the store of memos and the notifier
// that will be used to display overdue memos.
public MemoChecker(IQueryable<Memo> memos, IMemoDueNotifier notifier)
{
_memos = memos;
_notifier = notifier;
}
// Check for overdue memos and alert the notifier of any that are found.
public void CheckNow()
{
var overdueMemos = _memos.Where(memo => memo.DueAt < DateTime.Now);
foreach (var memo in overdueMemos)
_notifier.MemoIsDue(memo);
}
}
The following aspects of this class come as a direct result of using a dependency-injected style:
These things make the class more easily testable, configurable and maintainable. Notifying the UserThe // A memo notifier that prints messages to a text stream.
class PrintingNotifier : IMemoDueNotifier
{
TextWriter _writer;
// Construct the notifier with the stream onto which it will
// print notifications.
public PrintingNotifier(TextWriter writer)
{
_writer = writer;
}
// Print the details of an overdue memo onto the text stream.
public void MemoIsDue(Memo memo)
{
_writer.WriteLine("Memo '{0}' is due!", memo.Title);
}
}
Like Data StorageThe IQueryable<Memo> memos = new List<Memo>() {
new Memo { Title = "Release Autofac 1.1", DueAt = DateTime.Now },
new Memo { Title = "Write CodeProject Article", DueAt = DateTime.Now },
new Memo { Title = "Release Autofac 1.2", DueAt = new DateTime(2008, 07, 01) }
}.AsQueryable();
The Wiring Up ComponentsThe ultimate structure of the application looks like:
Most of this article is concerned with how a Dependency Injection by HandWith only a few components, configuring a var checker = new MemoChecker(memos, new PrintingNotifier(Console.Out));
checker.CheckNow();
A real application, with several layers and all sorts of components, shouldn't be configured this way. This kind of direct object creation works well locally on a few classes, but doesn't scale up to large numbers of components. For one thing, the code that did this at startup would get increasingly complex over time, and would potentially need to be reorganized every time the dependencies of a class change. More importantly, it is hard to switch the implementations of services; e.g., an Autofac and other dependency injection containers circumvent these issues by 'flattening out' the deeply-nested structure of the object graph at configuration-time... Dependency Injection with a ContainerWhen using Autofac, accessing the container.Resolve<MemoChecker>().CheckNow();
The Component RegistrationsA dependency injection container is a collection of registrations that map services to components. A service, in this context, is a way of identifying a particular functional capability – it could be a textual name, but is more often an interface type. A registration captures the dynamic behaviour of the component within the system. The most noticeable aspect of this is the way in which instances of the component are created. Autofac can accept registrations that create components using expressions, provided instances, or with Reflection based on Registering a Component Created with an ExpressionThe following sets up a registration for the builder.Register(c => new MemoChecker(c.Resolve<IQueryable<Memo>>(),
c.Resolve<IMemoDueNotifier>()));
Each The lambda expression Each The registration doesn't say anything about which components will implement The expression being provided to builder.Register(c => new MemoChecker(...)).As<MemoChecker>();
Either way, a request for Autofac won't execute the expression when the component is registered. Instead, it will wait until Registering a Component InstanceThe builder.Register(memos);
builder.Register(Console.Out).As<TextWriter>().ExternallyOwned();
The Registering a Component with its Implementation TypeAutofac can also create components the way that other containers do, using Reflection (many optimise this scenario with MSIL-generation.) This means that you can tell Autofac about the type that provides a service, and it will work out how to call the most appropriate constructor, with parameters chosen according to the other available services. The builder.Register<MemoChecker>();
In general, the most common use of auto-wiring is to register a batch of components, e.g.: foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
if (typeof(IController).IsAssignableFrom(t))
builder.Register(t).FactoryScoped();
This makes large numbers of components available without the overhead of registering each one, and you should definitely consider it in these situations. Autowiring is also very useful when components are registered via the application's XML configuration file. Autowiring is very powerful but is best used judiciously – while the mechanics of an expression-based registration are very clear, reflection-based behaviour is dynamic and relies on the constructor selection policy being used by the container. Adding a new constructor to a class might change which constructor will be used, with unintended consequences. Completing the ExampleThe process of creating the component registrations before requesting the var builder = new ContainerBuilder();
builder.Register(c => new
MemoChecker(c.Resolve<IQueryable<Memo>>(),
c.Resolve<IMemoDueNotifier>()));
builder.Register(c => new
PrintingNotifier(c.Resolve<TextWriter>())).As<IMemoDueNotifier>();
builder.Register(memos);
builder.Register(Console.Out).As<TextWriter>().ExternallyOwned();
using (var container = builder.Build())
{
container.Resolve<MemoChecker>().CheckNow();
}
The lack of nesting in the configuration code demonstrates the 'flattening out' of the dependency structure that a container provides. It may be hard to see how this could ever be simpler than the direct object construction in the 'by hand' example, but again remember that this sample application has far fewer components than most useful systems. The most important difference to note is that each component is now configured independently of all the others. As more components are added to the system, they can be understood purely in terms of the services they expose and the services they require. This is one effective means of controlling architectural complexity. Deterministic Disposal
The problem is made worse by designs that allow for multiple implementations of the same service. In the example application, it is feasible that many different implementations of Components that use a notifier have no way of knowing whether they should try to cast it to Autofac solves this problem by tracking all of the disposable objects created by the container. Note the example from above: using (var container = builder.Build())
{
container.Resolve<MemoChecker>().CheckNow();
}
The container is in a This is important because true to the spirit of separating usage from configuration concerns, the With this comes peace of mind – you don't even need to read back through the example to discover whether any of the classes in it actually implemented Disabling DisposalNote the Fine-Grained Control of Component LifetimesThe container will normally exist for the duration of an application execution, and disposing it is a good way to free resources held by components with the same application-long life-cycle. Most non-trivial programs should also free resources at other times: on completion of an HTTP request, at the exit of a worker thread, or at the end of a user's session. Autofac helps you manage these life-cycles, with nested containers for each lifespan: using (var appContainer = builder.Build())
{
using (var request1Container = appContainer.CreateInnerContainer())
{
request1Container.Resolve<MyRequestHandler>().Process();
// resources associated with request 1 are freed
}
using (var request2Container = appContainer.CreateInnerContainer())
{
request2Container.Resolve<MyRequestHandler>().Process();
// resources associated with request 2 are freed
}
// resources at the application level are freed
}
Defining how components are associated with levels in the container hierarchy is done using what Autofac terms 'scope.' ScopeNot every component in an application will be a Singleton, as they are in the example. A DI container like Autofac allows you to specify how many instances of a component can exist and how they will be shared between other components. Controlling the scope of a component independently of its definition is a very important improvement over traditional methods like defining singletons through a static Autofac's approach to scope relies on three fundamental models:
Singleton ScopeWhen no scoping model is specified in a component registration, singleton scope is assumed. There will be at most one instance of the component in the container hierarchy, and it will be disposed when the container in which it is registered is disposed (e.g., Factory ScopeA component can be configured to have factory scope using the builder.Register(c => new MyClass()).FactoryScoped();
Each time a factory-scoped component is requested from the container, a new instance will be created: var a = container.Resolve<MyClass>();
var b = container.Resolve<MyClass>();
Assert.AreNotSame(a, b);
A factory-scoped component will be disposed along with the container in which it was requested. This is not always the most-inner container – if a factory-scoped component is required in order to construct a singleton component, for example, then the factory-scoped component will live alongside the singleton component in the root container. Container ScopeThe final scope model is per-container, achieved using the builder.Register(c => new MyClass()).ContainerScoped();
This provides the flexibility needed to implement per-thread, per-request, or per-transaction component life-cycles. Simply create an inner container that lives for the duration of the required life-cycle. Requests within the same container will retrieve the same instance, while requests in different containers will result in different instances: var a = container.Resolve<MyClass>();
var b = container.Resolve<MyClass>();
Assert.AreSame(a, b);
var inner = container.CreateInnerContainer();
var c = inner.Resolve<MyClass>();
Assert.AreNotSame(a, c);
Using Scope to Control VisibilityA component's dependencies can only be satisfied by other components within the same container or in an outer (parent) container. This ensures that a component's dependencies are not disposed before it is. If a nesting of application, session, and request is desired, then the containers would be created as: var appContainer = builder.Build();
var sessionContainer = appContainer.CreateInnerContainer();
var requestContainer = sessionContainer.CreateInnerContainer();
var controller = requestContainer.Resolve<IController>("home");
Keep in mind with these examples that the In this scenario, the allowed direction of dependencies is request -> session -> application. Components that are used in processing a user's request can reference any other component, but dependencies in the other direction are not allowed, so, for instance, an application-level singleton won't be wired up to a component specific to a single user's session. In such a hierarchy, Autofac will always serve component requests in the shortest-lived container. This will generally be the request container. Singletons will naturally reside at the application-level. To pin the lifetime of a component to the session-level, see the tags article on the Autofac wiki. Autofac's scope model is unique and powerful. The relationship between scope and nested container disposal makes a huge number of dependency configurations possible, while enforcing that an object will always live at least as long as the objects that depend on it. Autofac in ApplicationsDependency injection is an extremely powerful structuring mechanism, but to gain those advantages, a significant proportion of a system's components need to be available to other components through the container. Normally, this presents some challenges. In the real world, existing components, frameworks, and architectures often come with their own unique 'creational' or life-cycle requirements. The features of Autofac described so far are designed to get existing, 'plain old .NET' components into the container without the need for modifications or adapter code. Expressive RegistrationsUsing expressions for component registration makes including Autofac in an application a snap. A few example scenarios illustrate the kinds of things Autofac facilitates: Existing factory methods can be exposed using expressions: builder.Register(c => MyFactory.CreateProduct()).As<IProduct>().FactoryScoped();
Existing singletons that need to be loaded on first access can be registered using an expression, and loading will remain 'lazy': builder.Register(c => MySingleton.Instance);
Parameters can be passed to a component from any available source: builder.Register(c => new MyComponent(Settings.SomeSetting));
An implementation type can even be chosen based on a parameter: 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();
Simplified IntegrationIntegration, in this context, means making the services of existing libraries and application components available through the container. Autofac comes with support for some typical integration scenarios like usage within an ASP.NET application; however, the flexibility of the Autofac model makes a lot of integration tasks so trivial that they're best left to the designer to implement in the way most suitable for their application. Expression-based registrations and deterministic disposal, combined with the 'laziness' of component resolution, can be surprisingly handy when integrating technologies: var builder = new ContainerBuilder();
builder.Register(c => new ChannelFactory<ITrackListing>(new BasicHttpBinding(),
new EndpointAddress("http://localhost/Tracks")))
.As<IChannelFactory<ITrackListing>>();
builder.Register(c => c.Resolve<IChannelFactory<ITrackListing>>().CreateChannel())
.As<ITrackListing>()
.FactoryScoped();
using (var container = builder.Build())
{
var trackService = container.Resolve<ITrackListing>();
var tracks = trackService.GetTracks("The Shins", "Wincing the Night Away");
ListTracks(tracks);
}
This is an example of WCF client integration taken from the Autofac website. The two key services here are Some things to note here:
This section should have given you an idea of how working with Autofac lets you focus on writing your application – not extending or fussing over the intricacies of a DI container. Where to Next?I hope this article has illustrated the kinds of rewards that will come from learning how to use Autofac. The next steps might be to:
Autofac ContribAs a central architectural component, a dependency injection container has a lot of responsibilities and needs to be flexible, yet behave very predictably. This makes for some difficult trade-offs when deciding what should go in to the project and what should not. Out of this, a new project dedicated to integration and extension has been born. Once you've become comfortable with Autofac and found the best way of using it in your applications, please come and share your discoveries! CreditsThanks to Rinat Abdullin, Luke Marshall, Tim Mead, and Mark Monsour for reviewing this article and providing many helpful suggestions. Without your help guys, I fear it would have been completely unintelligible. If it is still unintelligible, then all of the blame lies with me :)
|
||||||||||||||||||||||