在Autofac中,如何通过适配器传播密钥

时间:2014-01-08 02:07:29

标签: autofac

我正在使用Autofac中的adapter support将多种类型转换为所需类型。我还想保留附加到适配器输入类型的键/名称/元数据,以便它们在适配器输出类型上具有相同的值 - 这是使用IIndex<,>按名称解析实例所需的。

我无法弄清楚如何通过适配器函数传播键/名称/元数据,因为适配器函数在组件构造期间运行,并且元数据需要在构建容器时传播。

这是一个示例xunit测试,它失败了:

/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{

public class A
{
    public A(string key)
    {
        Key = key;
    }

    public string Key { get; private set; }
}


public class B
{
    public B(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }
}


public class C : B
{
    public C(string name)
        : base(name)
    {}
}

public class LookerUpper
{
    private readonly IIndex<string, B> _bIndex;

    public LookerUpper(IIndex<string, B> bIndex)
    {
        _bIndex = bIndex;
    }

    public B LookupByName(string name)
    {
        return _bIndex[name];
    }
}

[Fact]
public void TestPropagateKeysThroughAdapters()
{
    var builder = new ContainerBuilder();

    // Register named types
    builder.RegisterType<A>().Named<A>("A").WithParameter("key", "A");
    builder.RegisterType<B>().Named<B>("B").WithParameter("name", "B");
    builder.RegisterType<C>().Named<C>("C").Named<B>("C").WithParameter("name", "C");

    // Adapter to convert an A to a B, since it's not a subclass
    builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));

    // Register LookerUpper, which is the only top-level type that needs to be autowired
    builder.RegisterType<LookerUpper>();

    var container = builder.Build();
    var lookerUpper = container.Resolve<LookerUpper>();

    // Test expected results
    Assert.Equal("A", lookerUpper.LookupByName("A").Name);
    Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B

    Assert.Equal("B", lookerUpper.LookupByName("B").Name);
    Assert.IsType<B>(lookerUpper.LookupByName("B"));

    Assert.Equal("C", lookerUpper.LookupByName("C").Name);
    Assert.IsType<C>(lookerUpper.LookupByName("C"));

    Assert.Throws<ComponentNotRegisteredException>(() => lookerUpper.LookupByName("D"));
}

}

语句lookerUpper.LookupByName("A")失败并带有ComponentNotRegisteredException,因为名称值"A"不会通过适配器功能传播(适应A - > B)。如果前两行Asserts被注释掉,那么测试的其余部分将按预期工作。

2 个答案:

答案 0 :(得分:1)

我通过使用Autofac元数据而不是Autofac键或名称找到了解决此问题的可行解决方案。对于RegisterAdapter<TFrom, TTo>(Func<TFrom,TTo>)的来电,元数据会从IComponentRegistration TFrom传播到IComponentRegistration TTo;但是密钥/名称不会传播。遗漏密钥可能是一个错误或设计,我将用autofac提交一个错误来弄清楚是哪种情况并跟进。

关于使用元数据的不幸部分是我不能使用IIndex<string, B>构造函数参数,因此我必须使用IEnumerable<Meta<Lazy<B>>>参数并创建我自己的字符串字典 - &gt; Lazy<B>IIndex提供类似的功能。这是有效的代码:

/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{
    internal const string LookupKey = "lookup";

    public class A
    {
        public A(string key)
        {
            Key = key;
        }

        public string Key { get; private set; }
    }

    public class B
    {
        public B(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    public class C : B
    {
        public C(string name)
            : base(name)
        {}
    }

    public class LookerUpper
    {
        private readonly IDictionary<string, Lazy<B>> _bLookup;

        public LookerUpper(IEnumerable<Meta<Lazy<B>>> bMetas)
        {
            _bLookup = bMetas.ToDictionary(meta => meta.Metadata[LookupKey].ToString(), meta => meta.Value);
        }

        public B LookupByName(string name)
        {
            return _bLookup[name].Value;
        }
    }

    [Fact]
    public void TestPropagateKeysThroughAdapters()
    {
        var builder = new ContainerBuilder();

        // Register types that will be looked up; attach metadata for the lookup key
        builder.Register((c) => new A("A")).WithMetadata(LookupKey, "A");
        builder.Register((c) => new B("B")).WithMetadata(LookupKey, "B");
        builder.Register((c) => new C("C")).AsSelf().As<B>().WithMetadata(LookupKey, "C");

        // Adapter to convert an A to a B, since it's not a subclass
        builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));

        // Register LookerUpper, which is the only top-level type that needs to be autowired
        builder.RegisterType<LookerUpper>();

        var container = builder.Build();

        var lookerUpper = container.Resolve<LookerUpper>();

        // Test expected results
        Assert.Equal("A", lookerUpper.LookupByName("A").Name);
        Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B

        Assert.Equal("B", lookerUpper.LookupByName("B").Name);
        Assert.IsType<B>(lookerUpper.LookupByName("B"));

        Assert.Equal("C", lookerUpper.LookupByName("C").Name);
        Assert.IsType<C>(lookerUpper.LookupByName("C"));

        Assert.Throws<KeyNotFoundException>(() => lookerUpper.LookupByName("D"));
    }
}

还应该可以创建IRegistrationSource和一些扩展方法来扩展RegisterAdapter<TFrom, TTo>中的内容,以便TFrom中的密钥传播到TTo - 这将是一个理想的解决方案,但可能需要维护更多的工作,所以我可能会坚持这一点。

答案 1 :(得分:1)

它已在Autofac版本3.5.1中修复。

Link to the bug
Link to the fix