Question

Warning, long post ahead.

I've been thinking a lot about this lately and I'm struggling to find a satisfying solution here. I will be using C# and autofac for the examples.

The problem

IoC is great for constructing large trees of stateless services. I resolve services and pass the data only to the method calls. Great.

Sometimes, I want to pass a data parameter into the constructor of a service. That's what factories are for. Instead of resolving the service I resolve its factory and call create method with the parameter to get my service. Little more work but OK.

From time to time, I want my services to resolve to the same instance within a certain scope. Autofac provides InstancePerLifeTimeScope() which is very handy. It allows me to always resolve to the same instance within an execution sub-tree. Good.

And there are times when I want to combine both approaches. I want data parameter in constructor and have have the instances scoped. I have not found a satisfying way to accomplish this.

Solutions

1. Initialize method

Instead of passing data into the constructor, just pass it to Initialize method.

Interface:

interface IMyService
{
    void Initialize(Data data);
    void DoStuff();
}

Class:

class MyService : IMyService
{
    private Data mData;
    public void Initialize(Data data)
    {
        mData = data;
    }

    public void DoStuff()
    {
        //...
    }
}

Registration:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

Usage:

var myService = context.Resolve<IMyService>();
myService.Init(data);

// somewhere else
var myService = context.Resolve<IMyService>();

After resolving the service for the first time and calling Initialize I can happily resolve within the same context and get the same initialized instance. I don't like the fact that before calling Initialize I have an unusable object. There is a danger that the instance will be resolved and used somewhere else before I call Initialize().

2. Holder pattern

This is a pattern that holds a reference to the data object and instead of injecting the data object itself I inject the holder object.

Interface:

interface IMyService
{
    void DoStuff();
}

Class:

class MyService : IMyService
{
    private Data mData;
    public MyService(IDataHolder dataHolder)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}

Registration:

builder.RegisterType<MyService>().As<IMyService>();
builder.RegisterType<DataHolder>().As<IDataHolder>().InstancePerLifetimeScope();

Usage:

var holder = context.Resolve<IDataHolder>();
holder.Data = data;

// somewhere else
var myService = context.Resolve<IMyService>();

This is a little bit better as I moved the responsibility of holding an instance to a different class. I can now use the holder in other services too. Other advantage is that I can hot swap data in holder if necessary. I don't like the fact that it obfuscates the code and adds another interface I have to mock during testing.

3. Let container hold the instance

Interface:

interface IMyService
{
    void DoStuff();
}

Class:

class MyService : IMyService
{
    private Data mData;
    public MyService(Data data)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}

Registration:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

Usage:

var myServiceFactory = context.Resolve<Func<Data, IMyService>>();
myServiceFactory(data);

// somewhere else
var myService = context.Resolve<IMyService>();

That's right. I don't store the result of a factory call anywhere, because autofac stores it for me. This is pretty surprising to anybody who will read the code. I'm not sure if autofac was even meant to be used like this. Nice thing about this is that I need neither an extra initialize method nor extra class for holding instance.

Question

What is your take on this? How do you handle a situation with run-time data parameters and lifetime scoping? Am I missing a better approach?

Was it helpful?

Solution

Autofac now supports this out of the box with an extension to the lifetime scopes. The BeginLifetimeScope() method has an overload that takes an Action<ContainerBuilder> that allows for adding new registrations specific to only that lifetime scope. So for the given example it would look something like:

var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope(
  builder =>
  {
    builder.RegisterInstance(new Data(....));
  }))
{
  // References to 'IMyService' will always be resolved to the same instance within this lifetime scop
  // References to 'Data' will be resolved to the instance registered just for this lifetime scope.
  var svc = scope.Resolve<IMyService>();
}

OTHER TIPS

Most of the time, runtime data is the non static info you need to pass in any process, like x in a math function, so the easiest way to deal with it is using a parameter in the function:

class MyService : IMyService
{
    public MyService(){}

    public void DoStuff(Data mData)
    {
        //...
    }
}

var myService = context.Resolve<IMyService>();
myService.DoStuff(data);

But, assuming your example is just a example and you are asking because your class need to keep runtime data to run more processes and you don't wanna to pass the same argument in every function:

1.- If you don't loose the scope of the runtime data in every Resolve you can resolve with TypedParameter:

Ej:

//initilization
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

//any point of your app
Data mData = new Data("runtimeData"); // must to be accesible in every place you Resolve

using(var scope = container.BeginLifetimeScope())
{

  var service = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData));
service.DoStuff();
}

using(var scope = container.BeginLifetimeScope())
{

  var service2 = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData));
service2.DoStuff();
}

