Simple Injector plugins - c#

I am building a system in .NET 4.5 which will have different implementations (i.e. implemented on premise at different customers). Each customer will have its own infrastructure and database structure, hence I am building the system heavily relying on the onion architecture, in itself relying on interfaces and DI. In that way, I can use customer specific "Repository" and "Service" implementations.
My aim is to, without recompiling, be able to install the system on a customers server (the system entry point is basically a Windows service containing business logic periodically fired, and also hosting WCF services). For that to work, what I have in mind is some sort of "Dependencies" or "Plugins" folder as a subfolder of the folder containing the Windows service executable, which would contain a customer specific DLL which has concrete classes implementing all the necessary interfaces on which the application relies.
I'm trying to achieve this with Simple Injector. I have had a look at the SimpleInjector.Packaging assembly, and also at the paragraph about "Registering plugins dynamically" here, but I'm still kind of stuck and don't know where to start, like what should I define in which assembly.
I'm in need of some concrete sample on how to achieve this.
Is the SimpleInjector Packaging assembly to be used for this purpose, or am I seeing this wrong ? If so, how ?
Anybody please enlighten me.
Thanks
ps: to be 100% clear: the interfaces and concrete implementations are obviously separated into different assemblies. This question is about how to wire all things up dynamically using Simple Injector.

The beauty of doing this in combination with an IoC Container such as Simple Injector is that it is so easy to add common logic to all of the plug-ins. I recently wrote a bulk image converter utility that allowed plugging in new image converters.
This is the interface
public interface IImageConverter : IDisposable
{
string Name { get; }
string DefaultSourceFileExtension { get; }
string DefaultTargetFileExtension { get; }
string[] SourceFileExtensions { get; }
string[] TargetFileExtensions { get; }
void Convert(ImageDetail image);
}
Registration is done like this (note the error checking for plug-in dependencies that are not .NET assemblies)
private void RegisterImageConverters(Container container)
{
var pluginAssemblies = this.LoadAssemblies(this.settings.PluginDirectory);
var pluginTypes =
from dll in pluginAssemblies
from type in dll.GetExportedTypes()
where typeof(IImageConverter).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
select type;
container.RegisterAll<IImageConverter>(pluginTypes);
}
private IEnumerable<Assembly> LoadAssemblies(string folder)
{
IEnumerable<string> dlls =
from file in new DirectoryInfo(folder).GetFiles()
where file.Extension == ".dll"
select file.FullName;
IList<Assembly> assemblies = new List<Assembly>();
foreach (string dll in dlls) {
try {
assemblies.Add(Assembly.LoadFile(dll));
}
catch { }
}
return assemblies;
}
Common logic required for all plug-in operations is handled by decorators
container.RegisterDecorator(
typeof(IImageConverter),
typeof(ImageConverterChecksumDecorator));
container.RegisterDecorator(
typeof(IImageConverter),
typeof(ImageConverterDeleteAndRecycleDecorator));
container.RegisterDecorator(
typeof(IImageConverter),
typeof(ImageConverterUpdateDatabaseDecorator));
container.RegisterDecorator(
typeof(IImageConverter),
typeof(ImageConverterLoggingDecorator));
This final class is more specific but I have added it as an example of how you can find the required plug-in without taking a direct dependency on the container
public sealed class PluginManager : IPluginManager
{
private readonly IEnumerable<IImageConverter> converters;
public PluginManager(IEnumerable<IImageConverter> converters) {
this.converters = converters;
}
public IList<IImageConverter> List() {
return this.converters.ToList();
}
public IList<IImageConverter> FindBySource(string extension) {
IEnumerable<IImageConverter> converters = this.converters
.Where(x =>
x.SourceFileExtensions.Contains(extension));
return converters.ToList();
}
public IList<IImageConverter> FindByTarget(string extension) {
IEnumerable<IImageConverter> converters = this.converters
.Where(x =>
x.TargetFileExtensions.Contains(extension));
return converters.ToList();
}
public IList<IImageConverter> Find(
string sourceFileExtension,
string targetFileExtension)
{
IEnumerable<IImageConverter> converter = this.converters
.Where(x =>
x.SourceFileExtensions.Contains(sourceFileExtension) &&
x.TargetFileExtensions.Contains(targetFileExtension));
return converter.ToList();
}
public IImageConverter Find(string name) {
IEnumerable<IImageConverter> converter = this.converters
.Where(x => x.Name == name);
return converter.SingleOrDefault();
}
}
You register IPluginManager in the container and Simple Injector does the rest:
container.Register<IPluginManager, PluginManager>();
I hope this helps.

