Autofac 2.2 Preview news: the Mutable Edition

110 views
Skip to first unread message

Nicholas Blumhardt

unread,
May 3, 2010, 1:13:14 AM5/3/10
to Autofac
Hi all,

The Autofac team hasn't slowed down since the 2.1 release last month. Already a lot of work has gone into the 2.2 version, so I've uploaded some preview builds that I hope you'll be able to try out:

Any feedback you can provide would be great - there's a full list of changes below, and I know many of you have been waiting eagerly for some of them :)

Your feedback is especially valuable if you are using container mutability - that is, the ability to change a container once services have already been resolved.

The Autofac 2.1 release removed the ContainerBuilder.Build() method capable of updating an existing container. This enabled a lot of the 2.1 features to be implemented very efficiently, but caused problems for integrators, who need to plug Autofac into third-party frameworks not observing the register-then-resolve split that Autofac uses.

In 2.2, mutability is back, in the form of ContainerBuilder.Update(). The implementation should be zero-cost for those not modifying their containers after construction, yet provide all of the Autofac relationship-type goodness to those who do, also without much impact on performance. Getting this algorithm right has taken a long time, so there may be some issues yet to find.

Support for customising nested lifetime scopes through BeginLifetimeScope() has also been improved in this release, with several bugs fixed there.

The change list:
  • Aggregate Service support (Contrib)
  • Lightweight Adapter support
  • ASP.NET MVC 2.0 Areas Support (Issue 162)
  • Earlier checking of generic types in RegisterGeneric
  • Exclude delegate types from assembly scanner
  • Resolve from parent scope in correct order in multi-level nesting (Issue 214)
  • Updated Silverlight build to work with VS2010 RTM (Issue 208)
  • Instances/Singletons registered in nested scopes are scoped to and disposed with the nested scope, and can resolve dependencies on each other (Issue 188)
  • Support embedded interop types ("no-PIA") as TypedServices (Issue 211)
  • Resolve() overloads for KeyedServices and generic version of TryResolve() (Issue 192)
  • ContainerBuilder.Update() for adding to an existing container
Breaking changes in this version:
  • ContainerBuilder.RegisterSource() and IComponentRegistry.AddRegistrationSource() take an additional parameter (isAdapter) - this may change again before release
  • Several previously virtual ContainerBuilder methods are now non-virtual - if using these to circumvent the old mutability restriction, switch to the new Update() method
Cheers,

Nick

--
You received this message because you are subscribed to the Google Groups "Autofac" group.
To post to this group, send email to aut...@googlegroups.com.
To unsubscribe from this group, send email to autofac+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/autofac?hl=en.

CVertex

unread,
May 3, 2010, 1:30:38 AM5/3/10
to Autofac
Hi Nick,

Could you please explain the Mutability feature more? Perhaps in a
blog post. I'm unsure how to use it, or when it's wise to use it.

Regards,
CV

On May 3, 3:13 pm, Nicholas Blumhardt <nicholas.blumha...@gmail.com>
wrote:
> Hi all,
>
> The Autofac team hasn't slowed down since the 2.1 release last month.
> Already a lot of work has gone into the 2.2 version, so I've uploaded some
> preview builds that I hope you'll be able to try out:
>
>    - .NET 3.5:
>    http://autofac.googlecode.com/files/Autofac-2.2.2.875-NET35.zip
>    - .NET 4.0:
>    http://autofac.googlecode.com/files/Autofac-2.2.2.875-NET40.zip
>    - Silverilght 3:
>    http://autofac.googlecode.com/files/Autofac-2.2.2.875-SL3.zip
>    - Contrib (.NET 3.5+)
>    http://autofac.googlecode.com/files/AutofacContrib-2.2.2.875-NET35.zip
>
> Any feedback you can provide would be great - there's a full list of changes
> below, and I know many of you have been waiting eagerly for some of them :)
>
> Your feedback is especially valuable if you are using *container mutability* -
> that is, the ability to change a container once services have already been
> resolved.
>
> The Autofac 2.1 release removed the ContainerBuilder.Build() method capable
> of updating an existing container. This enabled a lot of the 2.1 features to
> be implemented very efficiently, but caused problems for integrators, who
> need to plug Autofac into third-party frameworks not observing the
> register-then-resolve split that Autofac uses.
>
> In 2.2, mutability is back, in the form of ContainerBuilder.Update(). The
> implementation should be zero-cost for those not modifying their containers
> after construction, yet provide all of the Autofac relationship-type
> goodness to those who do, also without much impact on performance. Getting
> this algorithm right has taken a *long* time, so there may be some issues
> yet to find.
>
> Support for customising nested lifetime scopes through BeginLifetimeScope()
> has also been improved in this release, with several bugs fixed there.
>
> The change list:
>
>    - Aggregate Service support (Contrib)
>    - Lightweight Adapter support
>    - ASP.NET MVC 2.0 Areas Support (Issue 162)
>    - Earlier checking of generic types in RegisterGeneric
>    - Exclude delegate types from assembly scanner
>    - Resolve from parent scope in correct order in multi-level nesting
>    (Issue 214)
>    - Updated Silverlight build to work with VS2010 RTM (Issue 208)
>    - Instances/Singletons registered in nested scopes are scoped to and
>    disposed with the nested scope, and can resolve dependencies on each other
>    (Issue 188)
>    - Support embedded interop types ("no-PIA") as TypedServices (Issue 211)
>    - Resolve() overloads for KeyedServices and generic version of
>    TryResolve() (Issue 192)
>    - ContainerBuilder.Update() for adding to an existing container
>
> Breaking changes in this version:
>
>    - ContainerBuilder.RegisterSource() and
>    IComponentRegistry.AddRegistrationSource() take an additional parameter
>    (isAdapter) - this may change again before release
>    - Several previously virtual ContainerBuilder methods are now non-virtual

