How to determine which constructor Autofac uses when resolving

梦想的初衷 提交于 2019-12-09 03:08:29

There are a couple of extension points Autofac has for reflection-based activations but doesn't have well documented yet that may help you out: IConstructorFinder and IConstructorSelector.

IConstructorFinder is used to locate all the available constructors on a type. The core example is the DefaultConstructorFinder which locates only public constructors. If you wanted to, say, hide constructors with particular attributes or start finding internal/private constructors, you could create a custom finder. This really only happens once so you don't get to make runtime choices here.

IConstructorSelector is used to choose, at resolve time, which constructor should be used to instantiate the object. There are a couple of these in core Autofac, but the primary example is the MostParametersConstructorSelector which selects the constructor that has the most available matching parameters at the time. Constructors get found by the IConstructorFinder and then that set of constructors is what is presented to the IConstructorSelector to choose from. This is where you could make more runtime choices since it happens every time the object is resolved.

There are extension methods to help you add your finder/selector to a registration:

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

You don't have to customize both things, you can just do one or the other if you want. I'm just showing you the extensions.

Actually Autofac is able to decide which constructor to use both ways - during registration or resolution. For resolution part here is the quote from documentation: "Autofac automatically uses the constructor for your class with the most parameters that are able to be obtained from the container" (see here).

Consider following example.

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

Travis Illig sent me in the right direction - thanks!

I ended up implementing a solution around the following details:

Implement custom attributes, e.g.: public class DeserializeCtorAttribute : Attribute { }, which will be used by the (also to be implemented) IConstructorFinder.

Implement an empty generic interface, e.g.: IDeserializable<T>, which will be used for resolving the services/components.

Let relevant component classes implement the interface (MyClass : IDeserializable<MyClass>) and add an extra registration for the component:

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

Use the implemented DeserializeCtorAttribute in the desired constructor of MyClass.

Let the JsonConverter create the required instance by calling (MyClass) scope.Resolve(IDeserializable<MyClass>); casting is required, but safe. Due to the registration the instance will be created using the desired constructor.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!