2.- If you don't have a reference to runtime data in every place you are resolving you can RegisterInstance when and where you create runtime data. Autofac should inyect mData instance thanks to Direct Depency Policy

//initilization
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

//where you create or modify runtime data. When runtime data changes you have to update the container again.
var mData = new Data("runtimeData");
updatedBuilder= new ContainerBuilder();
updatedBuilder.RegisterInstance(mData).As<Data>
updatedBuilder.Update(builder);

//in any point of your app
using(var scope = updatedBuilder.BeginLifetimeScope())
    {

      var service = scope.Resolve<IMyService>();
    service.DoStuff();
    }

//in any other point of your app
    using(var scope = updatedBuilder.BeginLifetimeScope())
    {

      var service2 = scope.Resolve<IMyService>();
    service2.DoStuff();
    }

My take on this is that you've done about as good as you can do. The only niggle that I have about it is that Autofac doesn't really do a great job of helping you manage those lifetime scopes, so you're stuck calling their BeginLifetimeScope somewhere. And they can be nested. Mind Blown.

Ninject, on the other hand, does some really cool stuff that doesn't require turning your brain inside-out. Their named scope extension makes it possible for you to create a (gasp) named scope and bind the lifetime of objects within that scope. If you are using factories (clearly you are, judging from the question) you'll also want to use the context preservation extension, so that stuff activated out of factories gets the lifetime management from the named scope that the factory was activated within. Bindings wind up looking something like this:

var scopeName = "Your Name Here";

Bind<TopLevelObject>().ToSelf().DefinesNamedScope(ScopeName);
Bind<ISomeScopedService>().To<SomeScopedService>().InNamedScope(ScopeName);
// A minor bit of gymnastics here for factory-activated types to use
//  the context-preservation extension.
Bind<FactoryActivatedType>().ToSelf().InNamedScope(ScopeName);
Bind<IFactoryActivatedType>().ToMethod(x => x.ContextPreservingGet<FactoryActivatedType>());

The nice part about this is that the scope of those bindings is specifically tied to the named scope rather than just being tied to whatever the nearest lifetime scope up the chain is. IMHO, it makes the lifetimes of those objects much more predictable.

Many IoC frameworks support registration of a factory function (or lambda expression), that takes as one of its arguments an instance of the container / scope / resolution context itself.

This allows using additional levels of indirection, as well as the use of information that uniquely identifies the context or scope. Additionally many provide hooks, like event handlers or the option to derive from a life cycle scope class, to interact with a scope being started or ended.

Principle

For AutoFac and your specific example, the following principle would work, using additional levels of indirection in registration.

// Inject `Data` instance resolved from current scope.
builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>()));

// Extra level of indirection, get a "factory" for a 'Data' instance.
builder.Register<Data>(ctx => ctx.Resolve<Func<Data>>()()).InstancePerLifetimeScope();

// The indirection resolves to a map of scopes to "factory" functions. 
builder.Register<Func<Data>>(ScopedDataExtensions.GetFactory);

We can use any available unique property on a context / scope to construct this mapping.

// Maps scopes to data "factories".
public static class ScopedDataExtensions
{
    private static readonly ConcurrentDictionary<object, Func<Data>> _factories = new ConcurrentDictionary<object, Fund<Data>>();

    public static Func<Data> GetFactory(this IComponentContext ctx) 
    {
        var factory = default(Func<Data>);
        return _factories.TryGetValue(ctx.ComponentRegistry, out factory) ? factory : () => null;
    }
    public static void SetFactory(this ILifetimeScope scope, Func<Data> factory)
    {
        _factories[scope.ComponentRegistry] = factory;
    }
}

We can use it like this to supply "local" data instances to be injected into our scoped service instances.

var myData = new Data("nested");
nestedScope.SetFactory(() => myData);
// ...
var myService = nestedScope.Resolve<IMyService>();

A more complete and generic example for AutoFac follows below.

Generic extension class for this pattern

public static class AutofacScopeExtensions
{
    // Map from context => factories per type
    public static readonly ConcurrentDictionary<object, ConcurrentDictionary<Type, object>> _factories =
        new ConcurrentDictionary<object, ConcurrentDictionary<Type, object>>();

    private static class ScopedFactoryFor<T>
    {
        public static Func<T> DefaultFactory = () => default(T);
        public static Func<T> GetFactory(ConcurrentDictionary<Type, object> fromContext)
        {
            object factory;
            return (fromContext.TryGetValue(typeof(T), out factory)) ? (Func<T>)factory : DefaultFactory;
        }
    }