Nicholas Blumhardt

unread,
May 3, 2010, 4:12:41 AM5/3/10
to aut...@googlegroups.com
Hi CV,

If you can, registering everything up-front and calling ContainerBuilder.Build() only once is the best bet. Even in containers that don't split registration from dependency resolution in their API, there are benefits to keeping the two separated.

For a start, IoC containers really simplify code by taking over ordering of construction - doing DI by hand means that things have to be manually sorted so that the most-depended-on things are constructed before their dependants.

When mixing register/resolve calls by registering some things, resolving others, then registering more (i.e. with container mutability,) the developer has to be aware of that dependency graph in order to ensure that nothing is resolved before its correct dependencies are registered. Once defaults and other corner cases are in the mix there's a lot more for a developer/maintainer to understand.

I'd only encourage using ContainerBuilder.Update() - which is called using a new builder, different from the one that created the container - if you're implementing a generic "IContainerAdapter" as some third-party frameworks use to support multiple containers.

Even then, if the framework makes all of its Register() calls before any Resolve() calls, using a single ContainerBuilder and a lazily-built container is a better idea.

Anyhow, this is somewhat opinionated, and there are naturally some scenarios where it is the right thing to do, so we do have ContainerBuilder.Update() in 2.2. The work people are doing out there to get Autofac integrated with other frameworks is incredibly valuable, so supporting them and making the task easy is #1 priority :)

The fun in the implementation goes something like this...

When resolving an adapter for a relationship type like Func<Foo> for the first time, a component registration has to be created that will manage the sharing/lifetime of the returned Funcs. This means tracking down the target Foo, because Func<Foo> isn't a resolver in Autofac, each Func targets a specific component. If there are no Foos, there are no Func<Foos> and if there are 10 Foos, there are 10 Func<Foos>. All awesomeness (IMHO.)

So, to make sure this 1:1 correspondence is maintained, the first time Func<Foo> is resolved, Autofac finds all the Foos, and creates the registrations for the Funcs.

Now, a component supporting service Foo is registered. Next time we resolve Func<Foo>, we'd expect to get it as the default (last-in wins) and if we resolve all Func<Foo>s then it should be represented in the collection.

This means that we have to go from Foo back to the Func<Foo> adapter. IRegistrationSource is very general and doesn't say anything about the kinds of things it can adapt. Furthermore, to get composability between Func, Lazy, Owned, Meta etc., we have to be prepared for Foo to be at the bottom of a chain.

The essence of the solution is to take all of the adapters we have, put them in a box where they can recursively call each other but not see any of the existing components except the new one. We then find all the services that have implementations that directly or indirectly target another component supporting the service Foo. Asking the 'boxed' adapters for these services generates the complete set of components adapting Foo. These get registered back in the main component registry, and away we go!

Now that the algorithm is working I think there are some ways that it can be made clearer and more robust. Before tweaking any of the rest of the model to provide better information to feed into it, it needs some practical validation though.

Importantly, we only go through this process when Foo implementations are registered after Resolve() has already been called. Using a single ContainerBuilder.Build(), none of this is necessary, and so I'd recommend it just on the principle of doing the least work possible.