What you are probably looking for is this:
public class TypeLoader<T> : List<T>
{
public const BindingFlags ConstructorSearch =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance |
BindingFlags.Instance;
private void Load(params Assembly[] assemblies)
{
foreach (
Type t in
assemblies.SelectMany(
asm =>
asm.GetTypes()
.Where(t => t.IsSubclassOf(typeof (T)) || t.GetInterfaces().Any(i => i == typeof (T)))))
{
Add((T) Activator.CreateInstance(t, true));
}
}
}
All you need to do is call Assembly.ReflectionOnlyLoad the assemblies from your Plugins directory and pass them to this method.
If you, for example declare IPlugin in all your plugin classes in your assemblies, you'd use it like new TypeLoader<IPlugin>().Load(assemblies); and you'll end up with a neat list of all your objects that implement IPlugin.

You need probe the plugin directory at startup and use the .NET reflection API to get the repository types from the dynamically loaded assemblies. There are many ways to do this, depending on what you exactly need. There's no API for this in Simple Injector, simply because there are a lot of ways to do this, and writing a few custom LINQ statements is often much more readable, and flexible.
Here's an example of how this might look:
// Find all repository abstractions
var repositoryAbstractions = (
from type in typeof(ICustomerRepository).Assembly.GetExportedTypes()
where type.IsInterface
where type.Name.EndsWith("Repository")
select type)
.ToArray();
string pluginDirectory =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
// Load all plugin assemblies
var pluginAssemblies =
from file in new DirectoryInfo(pluginDirectory).GetFiles()
where file.Extension.ToLower() == ".dll"
select Assembly.LoadFile(file.FullName);
// Find all repository abstractions
var repositoryImplementationTypes =
from assembly in pluginAssemblies
from type in assembly.GetExportedTypes()
where repositoryAbstractions.Any(r => r.IsAssignableFrom(type))
where !type.IsAbstract
where !type.IsGenericTypeDefinition
select type;
// Register all found repositories.
foreach (var type in repositoryImplementationTypes)
{
var abstraction = repositoryAbstractions.Single(r => r.IsAssignableFrom(type));
container.Register(abstraction, type);
}
The code above is a variation of the code sample in the Registering plugins dynamically wiki page from the Simple Injector documentation.

Related

Injecting dependencies into dynamically loaded .dll (.net core)

