Ambient Container Scope and Func<> Callbacks

110 views
Skip to first unread message

jonathan...@hotmail.com

unread,
Jun 26, 2008, 4:29:14 PM6/26/08
to Autofac
Nick,

Overview (you can always tell when a forum post is too long when it's
broken into sections)

Over the past few days we have been starting down the path of letting
Autofac take care of component disposal for us. Prior to Autofac we
had to explicitly handle disposal of components and resources. As we
have proceeded down this path we have found a few “gotchas”.

One thing we found very curious was that the container’s scope wasn’t
ambient whereas a typical “scope” class such as TransactionScope was
ambient. A normal usage of TransactionScope might look like the
following:

// Main
using (var scope = new TransactionScope())
{
// call to UserRepository

// some work in UserRepository
using (var repositoryScope = new TransactionScope())
{
// call to UserDAO

// some work inside of UserDAO
using (var daoScope = new TransactionScope())
{
// DB activity
}
}
}

The above scenario might be a bit extreme and somewhat unrealistic,
but the idea is that each component wants and needs to control its
little slice of the universe and that in this case each component on
its level is very concerned about ensuring the atomicity of the work
performed. In the above scenario TransactionScope was “aware” of the
parent scope and was able to coordinate activity with the parent in a
consistent and predictable fashion.

The Problem

In the Autofac forum and wiki as well as sprinkled across the internet
are a few overly simplistic examples dealing with nesting of scopes
via the container’s CreateInnerContainer method. Typically those
examples looked like the following:

using (var root = builder.Build())
{
// do some work
using (var inner = root.CreateInnerContainer())
{
// get a container-scoped component
var dbConn = inner.Resolve<IDbConnection>();
}
}

These examples work well in order to grasp the general concept of
container scoping but they do create some confusion. Specifically,
the following does not appear to be possible:

var builder = new ContainerBuilder();

builder.Register(c => new SqlConnection("connection string here"))
.As<IDbConnection>()
.ContainerScoped();

builder.Register(c => (Func<IDbConnection>)c.Resolve<IDbConnection>())
.As<Func<IDbConnection>>() // registers a callback to create
connection.
.FactoryScoped();

using (var root = builder.Build())
{
var connectionFactory = root.Resolve<Func<IDbConnection>>();

using (var inner = root.CreateInnerContainer())
{
// Despite executin in an inner container, the delegate
factory below
// will resolve to the container that was used to get a
reference
// to the factory. This is because of “closure” over
// registration parameter "c" which points to root container.
var connection = connectionFactory();
}
}

Granted, the above example is also overly simplistic also and may well
provoke a “Why did he do it that way?” For the sake of argument,
let’s just suppose that that’s the way it is.

The above example shows the potentially negative side effect of being
“inside” an inner container but having components resolve through the
outer container. The subtle consequence is that in order to create
components I must hold a direct reference to the container.

A Possible Solution

One possible solution would be to use thread local storage (or
HttpContext.Current.Items) to store a reference to the inner-most
container for the currently executing thread. If Autofac held this
reference internally/privately (on the thread store) it could easily
perform the resolvethrough the inner-most container without
difficulty. Using this technique the following scenario would easily
be possible:

The “connectionFactory” delegate is invoked (see above code) which
then causes the “c.Resolve<IDbConnection>()” to be invoked. During
the current container’s c.Resolve, probably during one of the very
first steps, the thread store is inspected for a reference to an inner
container which, if found, the current container container would then
delegate the request to the inner-most container found.

Afterthoughts

This whole situation comes about because in order to effectively use
an inner container you have to hold a reference to the container. As
we understand it the container shouldn’t be passed around (http://
code.google.com/p/autofac/wiki/BestPractices). This being the case,
it seems a bit of a hack to do all of the setup all possible Units of
Work in the program’s Main method or Global.asax.cs. Furthermore the
“closure” formed around the Func<> callback always references the
container that was used when the Func<> was resolved – regardless of
where the Func<> is invoked.

Jonathan

Nicholas Blumhardt

unread,
Jun 26, 2008, 5:29:48 PM6/26/08
to aut...@googlegroups.com
Hi Jonathan!

Thanks for the detailed read. Took less than one cup of coffee, so I'm not calling it too long :)

