Search code examples
autofac

How to install default IConstructorSelector implementation to be used for each and every registration


So Autofac allows us to select what constructor to use in case the concrete registered type has multiple candidates. It is explained here - https://autofac.readthedocs.io/en/latest/advanced/constructor-selection.html#iconstructorselector

But what if I want to use my custom constructor selector for each and every registration of which I have quite a lot (thousands across several hundreds of Autofac.Module classes)? How can I change the default without explicitly customizing every registration?

Motivation

We have a very large .NET Framework application using MEF as IoC. We want to replace it with Autofac as a preliminary step before migrating to .NET Core.

There is Autofac.Mef, but it does not support Lazy or open generics injection, as per documentation here - https://autofac.readthedocs.io/en/latest/integration/mef.html#known-issues-gotchas

Therefore we will be migrating to Autofac proper. I have already written code that inspects the created MEF container and generates the respective Autofac registration code. But there is a problem - what if Autofac selects a different constructor than MEF? I.e. not the one attributed with [ImportingConstructor]?

Therefore I would like to be able to customize the registration code to respect the attribute, at least for now, in order to reduce the regression risk. Sure, since the code is generated anyway, I can modify each and every registration. But I wonder if there is a better way.


Solution

  • There is really not a great way to do this.

    A lot of the challenge is in the combination of builder syntax/callbacks used as well as the dynamic nature of things Autofac provides with concepts like registration sources.

    When you register things (like RegisterType<T>()) it adds a single callback function to the ContainerBuilder. The overall set of chained-up registration functions gets executed at the end, during Build() It's sort of building a middleware chain of callback-to-callback-to-callback that ends up building a complete object registration. There's no real place to "globally intercept them."

    Introducing something "global" has a few some implications that aren't super-straightforward to address, which is largely why such a thing doesn't exist.

    • You can register new things in child lifetime scopes. Should all of those use the same global constructor selector? What if you want to override the constructor selector just for that lifetime scope? If you override the selector at the lifetime scope, does that also take into account all the registrations that were registered in a parent lifetime scope, or does that now change those parent registrations?
    • How do we handle things like registration sources that dynamically add registrations through, say, assembly scanning? Do those obey the global selector? How do we enforce that in custom sources people write?
    • If we were, instead, to allow middleware to modify the constructor finder or selector at resolve time (which is one possible hook point for something like this), how does that start affecting things like singletons or instance-per-lifetime-scope entities?
    • How do we provide support/observability to help folks in large projects troubleshoot this when it goes wrong? Someone somewhere set a custom constructor selector, someone else doesn't know where that code is, now all the registrations are busted and "Autofac is broken, what'd you Autofac guys do?"

    Slap a quick set of options on the ContainerBuilder is, clearly, a really easy thing to do. It's just that the ripple effect of "change global behavior" brings a ton with it, so we haven't done it.

    That said, if this is something interesting, I'd encourage you to open an issue and propose it. If you do, make sure to include the proposed design for all the little far-reaching parts like I mentioned. The team is small and designing new language features like this requires the community pitch in with the leg-work. (We have an issue for a global IPropertySelector with a similar request, but there's no design, and there's no great way to address it.)

    Addendum: While I can't iterate over solutions, I'll say this:

    Based on the goal to force a particular constructor due to this MEF migration, if it was me, I'd go and...

    1. Find all the objects that have more than just a 0-parameter and multiple-parameter constructor. These will be the ones where you think you need to target some specific thing. It may not be that many. You may be trying to solve a problem where there are only 3 offending classes. If that's the case, register the selector inline. Barring that ..
    2. Either remove the constructors you don't want Autofac to consider or make them internal. In a standard DI system you actually try to avoid having a bunch of constructors specifically to get around this problem of selection.

    I get that may be trivializing the issue, but migrating from MEF isn't zero work, sort of like migrating from WCF to ASP.NET Core isn't zero work.

    But like I said - if it was ME, this is how I'd do it.