具有命名参数的 Autofac 工厂

Autofac factories with named parameters

我正在使用 AutoFac 作为我的 DI 容器。只要我将所有内容都保持在相同的 class,我就设置好了,但我一直在阅读指南,我不应该只是将我的 IContainer 传递给其他 class,所以我'我已经寻求 delegate factories 来处理这个问题。

在尝试适应这种方法的同时,我 运行 了解如何将其与命名的 属性 参数一起使用。如果我在同一个 class 中,我可以这样做:

var builder = new ContainerBuilder();
builder.Register(c => new Service()).As<IService>();
builder.Register((c,n) => new Foo(n.Named<string>("name"))).As<IFoo>();
var container = builder.Build();

var foo = container.Resolve<IFoo>(new NamedPropertyParameter("name", "blah"));

但是,如果我根据所看到的指南将其设置为使用工厂,我将如何提供命名的 属性?我知道我会这样设置..

public class Foo : IFoo {
  public delegate Foo Factory(string something);

  IService _service {get;set;}
  string _something {get;set;}

  public Foo(string something, IService service) {
    _service = service;
    _something = something;
  }
}

在命名 属性 参数之前,这对我来说很有意义。我可以在哪里设置它,以便它在运行时正确解析,因为它包含一个事先不可用的值?

当然,如果我只是不能在这里使用委托工厂,那很好,但是尽管有最佳实践,但将 IContainer 传递到 class 是唯一的出路吗?还是有其他我没有看到的前进方向?

谢谢!

TLDR:委托工厂不这样做。

如您所述,the docs say

By default, Autofac matches the parameters of the delegate to the parameters of the constructor by name. If you use the generic Func types, Autofac will switch to matching parameters by type.

这意味着委托签名中的参数名称需要与对象构造函数中的参数匹配(名称和类型)。

这是一个通过的单元测试,其中包含一些有助于澄清的评论:

using System;
using Autofac;
using Xunit;

namespace AutofacRepro
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Foo>();

            // Foo consumes a Bar in its constructor. If it's registered with
            // Autofac, it doesn't need to be in the delegate factory signature
            // - it'll come from the container.
            builder.RegisterType<Bar>();
            var container = builder.Build();

            // You may or may not be resolving from a lifetime scope, but if
            // other places in your app use IDisposable, you should use scopes
            // to avoid memory leaks.
            // https://autofaccn.readthedocs.io/en/latest/best-practices/index.html
            using var scope = container.BeginLifetimeScope();

            // The factory will take just the string parameter for the
            // constructor and the Bar parameter comes from the container.
            var fooFactory = scope.Resolve<Foo.Factory>();
            var foo = fooFactory("value");

            Assert.Equal("value", foo.Something);
            Assert.NotNull(foo.SomethingElse);
        }

        public class Foo
        {
            // The important thing here is that the NAME AND TYPE match. If you
            // switch the parameter NAME here to "Xsomething" then you can't
            // even resolve the factory because the name and type won't match
            // with the real constructor.
            public delegate Foo Factory(string something);

            public string Something { get; }

            public Bar SomethingElse { get; }

            // The string will come from the delegate factory, the Bar will come
            // from the container.
            public Foo(string something, Bar somethingElse)
            {
                Something = something;
                SomethingElse = somethingElse;
            }
        }

        public class Bar
        {
        }
    }
}

那么,为什么这个名字匹配的东西更有趣?

假设您有一个带有如下构造函数的对象:

public class Foo
{
  public Foo(string first, string second, string third){}
}

如果您尝试使用标准 Func<> 关系,它不会起作用:

var builder = new ContainerBuilder();
builder.RegisterType<Foo>();
var container = builder.Build();
using var scope = container.BeginLifetimeScope();

// The Foo object needs three strings, right?
var factory = scope.Resolve<Func<string, string, string, Foo>>();

// Every string parameter that goes to the Foo constructor
// will be "a" even if you passed the right values otherwise
// because the Func<T> relationship matches by TYPE.
var foo = factory("a", "b", "c");

如果您需要能够拥有多个给定类型的参数,这时委托工厂就派上用场了。

不过,你的问题是如何传递一个 命名为 属性 委托工厂不这样做。 委托工厂是关于构造函数参数,而不是属性。 Autofac(和大多数 IoC 容器)确实专注于构造函数注入,而 属性 和方法注入是次要的。如果它实际上是必需的,通常它应该在构造函数中。

如果您确实需要使用特定的 NamedPropertyParameter 来解决并且您无法绕过该设计,您可能不得不使用 ILifetimeScope 并自行解决。

使用 ILifetimeScope 而不是 IContainer 一个 ILifetimeScope 构造函数参数将被注入与对象本身相同的生命周期范围已解决。

public class Foo
{
    public Foo(ILifetimeScope scope)
    {
        // Here's where you could resolve stuff using
        // named property parameters or whatever.
        Property = scope.Resolve<Bar>();
    }

    public Bar Property { get; }
}

那么它可以这样工作:

var builder = new ContainerBuilder();
builder.RegisterType<Foo>();
var container = builder.Build();

// Doesn't matter how many nested scopes you have...
using var scope1 = container.BeginLifetimeScope();
using var scope2 = container.BeginLifetimeScope();
using var scope3 = container.BeginLifetimeScope();

// ... the scope injected in Foo will be scope3 because
// that's where Foo itself came from.
var foo = scope3.Resolve<Foo>();

这不是很漂亮,但是当涉及到必须专门使用命名 属性 参数来解决问题时,没有太多选择。如果您寻找以下设计,您可以打开将 Autofac 与代码分离的大门:

  • 使用构造函数参数
  • 匹配类型而不是名称