The way you're using the connectionFactory inside the inner container is the issue here, but I'm not sure that strikes at the heart of the problem.

Imagine this extra registration:

class SomeCommand
{
  public SomeCommand(Func<IDbConnection> connectionFactory) { .. }

  public void Execute()
  {
    var conn = connectionFactory();
    ...;
  }
}

// ...

builder.Register<SomeCommand>().FactoryScoped();

// ...


using (var root = builder.Build())
{
   var connectionFactory = root.Resolve<Func<IDbConnection>>();

   using (var inner = root.CreateInnerContainer())
   {
       var container = inner.Resolve<SomeCommand>();
       someCommand.Execute();
   }
}


Of course, the connection factory then becomes irrelevant because the container is acting as a factory, so you can simplify to:

class SomeCommand
{
  public SomeCommand(IDbConnection connection) { .. }
  public void Execute() {}
}

I'm sure you've already looked at this kind of approach, but I suppose the essence of the Autofac design is that dependencies are always resolved from the innermost container outwards. You can still resolve dependencies from the other containers in a hierarchy, but you need to use InContext() if you want to share the instances resolved from the outer containers with instances resolved from the inner containers.

You shouldn't explicitly pass objects from the outer to the inner scope - they should be wired directly to comoponents being resolved from the inner container instead.

This isn't what is meant by 'passing the container around' since the container will always need to be accessed at the point where an object graph is being created - the important thing is to keep this isolated to infrastructural code (rather than within application components a-la Service Locator.)

Perhaps a good way to think about it is that there will always be one component at the root of each unit of work (in an MVC app this is most often the Controller.) Once the root object is resolved from the most-nested container within the unit of work, it can satisfy any additional component creation needs through the likes of factories implemented similarly to the Func<>-based one in this example.

Hope this makes sense. Need to start to get more examples and information onto the web :)

Cheers,

Nick

jonathan...@hotmail.com

unread,
Jun 26, 2008, 11:12:49 PM6/26/08
to Autofac
Nick,

This whole issue, as you said, revolves around that root component
responsible for each unit of work. This component would almost
exclusively be an "application layer" component which coordinates the
activities among the various business objects.

What I plan on doing is injecting a simple "proxy" object that exposes
the same interface in place of the real business object. This proxy
object would hold a reference to the inner-most IContainer and when
the method(s) were called it would use the IContainer reference
resolve a reference to the real object, setup an inner container and
corresponding unit of work. Then it would forward the calls to the
real object.

Most likely I would have the "proxy" object hold a reference to the
container along with a string value representing the named instance
(if any) of the service to be resolved. If the service name string
were injected into the proxy object this would avoid any tight
coupling of the proxy object to a particular service.

Thus I can have my cake and eat it too. It appears that the old
saying remains true once more - all problems can be solved by another
layer of indirection.

Jonathan

On Jun 26, 3:29 pm, "Nicholas Blumhardt"
<nicholas.blumha...@gmail.com> wrote:
> Hi Jonathan!
>
> Thanks for the detailed read. Took less than one cup of coffee, so I'm not
> calling it too long :)
>
> The way you're using the connectionFactory inside the inner container is the
> issue here, but I'm not sure that strikes at the heart of the problem.
>
> Imagine this extra registration:
>
> class SomeCommand
> {
>   public SomeCommand(Func<IDbConnection> connectionFactory) { .. }
>
>   public void Execute()
>   {
>     var conn = connectionFactory();
>     ...;
>   }
>
> }
>
> // ...
>
> builder.Register<SomeCommand>().FactoryScoped();
>
> // ...
>
> using (var root = builder.Build())
> {
>    var connectionFactory = root.Resolve<Func<IDbConnection>>();
>
>    using (var inner = root.CreateInnerContainer())
>    {
>        var container = inner.Resolve<SomeCommand>();
>        someCommand.Execute();
>    }
>
> }
>
> Of course, the connection factory then becomes irrelevant because the
> container is acting as a factory, so you can simplify to:
>
> class SomeCommand
> {
>   public SomeCommand(*IDbConnection *connection) { .. }
> > Jonathan- Hide quoted text -
>
> - Show quoted text -
Reply all
Reply to author
Forward
0 new messages