Similar to How to Inject Dependencies to Dynamically Loaded Assemblies, I want to inject dependencies into classes from dynamically loaded assemblies. Is this possible with the .NET 6.0 DI Container? If so, how? If not, is there a light-weight IOC container that can you might recommend? (Not adding a 2nd IOC system to the project would be preferred.)
(Note: there will be only 2-4 maximum possible dependencies to inject, so a fake injection system with if/switch statements could be acceptable.)
One challenge: ILogger<> typically expects a type, but the loading .dll has no compile-time knowledge of the types in the dynamically loaded assemblies, and vice-versa. I could use the non-generic ILogger interface, but am not sure if that works with DI.
EDIT:
Expanding example, as requested:
Given: All potential dependencies to inject come from the Microsoft.Extensions.Hosting nuget package. The two we initially expect to use are ILogging<> and IConfiguration.
Type desiredClass = <Type found in the dynamically loaded assembly>;
//The below line does not inject dependencies. I am trying to find out what will.
object classInstace = Activator.CreateInstance(desiredClass);
MethodInfo selectedMethod = desiredClass.GetMethods
.Single(m=>m.Name=="Execute" && m.GetParameters().All(p=>p.IsOptional));
//Schedule the method in HangFire
RecurringJob.AddOrUpdate(
() => selectedMethod!.Invoke(
ClassInstance,
Array.Empty<object?>(),
scheduleForThisTask);
I found a very ugly way to get this done, and very much hope there is a cleaner way to accomplish this. (Please let there be a built-in way to do this that I have missed.)
Create an extension method GetInjectedObject for IService Provider:
public static object GetInjectedObject(this IServiceProvider serviceProvider, Type type)
{
//Dependency injection the ugly way
//Find the constructor that asks for the most injected parameters
var constructor = type.GetConstructors().Where(cn =>
cn.GetParameters().All(par => serviceProvider.GetServices(par.ParameterType).Any()))
.OrderByDescending(cn => cn.GetParameters().Length).FirstOrDefault();
if (null == constructor)
throw new Exception($"Type {type.Name} does not have a constructor without non-injectible parameters.");
//Get the needed parameters from the IServiceProvider
var constructorParameters =
constructor.GetParameters().Select(par => serviceProvider.GetService(par.ParameterType)).ToArray();
//Create the object with the parameters
var classInstance = Activator.CreateInstance(type, constructorParameters);
return classInstance;
}
and then create the objects like this:
_classInstance = serviceProvider.GetInjectedObject(Class);
Perhaps I misunderstood the problem, might it work for you to:
Have an assembly shared by the loading assembly (i.e. your Composition Root) and the dynamically loaded assembly? This assembly could contain an interface that dynamically loaded types must implement. (you might already have a shared assembly)
Load the dynamically loaded assembly at startup at the point that you're still wiring the DI Container?
In that case I expect an interface similar to:
namespace MySharedAssembly
{
public interface ITask
{
void Execute(TaskSchedule schedule);
}
public class TaskSchedule { ... }
}
In the dynamically loaded assembly:
namespace MyDynamicAssembly
{
public class HelloWorldTask : ITask
{
public void Execute(TaskSchedule schedule)
{
Console.WriteLine("Hello world!");
}
}
}
By doing so, you can:
Easily find all types in the dynamically loaded assembly by the ITask interface.
Register them by their concrete type in the DI Container.
Resolve them by their concrete type when needed, while their dependencies are injected by the DI Container.
For instance:
string dynamicallyLoadedAssemblyPath = "c:\\...\etc\etc\myAssembly.dll";
// Load plugin assembly
Assembly assembly =
Assembly.Load(AssemblyName.GetAssemblyName(dynamicallyLoadedAssemblyPath));
// Load plugin types
Type plugins = assembly.GetExportedTypes().Where(typeof(ITask).IsAssignableFrom);
// Register plugins in DI Container
foreach (var plugin in plugins)
{
services.AddTransient(plugin, plugin);
}
// Add jobs after container was constructed
IServiceProvider provider = ...
foreach (var plugin in plugins)
{
var scheduleForThisTask = GetSchedule(plugin);
RecurringJob.AddOrUpdate(() =>
{
// It might be important to execute each task in its own scope.
using (var scope = provider.CreateScope())
{
var task = (ITask)scope.ServiceProvider.GetRequiredService(plugin);
task.Execute(scheduleForThisTask);
}
}
}

Execute method inside implemented interface in multiple modular plugins

I am implementing a plugin architecture for a .net core MVC application.
A requirement of each of my plugins is to implement an interface from a centralised core library in which the main mvc application will then call the implemented interface method in each of the plugin modules.
Now I get my list of assemblies and from my .NET Core Startup file call the LoadModuleAssemblies method:
public static void LoadModuleAssemblies(IServiceCollection services, FileInfo[] assemblyList)
{
foreach (var dir in assemblyList)
{
if (dir.Name != CoreLibFile)
{
var asl = new AssemblyLoader(dir.DirectoryName);
var assembly = asl.LoadFromAssemblyPath(dir.FullName);
var implementedList = assembly.GetTypes()
.Where(x => x.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IModule))).ToList();
if (implementedList.Any())
{
services.AddMvc()
.AddApplicationPart(assembly)
.AddControllersAsServices();
//Unsure how to load IModule implemented interface and call GetProperties method in the interface.
}
}
}
}
My Interface looks like this:
public interface IModule
{
ModuleProperties GetProperties();
List<string> GetViews();
}
I want to be able to call the GetProperties method in the implemented IModule for each of my plugins.
How can I do this?
You need to use Reflection to create instances of the plugin, and then call its GetProperties method
if (implementedList.Any())
{
services.AddMvc()
.AddApplicationPart(assembly)
.AddControllersAsServices();
foreach(var type in implementedList)
{
var module = Activator.CreateInstance(type) as IModule;
var properties = module.GetProperties();
var views = module. GetViews();
//make use of the properties and views...
}
}

