具有命名参数的Autofac工厂

时间:2020-10-28 03:46:20

标签: autofac

我将AutoFac用作我的DI容器。只要我将所有内容都放在同一个班级,就可以开始学习,但我一直在阅读指导,我不应该将我的IContainer传递给其他班级,所以我一直关注delegate factories处理这个。

在尝试适应这种方法时,我遇到了如何将其与命名属性参数一起使用的问题。如果我在同一堂课,我可以做类似的事情:

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;
  }
}

这对我来说一直很有意义,直到指定的property参数为止。我应该在哪里设置它,以便它包含预先不可用的值,从而在运行时可以正确解析?

当然,如果我根本不能在这里使用委托工厂,那很好,但是尽管有最佳实践,还是将IContainer传递给类的唯一途径是吗?还是我看不到其他途径?

谢谢!

1 个答案:

答案 0 :(得分:1)

TLDR:委托工厂不这样做。

如您所述,the docs say

默认情况下,Autofac按名称将委托的参数与构造函数的参数匹配。如果使用通用Func类型,则Autofac将按类型切换到匹配的参数。

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

这是一个通过及格的单元测试,其中包含一些注释,以帮助澄清:

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与代码脱钩的门:

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