I'm having problems finding proper solution for my problem, namely:
Let's consider workflow:
Plugin can add its own controllers. To properly handle that I had to prepare interface which will provide me types of custom controllers:
interface ICustomControllerProvider
{
IEnumerable<Type> GetControllerTypes();
}
Based on the above my app knows how to integrate specified types as controllers. All controllers are also defined as services, so Autofac deals with their creation, and so...
Problem: I want to avoid specifying custom controller type twice
public PluginControllerProvider : ICustomControllerProvider
{
public IEnumerable<Type> GetControllerTypes()
{
// 1st type specification
// controller types are specified here, so they could be integrated with app
yield return typeof(ControllerX);
yield return typeof(ControllerY);
}
}
public class PluginModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<PluginControllerProvider>().As<ICustomControllerProvider>();
// 2nd type specification
// controllers have to be register in module as well
builder.RegisterType<ControllerX>();
builder.RegisterType<ControllerY>();
}
}
Is there any way how ControllerX
and ControllerY
could be managed by Autofac, where I specified them only in PluginControllerProvider
?
I tried achieving that by providing custom registration source and resolving ICustomControllerProvider
, however I cannot resolve ICustomControllerProvider
based on arguments provided by IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
from IRegistrationSource
Autofac does not offer the ability to inject a list of types registered with Autofac. You'll have to get that yourself by working with the lifetime scope registry.
Before I get into how you might solve this, I should probably note:
ApiExplorer
that was built to do that on top of ASP.NET Core. That structure wouldn't be supported or involved with the DI system because you're querying about the system, not injecting live instances into the system.All that's to say, while I'll answer the question, the way the design here is posed may be something you'd want to look at. What you're doing, trying to involve DI with listing metadata about the app... seems somewhat backwards (you'd feed the DI container based on the list of types, not get the list of types from the DI container).
But let's just go with it. Given:
What I'd do is register all the controllers with some metadata. You need something to be able to locate the controllers in the list of all the types in the container.
builder.RegisterType<ControllerX>().WithMetadata("controller", true);
builder.RegisterType<ControllerY>().WithMetadata("controller", true);
Now in the plugin controller, you need to inject an ILifetimeScope
because you have to query the list of stuff registered. The ILifetimeScope
that gets injected into the controller will be the same scope from which the plugin controller itself was resolved.
You can use the injected scope to query things in the component registry tagged with your metadata.
public class PluginControllerProvider : ICustomControllerProvider
{
private readonly Type[] _controllerTypes;
public PluginController(ILifetimeScope scope)
{
_controllerTypes = scope
.ComponentRegistry
.Registrations
.Where(r => r.Metadata.ContainsKey("controller"))
.Select(r => r.Activator.LimitType)
.ToArray();
}
public IEnumerable<Type> GetControllerTypes()
{
return _controllerTypes;
}
}
Now, disclaimers:
BeginLifetimeScope()
call), you will need a controller provider from that scope or it won't get all the controller types. The provider needs to come from the scope that has all the registrations.AnyConcreteTypeNotAlreadyRegisteredSource
), this won't capture things that come from the registration sources. It'll only capture things that come from direct registrations of a type (or lambda) on a ContainerBuilder
.But it should work.