I think my assumption of your objective was way off and it actually seems to me you don't need to mess with ApplicationParts or FeatureProviders at all. To me it appears to be a DI registration issue.
- I started with boilerplate .net core 3.1 MVC application (nothing fancy there)
- then I added two more class library projects:
Common
and Orders
Common.dll
I assumed that your interfaces (I'll just use IDBLogger
for brevity) must be made available to the controller assembly as well as to the main application. So both projects are set to depend on it.
namespace Common
{
public interface IDBLogger
{
void Log(string s);
}
}
Orders.dll
this assembly defines a web api controller:
using Common;
namespace Orders
{
public class Order
{
private readonly IDBLogger _logger;
public Order(IDBLogger logger)
{
_logger = logger;
}
public void GetOrderDetail()
{
_logger.Log("Inside GetOrderDetail");
}
}
}
MVC
as most of the code is stock standard I'd only list files that I changed.
Note, that now I'm relying on default Controller resolver I only need to register my types onto ServiceCollection
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(); // add all controllers as you normally would. I think you don't need to mess with ApplicationParts. but if you do, you are already aware that https://stackoverflow.com/questions/36680933/discovering-generic-controllers-in-asp-net-core is a good example
services.AddScoped<IDBLogger, IdbLogger>();
// load assembly and register with DI
var assembly = Assembly.LoadFrom(Path.Combine("..\\path\\to", "Orders.dll"));
var orderType = assembly.ExportedTypes.First(t => t.Name == "Order");
services.AddScoped(orderType); // this is where we would make our type known to the DI container
var loadedTypesCache = new LoadedTypesCache(); // this step is optional - I chose to leverage the same DI mechanism to avoid having to load assembly in my controller for type definition. you can probably find a better approach at doing this
loadedTypesCache.LoadedTypes.Add("order", orderType);
services.AddSingleton(loadedTypesCache); // singleton seems like a good fit here
}
IdbLogger.cs
using Common;
using System;
namespace Test
{
public class IdbLogger : IDBLogger
{
public void Log(string s)
{
Console.WriteLine(s); // nothing fancy here either
}
}
}
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
public class ValuesController
{
public ValuesController(IServiceProvider serviceProvider, LoadedTypesCache cache)
{
var order = serviceProvider.GetService(cache.LoadedTypes["order"]); // you could load the same assembly here to get the type again, but i opted to inject a dictionary with preloaded type instead
// following two lines are just to call the method. you probably have better way of doing it
var m = cache.LoadedTypes["order"].GetMethod("GetOrderDetail", BindingFlags.Public|BindingFlags.Instance);
m.Invoke(order, new object[] { });
}
[HttpGet]
public IEnumerable<string> Get()
{
return new [] { "value1", "value2" };
}
}
relying on built-in DI container allows us to not think about manually instantiating your dynamically loaded types (and their dependencies as long as they are known to the DI container). You might find that having to use reflection to work with these instances is a bit cumbersome so you might want to explore your options there.
hopefully this is the missing piece