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

时间:2018-05-25 16:23:08

标签: c# json.net deserialization autofac

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

我有几个"核心对象"从服务请求构造函数中的唯一Id(正确地注入到构造函数中)。当反序列化时不应该发生,因为它相当昂贵,并且一旦创建了实例,Ids就会从Json文件中填充。

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

当使用IOC时,有些人显然认为多个构造函数比代码气味更糟糕(这让我想知道为什么Autofac提供了关于该主题的几个特征),但在反序列化的背景下,我认为它可以完全合理。

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

3 个答案:

答案 0 :(得分:2)

Autofac有几个扩展点可用于基于反射的激活,但尚未有详细记录可能对您有所帮助:IConstructorFinderIConstructorSelector

IConstructorFinder用于定位类型上的所有可用构造函数。核心示例是DefaultConstructorFinder,它只定位公共构造函数。如果您想隐藏具有特定属性的构造函数或者开始查找内部/私有构造函数,您可以创建自定义查找程序。这实际上只发生一次,所以你不能在这里做出运行时选择。

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

有一些扩展方法可以帮助您将查找器/选择器添加到注册中:

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

您不必自定义这两件事,如果需要,您可以单独执行其中一项操作。我只是向您展示扩展程序。

答案 1 :(得分:0)

实际上,Autofac能够决定在注册或解决期间使用哪种构造函数。对于解决方案部分,这里是文档的引用:“ Autofac自动使用您的类的构造函数,其中包含能够从容器中获取的最多参数”(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

答案 2 :(得分:0)

Travis Illig让我朝着正确的方向前进 - 谢谢!

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

实施自定义属性,例如:IConstructorFinder,将由(也将被实施)IDeserializable<T>使用。

实现一个空的通用接口,例如:MyClass : IDeserializable<MyClass>,用于解析服务/组件。

让相关的组件类实现接口(_builder.RegisterType<MyClass>().As<IDeserializable<MyClass>>() .FindConstructorsWith(MyConstructorFinder); )并为组件添加额外的注册:

DeserializeCtorAttribute

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

(MyClass) scope.Resolve(IDeserializable<MyClass>)通过调用<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> img:hover { -webkit-transform: scaleX(-1); transform: scaleX(-1); } </style> </head> <body <img src="Image URl" height="auto" width="auto"> </body> </html> 创建所需的实例;铸造是必需的,但安全。由于注册,将使用所需的构造函数创建实例。