How do I selectively change lifetime scope during Autofac assembly scanning and registration?

I have a class that uses AutoFac to build a container from the assemblies in the binary folder. This essentially iterates over the dll's and registers classes with an interface:
private static void RegisterAssembly(ContainerBuilder builder, string assemblyNamePattern)
{
// Get the path to the currently executing assembly. We will load dll's from here only.
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (String.IsNullOrWhiteSpace(path))
return;
// Get a list of all the assemblies in this folder
var assembliesToRegister = (Directory.EnumerateFiles(path, assemblyNamePattern, SearchOption.TopDirectoryOnly)
.Select(Assembly.LoadFrom)
.ToArray());
// Register the dll's
builder.RegisterAssemblyTypes(assembliesToRegister).AsImplementedInterfaces();
}
That's working fine and I can create instances of the classes I need. The problem I have is that there are couple of these classes that are singletons (Redis caching for example), so need to be registered as such. In code, they're defined using the Lazy<>:
private static readonly Lazy<CacheManager> _instance = new Lazy<CacheManager>(() => new CacheManager());
public static ICacheManager Instance
{
get { return _instance.Value; }
}
What I am wondering is whether there is some way to tell AutoFac that certain classes need to be registered as singletons. I'm guessing I can go through the container after it's been built and change specific definitions to be singletons, but it would be far more reliable if I could get Autofac to do it automatically at registration.
As a cheap solution, when the DLL's are scanned, you can exclude one or more interfaces:
// Register the dll's, except the singleton ones.
builder.RegisterAssemblyTypes(assembliesToRegister)
.AsImplementedInterfaces()
.Except<IOne>()
.Except<ITwo>();
This allows for everything except the singletons to be registered automatically. Next, register the singletons:
AddSingletonInstance<IOne>(builder, assembliesToRegister, "OneType");
AddSingletonInstance<ITwo>(builder, assembliesToRegister, "TwoType");
This relies on a generic method that scans the assembly list created earlier:
private static void AddSingletonInstance<T>(ContainerBuilder builder, Assembly[] assembliesToRegister, string typeName)
{
var singletonType = (from asm in assembliesToRegister
from tt in asm.DefinedTypes
where tt.Name == typeName
select tt).FirstOrDefault();
if (singletonType != null)
builder.RegisterType(singletonType).As<T>().SingleInstance().ExternallyOwned();
}
It's not ideal, but it works.

Unable to resolve a controller that was loaded from external dll

