The previous post on this blog showed how Autofac 2 can provide Lazy<T> dependencies whenever T is registered.
Other types that Autofac provides via the same mechanism are:
Func<T>,Func<T,U>&c., to create components on-the-fly;Owned<T>to manually control the lifetime of a component;IEnumerable<T>to get all implementers of a service; and- Instances of a particular generic type, e.g.
IRepository<Customer>, when the open generic type, e.g.Repository<T>, is registered.
Each of these can be thought of as a declarative context adapter. Each of these types supports a scenario involving some kind of interaction between a component and the container that hosts it. Instead of having the component call the container directly, the component expresses requirements declaratively using these little adapter types, and the container provides the implementation.
Don’t worry if it is a stretch to imagine how each of these works. For today, we’ll examine the implementation of Lazy<T>, and that should give you enough background to figure out the others if you want to.
IRegistrationSource: a better Service Locator?
Context adapters like these are provided to Autofac via IRegistrationSources.
{
IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor);
}
A registration source is much like a Service Locator: when the container needs to provide service, it queries the registration source to get any available implementations.
The registration source can use the passed-in registrationAccessor to get information about other components registered with the container.
At first glance this seems to be a Service Locator plain and simple, however there is an important distinction. The IComponentRegistration‘s returned from RegistrationsFor() tell the container how to make components supporting the service. They are not instances of the services themselves.
This means that among other benefits, a registration source is called only once for any particular service (e.g. Lazy<IFoo>) no matter how many times the service is resolved. The performance overhead, compared with using a Service Locator this way, is very low.
LazyRegistrationSource
If you’re using Autofac on .NET 4.0, you can use LazyRegistrationSource (by registering LazyDependencyModule) to get support for the context adapter System.Lazy<T>.
LazyRegistrationSource is a very simple implementation of IRegistrationSource.
{
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (swt == null ||
!swt.ServiceType.IsGenericType ||
swt.ServiceType.GetGenericTypeDefinition() != typeof(Lazy<>))
return Enumerable.Empty<IComponentRegistration>();
var valueType = swt.ServiceType.GetGenericArguments()[0];
var valueService = swt.ChangeType(valueType);
var registrationCreator = CreateLazyRegistrationMethod.MakeGenericMethod(valueType);
return registrationAccessor(valueService)
.Select(v => registrationCreator.Invoke(null, new object[] { service, v }))
.Cast<IComponentRegistration>();
}
static readonly MethodInfo CreateLazyRegistrationMethod = typeof(LazyRegistrationSource)
.GetMethod("CreateLazyRegistration", BindingFlags.Static | BindingFlags.NonPublic);
static IComponentRegistration CreateLazyRegistration<T>(
Service providedService,
IComponentRegistration valueRegistration)
{
var rb = RegistrationBuilder.ForDelegate(
(c, p) => {
var context = c.Resolve<IComponentContext>();
return new Lazy<T>(() => (T)c.Resolve(valueRegistration, p));
})
.As(providedService);
return RegistrationBuilder.CreateRegistration(rb);
}
}
Let’s break this down.
RegistrationsFor()
The whole purpose of RegistrationsFor() is to map a service to components that can provide it:
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
In LazyRegistrationSource we’re looking for services that are based on the Lazy<T> type.
So, the first part of RegistrationsFor checks to see if the service fits this description, and if not, the method returns an empty list of components, since it can’t provide anything.
if (swt == null ||
!swt.ServiceType.IsGenericType ||
swt.ServiceType.GetGenericTypeDefinition() != typeof(Lazy<>))
return Enumerable.Empty<IComponentRegistration>();
If we get past this test, then we know we’re dealing with Lazy<T>. Since we provide Lazy<T> on top of T, we extract T and construct a Service with it so that we can find implementations.
var valueService = swt.ChangeType(valueType);
Lazy<T> is a generic type, but we’re deep in the land of reflection here. Rather than clog our code up with tonnes of reflective goop, we use a generic method parameterised on T to switch over into strongly-typed code to create a registration for Lazy<T>.
We’ll come back to the implementation of CreateLazyRegistration in just a second.
Once we have a method that can create a IComponentRegistration for Lazy<T> based on a component registration for T, we use registrationAccessor to find all the T registrations, and use our function to map them to a result.
.Select(v => registrationCreator.Invoke(null, new object[] { service, v }))
.Cast<IComponentRegistration>();
Now to the implemenation behind registrationCreator.
CreateLazyRegistration
This method takes an IComponentRegistration for T and wraps it in an IComponentRegistration for Lazy<T>. The providedService parameter is the service that our registration is going to provie to the container (some kind of service based on Lazy<T>.)
Service providedService,
IComponentRegistration valueRegistration)
{
Autofac 2 provides some helpers for creating registrations. The syntax below is the implementaiton equivalent of calling Register((c, p) => ...).As(providedService) on a ContainerBuilder.
(c, p) => {
var context = c.Resolve<IComponentContext>();
return new Lazy<T>(() => (T)c.Resolve(valueRegistration, p));
})
.As(providedService);
return RegistrationBuilder.CreateRegistration(rb);
Some noteworthy points stick out:
- The
contextis re-resolved fromc, since we need the underlying context and not the temporary one used for the current operation; - We’re using a new
Resolve()overload that takes anIComponentRegistration; and - We forward the parameters
pon to the value resolve call, so that parameters passed when resolvingLazy<T>will be available toT.
Wrapping Up
This isn’t the kind of code you’ll write day-to-day when using Autofac 2. What’s exciting though, is how easy it can be to write sophisticated IoC-driven applications without having infrastructure concerns scattered through your code.
Next time you hear the question “but how do I do X without calling the container directly?” remember that there’s probably a declarative context adapter for that, based on IRegistrationSource.
Very nice mate. I quite like the implementation and have been pondering the thought of the use of Lazy types in our current project (currently using Unity). I don’t think that the overhead of using non-Lazy types is that high for us at the moment, but down the track we may run into a few scenarios where we’re ending up constructing a lot of stuff that we don’t need.
Obviously we can’t yet bind ourselves to any .NET 4 stuff (despite using MVC Preview 2
), but when it’s released it’s certainly on the cards. However, being bound to Unity I’m not sure how easy it’d be to convince the troops to use another IoC container.
So what are the chances of getting some lambda goodness when setting extended properties?
I can’t help but feel horrid when I see strings as property names.
Love ya work mate. Keep it up! Must catch up for lunch or something soon.
OJ
Ey OJ!
I like the ‘strongly-typed extended properties’ idea…
builder.RegisterType()()(ep => {
.As
.WithExtendedProperties
ep.For(m => m.AppenderName, “screen”);
ep.For(m => m.Dpi, 126);
});
There are some limitations in the builder syntax that might make this a bit trickier, I’ll have a spike and see how it goes…
I’m sure Unity will be updated to match .NET 4 as well sometime, might be interesting to try Lazy as a Unity extension?
See you soon for sure!
NB
I was thinking exactly the same thing re: Unity extension. Going to give that a whirl when I get back to work.
Re: EP suggestion -> that’s exactly the shiznit I was talking about. I might even spike our solution with an Autofac IoC setup and see what the troops think
Ya never know your luck eh?
Cheers bud
OJ
[...] Around with Autofac2 and Declarative Context Adapters in Autofac 2 (Nicholas [...]
[...] who’ve been following along with this blog lately will have seen how registration sources provide a simple way to extend the container to understand new component types (like Lazy [...]
[...] have any special knowledge of any of these adapter types. They’re all plugged in as IRegistrationSource implementations, meaning that you are free to change or extend the set of adapter types as your needs [...]