如何确定 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 能够决定在注册或解析期间两种方式使用哪个构造函数。对于这里的解决方案部分,引用自文档:“Autofac 自动使用您的 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 有几个扩展点用于基于反射的激活,但还没有很好的文档记录,可能会对您有所帮助:IConstructorFinder
和 IConstructorSelector
。
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>)
创建所需的实例;铸造是必需的,但安全。由于注册,实例将使用所需的构造函数创建。
我正在使用自定义 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 能够决定在注册或解析期间两种方式使用哪个构造函数。对于这里的解决方案部分,引用自文档:“Autofac 自动使用您的 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 有几个扩展点用于基于反射的激活,但还没有很好的文档记录,可能会对您有所帮助:IConstructorFinder
和 IConstructorSelector
。
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>)
创建所需的实例;铸造是必需的,但安全。由于注册,实例将使用所需的构造函数创建。