I am building a Web API using MVC4 Web API with an IoC container (Simple Injector in this case, but I don't think this problem is related to that container) that should expose a variety of CRUD and query operations. The reason for using IOC in my case is that we are a dev shop and I need to be able to let customers build their own web API controllers to expose the data they need to expose need from our system. Consequently, I was hoping to design my solution in a way that allowed me to dogfood my own product by making all the controllers, both ours and our customers', external and loadable through IOC.
The website does not have any reference to the library but the library contains controllers that I want to use in the website. The types are registered in the container and the DependencyResolver is set to the custom dependency resolver. I have the code finding the dll plugin and loading the controller type but when I try to navigate to the route that it would represent it says it can't find it.
i.e. if I try to navigate to /api/Test1Api I should see the text "hello world"
My problem here is that although I have loaded my controller type, I am unable to translate that into a route that the website says is there.
I get the error
No HTTP resource was found that matches the request URI
No type was found that matches the controller named 'Test1Api'.
Here is how I register the container
public static class SimpleInjectorInitializer
{
/// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
public static void Initialize()
{
//// Did you know the container can diagnose your configuration? Go to: http://bit.ly/YE8OJj.
// Create the IOC container.
var container = new Container();
InitializeContainer(container);
container.RegisterMvcAttributeFilterProvider();
// Verify the container configuration
container.Verify();
// Register the dependency resolver.
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
}
private static void InitializeContainer(Container container)
{
var appPath = AppDomain.CurrentDomain.BaseDirectory;
string[] files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories);
var assemblies = files.Select(Assembly.LoadFile);
// register Web API controllers
var apiControllerTypes =
from assembly in assemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IHttpController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select type;
// register MVC controllers
var mvcControllerTypes =
from assembly in assemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select type;
foreach (var controllerType in apiControllerTypes)
{
container.Register(controllerType);
}
foreach (var controllerType in mvcControllerTypes)
{
container.Register(controllerType);
}
}
}
Any help is appreciated.
One big warning about your solution. It is quite crucial to register your controller into your container as well (this advice holds for all DI frameworks, although some frameworks by default force you to register concrete types as well). Otherwise you will certainly get bitten by the same problem as this developer had.
Since the IHttpControllerTypeResolver makes use of the IAssembliesResolver, the simplest (and safest) solution is to simply ask the IHttpControllerTypeResolver for all controls to register. Your SimpleInjectorInitializer in that case will look like this:
public static class SimpleInjectorInitializer
{
public static void Initialize()
{
// Create the IOC container.
var container = new Container();
InitializeContainer(container);
container.RegisterMvcAttributeFilterProvider();
// Verify the container configuration
container.Verify();
// Register the dependency resolver.
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
}
private static void InitializeContainer(Container container)
{
GlobalConfiguration.Configuration.Services
.Replace(typeof(IAssembliesResolver),
new CustomAssembliesResolver());
var services = GlobalConfiguration.Configuration.Services;
var controllerTypes = services.GetHttpControllerTypeResolver()
.GetControllerTypes(services.GetAssembliesResolver());
// register Web API controllers (important!)
foreach (var controllerType in controllerTypes)
{
container.Register(controllerType);
}
}
}
Also note that your CustomAssembliesResolver can be made considerably easier:
public class CustomAssembliesResolver
: DefaultAssembliesResolver
{
private Assembly[] plugins = (
from file in Directory.GetFiles(
appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories)
let assembly = Assembly.LoadFile(file)
select assembly)
.ToArray();
public override ICollection<Assembly> GetAssemblies()
{
var appPath =
AppDomain.CurrentDomain.BaseDirectory;
return base.GetAssemblies()
.Union(this.plugins).ToArray();
}
}
I figured it out!
So I created a new class in my Web Api call CustomAssembliesResolver that inherits from DefaultAssembliesResolver. Essentially I add my assembly to the list of assemblies that are parsed when looking for controllers. I still have the code that uses Simple Injector for the DI portion of the solution.
public class CustomAssembliesResolver : DefaultAssembliesResolver
{
public override ICollection<Assembly> GetAssemblies()
{
var appPath = AppDomain.CurrentDomain.BaseDirectory;
var baseAssemblies = base.GetAssemblies();
var assemblies = new List<Assembly>(baseAssemblies);
var files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories);
var customAssemblies = files.Select(Assembly.LoadFile);
// register Web API controllers
var apiControllerAssemblies =
from assembly in customAssemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IHttpController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select assembly;
foreach (var assembly in apiControllerAssemblies)
{
baseAssemblies.Add(assembly);
}
return assemblies;
}
}
I also added the following line to the beginning of the App_Start in the Global.asax.cs
GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());
Hope this helps someone!
A huge, huge thank you to Steven for his help and insight! I really appreciate it!!