Hope this is what you were interested in - I'd write it as an article but I think I'd drive myself crazy trying to express all of it in a way that makes sense to someone not already familiar with the container!

Cheers,

Nick

Alex Meyer-Gleaves

unread,
May 3, 2010, 10:28:39 AM5/3/10
to aut...@googlegroups.com
Hi Nick,

I think this is good news for Autofac. The need to once again support mutability was not about to go away. Aside from providing better support for integration, this change should make migration easier for those who have invested a lot in earlier versions of Autofac and are relying on its mutable behaviour. With the number of breaking changes from 1.4 being reasonably high already, removing mutability from the list of potential upgrade issues should hopefully see an increase in the number of users upgrading. The same could apply to users of other containers that are relying on mutability, and would like to switch to Autofac without incurring too much refactoring cost.

Cheers,

Alex.

Matt Burton

unread,
May 4, 2010, 6:50:19 PM5/4/10
to aut...@googlegroups.com
Nick -

Can you provide any insight / preview as to how you're planning to
support native multi-tenancy in 2.2? (issue 202) We're trying to wrap
our heads around how to reproduce what we had working in 1.x with
nested containers per tenant in 2.x, so any guidance as to how to
handle it in 2.1 or insight into what's coming would be most
appreciated.

Thanks,
Matt

Nicholas Blumhardt

unread,
May 5, 2010, 7:24:48 AM5/5/10
to aut...@googlegroups.com
Hi Matt,

You can achieve the same results as you would with nested containers by using nested lifetimes - if there are any limitations that hold you up there let me know.

I did start looking into the possibilities in 2.x, and came up with an API for MultiTenancyContainerProvider like the following:

public class MultiTenancyContainerProvider : IContainerProvider
{
    public MultiTenancyContainerProvider(Action<ContainerBuilder> baseConfiguration);
    public void ConfigureDefaultTenant(Action<ContainerBuilder> defaultTenantConfiguration);
    public void ConfigureTenant(string id, Action<ContainerBuilder> tenantConfiguration);
}

The idea is that the base configuration covers everything common to all tenants; the default configuration, which is optional, applies when a tenant is unidentified or has no special configuration, and for tenants with specialised configuration their own specific set of configuration is applied.

So usage would look like:

var cp = new MultiTenancyContainerProvider(builder => builder.Register<Bar>());
cp.ConfigureDefaultTenant(builder => builder.Register<AverageFoo>().As<IFoo>());
cp.ConfigureTenant("matt", builder => builder.Register<SuperFoo>().As<IFoo>());

Now, I hit some analysis paralysis trying to decide how to identify tenants - some ideas there would be great, I can come up with schemes that work but I don't know what best practice is.

The implementation could be done one of two ways. The "Autofac 1" way would be to use the base configuration to build a container, then keep an ILifetimeScope for each tenant, configured appropriately, and create the child IRequestLifetimes off of that.

The second option would be to keep all tenants completely isolated by building a whole container for each one. I.e. the configuration actions for the base and tenant-specific configuration would be applied to the same builder.

If the tenants are just different kinds of users of the same application then the first option seems natural, while if the tenants are say, different customers with different data and no interaction, then option two seems to fit.

I've also toyed with the idea of having a default option like:

    public void ConfigureDefaultTenant(Action<string, ContainerBuilder> defaultTenantConfiguration);

In this case, you could drive per-tenant customisation via XML config, along the lines of:

cp.ConfigureDefaultTenant((name, builder) =>
   builder.RegisterModule(new ConfigurationSettingsReader("autofac." + name)));

The parameter to CSR is the config section name, so some thought needs to go into how to handle missing sections.

What do you think? Is this along the lines that you've been exploring? I'd still love to get some movement around this but don't feel I'm the most experienced in this scenario, so any guidance/feedback/criticism much appreciated.

Cheers,
Nick

Matt Burton

unread,
May 7, 2010, 7:55:44 PM5/7/10
to aut...@googlegroups.com
Nick -

Hey - behind in emails here - bit of a crazy week... Cool - thanks for
the preview and insight! I definitely like where you're headed here
re: multi-tenancy support - we'll discuss these ideas here next week -
have a team spinning up on a next-gen project that will use Autofac
2.x and will be multi-tenant top to bottom...

Thanks,
Matt

On Wed, May 5, 2010 at 4:24 AM, Nicholas Blumhardt

