如何确定 Autofac 在解析时使用哪个构造函数

How to determine which constructor Autofac uses when resolving

我正在使用自定义 JsonConverter 和 JsonSerializerSettings.TypeNameHandling = TypeNameHandling.Objects 在反序列化期间创建所需的实例。这些实例是通过解析来自 Autofac IOC 容器的类型来创建的。一切正常,除了...

我有几个 "core objects" 从服务请求构造函数中的唯一 ID(已正确注入构造函数)。反序列化时不应发生这种情况,因为它相当昂贵,并且一旦创建实例,ID 就会从 Json 文件中填充。

目前,当从自定义 JsonConverter 中解析时,我使用 _scope.Resolve<T>(new TypedParameter(typeof(IIdService), null)); 然后 - 在被调用的构造函数中 - 检查 null 并采取相应行动。

有些人显然认为使用 IOC 时多个构造函数比代码味道更糟糕(这让我想知道为什么 Autofac 提供了与该主题相关的多个功能),但在反序列化的上下文中,我认为它非常有意义。

据我所知,Autofac 有机制来决定在注册期间使用哪个构造函数,但在解析时没有。我的首选解决方案是将自定义属性添加到构造函数(例如 [CtorForDeserializing])并使用它来决定。这可能吗?

实际上,Autofac 能够决定在注册或解析期间两种方式使用哪个构造函数。对于这里的解决方案部分,引用自文档:“A​​utofac 自动使用您的 class 的构造函数,其中包含能够从容器 获得的最多参数”(see here).

考虑以下示例。

public interface ISomeService
{
    Guid Id { get; }
}

public class SomeService : ISomeService
{
    public Guid Id { get; }

    public SomeService()
    {
        Id = Guid.NewGuid();
    }

    public SomeService(Guid id)
    {
        Id = id;
    }
}

// Startup.cs:
builder.RegisterType<SomeService>().As<ISomeService>().InstancePerLifetimeScope();

// TestController.cs:
[Route("api/[controller]")]
public class TestController : Controller
{
    private readonly IComponentContext _context;

    public TestController(IComponentContext context)
    {
        _context = context;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var service = _context.Resolve<ISomeService>();
        return Ok(service.Id);
    }

    [HttpGet("{id}")]
    public IActionResult Get(Guid id)
    {
        var service = _context.Resolve<ISomeService>(new NamedParameter("id", id));
        return Ok(service.Id);
    }
}

// GET http://localhost:5000/api/test/e0198f72-6337-4880-b608-68935122cdea
// each and every response will be the same: e0198f72-6337-4880-b608-68935122cdea

// GET http://localhost:5000/api/test
// this way it responds with some random guid each time endpoint is called

Autofac 有几个扩展点用于基于反射的激活,但还没有很好的文档记录,可能会对您有所帮助:IConstructorFinderIConstructorSelector

IConstructorFinder 用于定位一个类型上所有可用的构造函数。核心示例是 DefaultConstructorFinder,它仅定位 public 构造函数。例如,如果您想隐藏具有特定属性的构造函数或开始查找 internal/private 构造函数,您可以创建自定义查找器。这实际上只发生一次,因此您不必在此处做出运行时选择。

IConstructorSelector 用于在解析时选择应该使用哪个构造函数来实例化对象。在核心 Autofac 中有几个,但主要示例是 MostParametersConstructorSelector,它选择当时具有最多可用匹配参数的构造函数。 IConstructorFinder 找到构造函数,然后将那组构造函数呈现给 IConstructorSelector 以供选择。这是您可以做出更多运行时选择的地方,因为它会在每次解析对象时发生。

有一些扩展方法可以帮助您将 finder/selector 添加到注册中:

builder.RegisterType<MyType>()
       .FindConstructorsWith(new MyConstructorFinder())
       .UsingConstructor(new MyConstructorSelector());

您不必自定义这两件事,您可以根据需要只做其中之一。我只是向您展示扩展。

Travis Illig 向我介绍了正确的方向 - 谢谢!

我最终围绕以下细节实施了一个解决方案:

实施自定义属性,例如:public class DeserializeCtorAttribute : Attribute { },将由(也将实施)IConstructorFinder.

使用

实现一个空的通用接口,例如:IDeserializable<T>,它将用于解析 services/components。

让相关组件类实现接口(MyClass : IDeserializable<MyClass>)并为组件添加一个额外的注册:

_builder.RegisterType<MyClass>().As<IDeserializable<MyClass>>()
                .FindConstructorsWith(MyConstructorFinder);

MyClass 的所需构造函数中使用已实现的 DeserializeCtorAttribute

JsonConverter通过调用(MyClass) scope.Resolve(IDeserializable<MyClass>)创建所需的实例;铸造是必需的,但安全。由于注册,实例将使用所需的构造函数创建。