2

I want to replace existing registered instances in Autofac with new ones in ASP.NET MVC application in runtime. Registrations are keyed as I work with collections of instances of different subtype, though it seems to be irrelevant to my issue.

Initial registration on application startup

foreach (var instance in instances)
{
    builder.RegisterInstance(instance).Keyed<IInstance>(InstanceType.A); 
}
IContainer container = builder.Build();    
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Further on, in a controller method I do the following: dispose old instances, obtain new ones, create a new builder, reregister existing components and also register new instances, then update Autofac's ComponentContext

//...dispose old instances, obtain new instances

var builder = new ContainerBuilder();
foreach (var c in _componentContext.ComponentRegistry.Registrations)
{
    builder.RegisterComponent(c);
}
foreach (var instance in newInstances)
{
    builder.RegisterInstance(instance).Keyed<IInstance>(InstanceType.A);
}
builder.Update(_componentContext.ComponentRegistry);

Next time I enter the controller method, in controller constructor the old instances are resolved as IIndex<InstanceType, IInstance[]>, not the new ones. What am I doing wrong?

Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
jjjjj
  • 339
  • 4
  • 11
  • 4
    This feels like a bad idea to me. Why not register an object that can hold and change the references? If you are using your container outside of the [composition root](http://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection) of your application then that is a code smell. – Sam Holder Apr 28 '15 at 15:39
  • 2
    I completely agree with @SamHolder: doing this is a terrible practice. It is complex, error prone, performance heavy, and a maintenance nightmare. If you explain what it is you are trying to accomplish (why are you replacing registrations), we might be able to give some feedback on this and show a better approach. – Steven Apr 28 '15 at 15:52
  • @SamHolder and Steven: Yes, I know the code smells pretty bad, though I was curious why component context update didn't work. Instances are MassTransit service buses, I need to keep object references to be able to close them properly and to get diagnostics data on demand. Upon refresh action, the configuration of buses might change and as a consequence I have to close old buses and start new ones. Anyhow, the best approach seems to be to use a kind of object provider which keeps references to available service buses. – jjjjj Apr 28 '15 at 18:44

1 Answers1

1

Your code doesn't work because componentContext is the context for the current scope and not the global scope. You can look at this .NetFiddle to show some code illustrating the problem : https://dotnetfiddle.net/GNvOL4

If you really want to replace instance it will be simpler to use a provider :

class Program
{
    static void Main(string[] args)
    {
        ContainerBuilder builder = new ContainerBuilder();
        builder.RegisterInstance(new FooProvider(new Foo("a")))
               .As<FooProvider>();
        builder.Register(c => c.Resolve<FooProvider>().Value)
               .ExternallyOwned()
               .Keyed<Foo>(1);

        IContainer container = builder.Build();
        using (ILifetimeScope scope = container.BeginLifetimeScope())
        {
            Do(scope);
        }

        using (ILifetimeScope scope = container.BeginLifetimeScope())
        {
            IComponentContext context = scope.Resolve<IComponentContext>();
            container.Resolve<FooProvider>().Value = new Foo("b");
            Do(scope);
        }

        using (ILifetimeScope scope = container.BeginLifetimeScope())
        {
            Do(scope);
        }
    }

    static void Do(ILifetimeScope scope)
    {
        IIndex<Int32, Foo> index = scope.Resolve<IIndex<Int32, Foo>>();
        Foo foo = index[1];
        Console.WriteLine(foo.Value);
    }
}

public class FooProvider 
{
    public FooProvider(Foo value)
    {
        this._value = value;
    }

    private volatile Foo _value;
    public Foo Value
    {
        get
        {
            return this._value;
        }
    }

    public void ChangeValue(Foo value)
    {
        if(value == null)
        {
            throw new ArgumentNullException("value");
        }
        if(value == this._value)
        {
            return; 
        }

        Foo oldValue = this._value; 
        this._value = value;
        oldValue.Dispose();
    }

    public void Dispose() 
    {
        this._value.Dispose(); 
    }
}
public class Foo : IDisposable
{
    public Foo(String value)
    {
        this._value = value;
    }

    private readonly String _value;
    public String Value
    {
        get
        {
            return this._value;
        }
    }

    public void Dispose()
    {
        // do things
    }
}
Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
  • I wouldn't dare to register an instance of container in container itself (otherwise how do I get it mvc controller constructor injected?), that would be too evil. I thought the idea is to use ComponentContext object as the default lifetime scope of instances registered with ReisterInstance() is Singleton. Object provider does make sense, no doubts, I'll rewrite my code to avoid any container-related manipulations after application startup. – jjjjj Apr 28 '15 at 18:49
  • @jjjjj I'm glad some container abuse if off limits :) – Sam Holder Apr 28 '15 at 18:51
  • @SamHolder no container harmed, at least in live environment :) – jjjjj Apr 28 '15 at 18:58