Nicholas Blumhardt

unread,
May 9, 2010, 1:24:03 AM5/9/10
to aut...@googlegroups.com
Matt, sounds awesome! Big week here too.

Looking forward to digging into this one further soon.

Nick

Matt Burton

unread,
May 12, 2010, 12:16:27 AM5/12/10
to aut...@googlegroups.com
Nick -

Alright - we had a chance to sit down and talk through this today.
Here's what we came up with:

1) Instead of the container provider strategy being the public entry
point for multi-tenancy, what if instead we leaned on the existing
module infrastructure and added the multi-tenancy angle to that? So a
multi-tenant IModule interface:

interface ITenantModule : IModule
{
object TenantId { get; }
}

Example impl:

class MyTenantModule : ITenantModule
{
public object TenantId
{
get { return 1; }
}
}

Then a new ContainerBuilder (extension) method to register tenant modules:

ContainerBuilder.RegisterTenantModule<T>(T module) where T : ITenantModule

builder.RegisterTenantModule(new MyTenantModule());

It would be nice to be able to offer a generic ITenantModule interface, i.e.:

interface ITenantModule<TKey> : ITenantModule
{
TKey TenantId { get; }
}

But unfortunately you get into all sorts of weirdness trying to make
the compiler happy...

2) XML config becomes much more prevalent in a multi-tenant world,
whether we like it or not. For that you could either take the approach
you mentioned where the configuration section gets named as the
tenant, or you could introduce a new tenant element with an ID
attribute which could contain all of the other element types -
components, modules, files, etc... just scoped to that tenant. That
way you could create a new MultiTenantConfigurationSettingsReader
implementation that would read in those tenant blocks and wire
everything up scoped correctly.

<autofac>
<tenants>
<tenant id="tenant1">
...
</tenant>
</tenants>
</autofac>

Another thought here is the ability to reuse the XML format and create
a new configuration reader whose source is a web service or some other
remote resource rather than a file in order to pull off centralized
config for all tenants...

It would seem that the order of precedence here would be default
container, tenant containers configured via code, then xml tenant
config augmenting/overriding that.

Thoughts?
Matt

On Sat, May 8, 2010 at 10:24 PM, Nicholas Blumhardt

Nicholas Blumhardt

unread,
May 18, 2010, 6:07:00 PM5/18/10
to aut...@googlegroups.com
Matt, suffering a bit of inbox cloggage here, hope I'm not too late to the party!

I like the idea of creating a more integrated API - the concept of using tentant ids is nice and the XML additions would be slick :)

On precedence, I think the standard 'last in wins' style will carry over to this implementation. I agree with the defaults you suggested.

There seem to be a few different pieces required -

a) the underlying container management infrastructure (still probably from one of the options discussed earlier)
b) the configuration API (either builder-per-tenant or a module-based system; definitely some XML goodness though)
c) a way of detecting and consuming the current tenant

The first two items just need a shake out - we could put together some prototypes and see where the rough edges are.

I'm still not sure about the third; is there a "typical" technique for pulling this off?

When I have some time - or if anyone else wants to jump in - perhaps moving all of this to the issue tracker now is a good idea.

Cheers,
Nick

Matt Burton

unread,
May 21, 2010, 2:03:58 PM5/21/10
to aut...@googlegroups.com
Ugh, started a response 3 days ago and haven't been able to finish.
Here's the bit I had:

> c) a way of detecting and consuming the current tenant

This is really a function of the hosting application, IMO - you could
perhaps offer sample code for say WCF services and an ASP.NET MVC
application with a suggested method for identifying the tenant for
each request but it's really an implementation detail that can't be
made generic, necessarily.

Everything else looks good - hopefully this weekend I might have some
time to update the issue tracker, if it hasn't been done already.

Thanks,
Matt

On Tue, May 18, 2010 at 3:07 PM, Nicholas Blumhardt

tillig

unread,
May 24, 2010, 2:20:13 PM5/24/10
to Autofac
I, too, am looking forward to the multitenancy integration. As it
stands, I'll probably be trying to get something similar to the above
running in a local environment. Not sure on timelines, but if I get it
up and running, I'll zip it up and send it over.

As for detecting and consuming the appropriate tenant, I agree, it's
up to the consuming app to propagate that around, but I'm thinking
there will probably have to be Resolve<T> overloads that take in a
tenant ID. If no tenant ID is passed in, if the tenant ID is null, or
if the overload without the tenant ID is used, then things behave as
they do today. If a tenant ID is provided, the tenant-specific
dependency will be resolved.