    public static IRegistrationBuilder<T, SimpleActivatorData, SingleRegistrationStyle> 
        WithContextFactoryFor<T>(this ContainerBuilder builder, Func<T> defaultFactory = null)
    {
        if (defaultFactory != null)
            ScopedFactoryFor<T>.DefaultFactory = defaultFactory;
        builder.Register<Func<T>>(AutofacScopeExtensions.GetFactory<T>);
        return builder.Register<T>(ctx => ctx.Resolve<Func<T>>()());
    }
    public static IContainer BuildContainer(this ContainerBuilder builder)
    {
        var container = builder.Build();
        container.ChildLifetimeScopeBeginning += OnScopeStarting;
        return container;
    }
    public static ILifetimeScope SetScopeFactory<T>(this ILifetimeScope scope, Func<T> factory)
    {
        ScopeMapFor(scope)[typeof(T)] = factory;
        return scope;
    }
    public static ILifetimeScope SetScopeValue<T>(this ILifetimeScope scope, T instance)
    {
        return SetScopeFactory(scope, () => instance);
    }
    public static Func<T> GetFactory<T>(IComponentContext ctx)
    {
        return ScopedFactoryFor<T>.GetFactory(ScopeMapFor(ctx));
    }

    private static ConcurrentDictionary<Type, object> ScopeMapFor(IComponentContext ctx)
    {
        return _factories.GetOrAdd(ctx.ComponentRegistry, x => new ConcurrentDictionary<Type, object>());
    }
    private static void OnScopeStarting(object sender, LifetimeScopeBeginningEventArgs evt)
    {
        evt.LifetimeScope.ChildLifetimeScopeBeginning += OnScopeStarting;
        evt.LifetimeScope.CurrentScopeEnding += OnScopeEnding; // so we can do clean up.
    }
    private static void OnScopeEnding(object sender, LifetimeScopeEndingEventArgs evt)
    {
        var map = default(ConcurrentDictionary<Type, object>);
        if (_factories.TryRemove(evt.LifetimeScope.ComponentRegistry, out map))
            map.Clear();
    }
}

Allowing the following syntax for registration:

builder.WithContextFactoryFor<Data>(() => new Data("Default")).InstancePerLifetimeScope();
builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>()));

And resolve like:

// ...
var myData = new Data("Some scope");
// ...
context.SetScopeFactory(() => myData);

// ...

// Will inject 'myData' instance.
var myService = context.Resolve<IMyService>();

Simpler Alternative

If you explicitly start nested scopes and at the time you do, you know how the scoped Data instance is to be created, you can skip the extension class and register the "factory" delegate with the nested scope when you create it:

var nestedScope = container.BeginLifetimeScope(
    "L2", 
    x => x.RegisterInstance<Func<Data>>(() => new Data("nested")));

If I understand you correctly you want to use factories by delegating object creation to container while passing some parameters to its constructor.

This is implemented in Castle Windsor with typed factory facility.

Example classes we want to resolve:

public interface IMyService
{
    void Do();
}

public class MyService : IMyService
{
    private readonly Data _data;
    private readonly IDependency _dependency;

    public MyService(Data data, IDependency dependency)
    {
        _data = data;
        _dependency = dependency;
    }

    public void Do()
    {
        throw new System.NotImplementedException();
    }
}

public class Data
{    
}

public interface IDependency
{         
}

public class Dependency : IDependency
{
}

We create a factory interface:

public interface IMyServiceFactory
{
    IMyService Create(Data data);
    void Release(IMyService service);
}

We won't be implementing this interface because Castle Windsor will be generating an implementation with Dynamic Proxy. There's an important detail here: parameter name(data) in factory method and the one in the constructor should match.

Then we do the registration and try to resolve the values.

[Test]
public void ResolveByFactory()
{
    WindsorContainer container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IMyServiceFactory>().AsFactory());
    container.Register(Component.For<IMyService>().ImplementedBy<MyService>().LifestyleScoped());
    container.Register(Component.For<IDependency>().ImplementedBy<Dependency>().LifestyleScoped());

    IMyServiceFactory factory = container.Resolve<IMyServiceFactory>();

    IMyService myService1;
    IMyService myService2;

    using (container.BeginScope())
    {
        myService1 = factory.Create(new Data());
        myService2 = factory.Create(new Data());

        myService1.Should().BeSameAs(myService2);
    }

    using (container.BeginScope())
    {
        IMyService myService3 = factory.Create(new Data());

        myService3.Should().NotBeSameAs(myService1);
        myService3.Should().NotBeSameAs(myService2);
    }
} 

You will see that the object created in the same scope are the same references. Let me know if this is the behaviour you want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top