How to manage discovery and composition as 2 separate concerns?

I have set up an assembly catalog:
private CompositionContainer GetContainer() {
// initialize directory info
ExtensionDirectory = new DirectoryInfo(settings.ExtensionsPath);
// directory catalog
var dirCatalog = new DirectoryCatalog(ExtensionDirectory.FullName);
return new CompositionContainer(dirCatalog);
}
The contents of the container will load up all the assemblies in the directory as expected. I do not want to actually compose anything yet because I have constructors that will be injected with dependencies.
What I want to do is use the AssemblyCatalog as a repository; query for a specific export, pass the constructor dependency, then compose only the parts involved in this process.
From what I understand, if I were to call
_container.ComposeParts(this);
...without providing exports for the [ImportingConstructor]s, then none of the parts would be included in the _container.
In order to facilitate queries to the container, I have a method as follows:
public Lazy<IEntity> GetPart(Func<Lazy<IEntity, IEntityMetaData>, bool> selector) {
var entity = _container.GetExports<IEntity, IEntityMetaData>()
.Where(selector)
.Select(e => e as Lazy<IEntity>)
.FirstOrDefault();
return entity; // this will be passed up to the composition service
}
It seems that GetExports<T, M>() will not return an export containing an [ImportingConstructor] if the part which would satisfy the dependency is not included in the container.
My approach is to have an extension container/catalog at a low level; a higher level composition service will receive all parts and compose the final object. I decided on this approach so we would be able to add/extend the types of catalogs available in the future.
I think these concerns are already separated: discovery is handled by catalogs, and composition is done by export providers.
In the typical case, you just pass a catalog directly to the container and for convenience it will automatically take care of creating an CatalogExportProvider for it.
But you can also create one or more export providers yourself and pass them to the container with this constructor overload. (You may also have to set the SourceProvider to point back at the container after that, so that the export providers can use each other.)
You can create your own ExportProvider implementations, and they don't even have to be backed by catalogs.
In order to satisfy the requirements, I created 3 classes:
public sealed class CompositionFactory {
[Import("Provider")]
private IProvider importProvider;
/* MEF initialization */
}
[Export("Provider")]
public sealed class AssemblyProvider : IProvider {
private CatalogExportProvider _provider;
}
internal sealed class ComposableAggregate { }
The CompositionFactory initializes MEF to discover the AssemblyProvider. When the provider initializes:
private CatalogExportProvider InitializeProvider() {
// directory catalog
var dirCatalog = new DirectoryCatalog(ExtensionDirectory.FullName);
return new CatalogExportProvider(dirCatalog);
}
...we return a CatalogExportProvider. I can now use an API to the CompositionFactory:
public ISomething GetSomething(string ContractName, object ContractParam) {
// implementation
}
...to query for the correct composable part using a contract name:
public ComposablePartDefinition GetPartDefinition(string ContractName) {
return _provider.Catalog.Parts
.Where(p => p.ExportDefinitions
.Select(e => e.ContractName)
.Any(c => c == ContractName))
.FirstOrDefault();
}
The work is then completed in the ComposableAggregate helper class:
internal ISomething Value {
get {
return _container.GetExport<IEntity>(_contractName).Value;
}
}
private CompositionBatch CreateBatch() {
CompositionBatch batch = new CompositionBatch();
// create composable part from definition
ComposablePart importDef = CreatePart(_contractName);
batch.AddPart(importDef);
return batch;
}
private ComposablePart CreatePart(string ContractName) {
// get part definition from catalog
return _provider.GetPartDefinition(ContractName).CreatePart();
}

Categories