2

My problem is that I want to use Func<> factory to resolve dependency. And in if I use ContainerBuilder Update() (I need it for mocking some services in integration tests), this factories still resolve outdated instances.

I created simple scenario to reproduce the problem:

class Program
{
    static void Main(string[] args)
    {
        var containerBuilder = new ContainerBuilder();
        containerBuilder.RegisterType<Test>().As<ITest>();
        containerBuilder.RegisterType<Test1Factory>().As<ITestFactory>();
        containerBuilder.RegisterType<TestConsumer>().AsSelf();
        var container = containerBuilder.Build();

        var tc1 = container.Resolve<TestConsumer>();

        var cbupdater = new ContainerBuilder();
        cbupdater.RegisterType<Test2>().As<ITest>();
        cbupdater.RegisterType<Test2Factory>().As<ITestFactory>();
        cbupdater.Update(container);

        var tc2 = container.Resolve<TestConsumer>();

        Console.ReadLine();
    }
}

public interface ITest
{
    int Id { get; set; }
}

public class Test : ITest
{
    public Test()
    {
        Id = 1;
    }

    public int Id { get; set; }
}

public class Test2 : ITest
{
    public Test2()
    {
        Id = 2;
    }

    public int Id { get; set; }
}

public interface ITestFactory
{
    ITest Create();
}

public class Test1Factory : ITestFactory
{
    public ITest Create()
    {
        return new Test();
    }
}

public class Test2Factory : ITestFactory
{
    public ITest Create()
    {
        return new Test2();
    }
}

public class TestConsumer
{
    public TestConsumer(Func<ITest> testFactory, ITest test, ITestFactory customFactory)
    {
        Console.WriteLine("factory: " + testFactory().Id);
        Console.WriteLine("direct: " + test.Id);
        Console.WriteLine("MyCustomFactory: " + customFactory.Create().Id);
        Console.WriteLine("*************");
        Console.WriteLine();
    }
}

The output is:

factory: 1 direct: 1 MyCustomFactory: 1


factory: 1 direct: 2 MyCustomFactory: 2


Notice "factory: 1" in both cases.

Am I missing something or I have to create my cusom factory in this scenario?

P.S.

Autofac 3.5.2 or 4.0 beta 8-157 .net 4.5.1

sharkbait
  • 2,980
  • 16
  • 51
  • 89
Vladimir
  • 21
  • 2

1 Answers1

0

That's by design unfortunately, the reasons, I don't know. Looking at the Autofac code gives you a better insight on how they register items with the same interface definition, in short, all registrations are maintained but the last registration wins (ref). Wait...that's not all, weirdly, for Fun<...>, you actually get them in order. You can easily test by changing the constructor of the TestConsumer class to:

public TestConsumer(Func<ITest> testFactory, IEnumerable<Func<ITest>> testFactories, IEnumerable<ITest> tests, ITest test, ITestFactory customFactory)
{
    // ...
}

Note that you get all the Funcs and the ITest registration. You are simply lucky that resolving ITest directly resolves to Test2.

Now, having said all of the above, there is a way described here. You have to create a container without the registration you want to override, therefore:

/// <summary>
/// This has not been tested with all your requirements
/// </summary>
private static IContainer RemoveOldComponents(IContainer container)
{
    var builder = new ContainerBuilder();
    var components = container.ComponentRegistry.Registrations
                        .Where(cr => cr.Activator.LimitType != typeof(LifetimeScope))
                        .Where(cr => cr.Activator.LimitType != typeof(Func<ITest>));
    foreach (var c in components)
    {
        builder.RegisterComponent(c);
    }

    foreach (var source in container.ComponentRegistry.Sources)
    {
        builder.RegisterSource(source);
    }
    return builder.Build();
}

And you can simply change your main method to the following:

static void Main(string[] args)
{
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterType<Test>().As<ITest>();
    containerBuilder.RegisterType<Test1Factory>().As<ITestFactory>();
    containerBuilder.RegisterType<TestConsumer>().AsSelf();
    var container = containerBuilder.Build();

    var tc1 = container.Resolve<TestConsumer>();

    container = RemoveOldComponents(container);

    var cbupdater = new ContainerBuilder();
    cbupdater.RegisterType<Test2>().As<ITest>();
    cbupdater.RegisterType<Test2Factory>().As<ITestFactory>();
    cbupdater.Update(container);

    var tc2 = container.Resolve<TestConsumer>();

    Console.ReadLine();
}

PS: Wouldn't it be great to have a method which does the exact opposite of PreserveExistingDefaults()

Community
  • 1
  • 1
Ruskin
  • 1,504
  • 13
  • 25