What I'm not sure about yet (at least, how it'd be implemented) is how
tenant specific lifetimes work and persist themselves. Like if you
have a SingleInstance lifetime object, is that SingleInstance *per
tenant* or for the *whole app*? What if one tenant overrides that
registration? I'm thinking there's going to have to be something like
InstancePerTenant() that translates to
InstancePerMatchingLifetimeScope(Tenant) or something, and
SingleInstance will actually mean for the whole app.

The tenant lifetime scope would have to stick around for the duration
of the app, too, so that'll have to be stuck somewhere.

Hmmm. I'll have to think some more about this. It was so much easier
in 1.x. :)

-T

On May 21, 11:03 am, Matt Burton <matt.bur...@gmail.com> wrote:
> Ugh, started a response 3 days ago and haven't been able to finish.
> Here's the bit I had:
>
> > c) a way of detecting and consuming the current tenant
>
> This is really a function of the hosting application, IMO - you could
> perhaps offer sample code for say WCF services and an ASP.NET MVC
> application with a suggested method for identifying the tenant for
> each request but it's really an implementation detail that can't be
> made generic, necessarily.
>
> Everything else looks good - hopefully this weekend I might have some
> time to update the issue tracker, if it hasn't been done already.
>
> Thanks,
> Matt
>
> On Tue, May 18, 2010 at 3:07 PM, Nicholas Blumhardt
>
> <nicholas.blumha...@gmail.com> wrote:
> > Matt, suffering a bit of inbox cloggage here, hope I'm not too late to the
> > party!
> > I like the idea of creating a more integrated API - the concept of using
> > tentant ids is nice and the XML additions would be slick :)
> > On precedence, I think the standard 'last in wins' style will carry over to
> > this implementation. I agree with the defaults you suggested.
> > There seem to be a few different pieces required -
> > a) the underlying container management infrastructure (still probably from
> > one of the options discussed earlier)
> > b) the configuration API (either builder-per-tenant or a module-based
> > system; definitely some XML goodness though)
> > c) a way of detecting and consuming the current tenant
> > The first two items just need a shake out - we could put together some
> > prototypes and see where the rough edges are.
> > I'm still not sure about the third; is there a "typical" technique for
> > pulling this off?
> > When I have some time - or if anyone else wants to jump in - perhaps moving
> > all of this to the issue tracker now is a good idea.
> > Cheers,
> > Nick
> ...
>
> read more »

tillig

unread,
May 24, 2010, 3:09:41 PM5/24/10
to Autofac
I take back the bit about the Resolve overloads. You'd resolve out of
the tenant-specific lifetime, so the question would be how to get that
lifetime. Maybe some sort of ResolveTenantLifetime() method that would
either find the tenant lifetime that exists or start a new one if it
doesn't exist.

Then there could be an ITenantContextProvider like:

public interface ITenantContextProvider
{
object CurrentTenantId { get; }
}

If one of those is registered in the container, that's what would be
used to determine the current tenant. Implementations could get it
from the Principal, from a variable, or wherever. If no provider is
registered, the "null tenant" or "default tenant" lifetime would be
returned.

-T

Nicholas Blumhardt

unread,
May 26, 2010, 5:55:25 PM5/26/10
to aut...@googlegroups.com
> c) a way of detecting and consuming the current tenant

I'm agreed that this needs to be configurable. My real question is, until the tenant is identified, what set of services is available? Can you use a service (e.g. a repository) to actually determine who the tenant is?

The only way I can currently see this working transparently is if on request #1, when the tenant is unidentified, the default set of services is available. After this 'log in' process, the tenant could come in identified on subsequent requests via a cookie. I'm not sure how this would be secured though.

Nicholas Blumhardt

unread,
May 26, 2010, 5:59:38 PM5/26/10
to aut...@googlegroups.com
I'm a bit wary of giving application-level components a way of switching between the two sets of registrations- we might end up with that implementation, but there could be cases where components that should be tenant-specific are inadvertently loaded and used via the wrong lifetime. Like having multiple containers visible in an app, multiple visible lifetimes will get outside of the saftety net of the container's resolution process. I'd much prefer to have the tenant identified up-front somehow.... Though I don't have a complete solution there :)

Its very interesting to read your reports on the provider you're building - I need a bit more time to digest your most recent message but thanks for sharing it Travis!

Nick
Reply all
Reply to author
Forward
0 new messages