There are various reasons why one would want to support plugins or extensions for an application. It allows third party to extend application, one can isolate development of parts of the application, and pick what to load at runtime.
Whatever the motivation, this means loading .dll’s after the application has started, finding interfaces of interest and registering the classes associated with these interfaces in IoC.
Since IoC already provides the losely coupled instancing required extensions, a bit of dynamic loading is not that far off.
In my previous blog post I showed a basic boilerplate for IoC in .Net Core. “ConfigureService” is the place to add services, and hence a good place to do some dynamic loading.
Loading .dll-files dynamically
1 2 3 |
var file = "somefile.dll"; var absFile = new FileInfo(file).FullName; var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(absFile); |
Loading an assembly has changed slightly from .Net to .Net core, but its still fairly straight forward. It does want a proper file name though. Running it through FileInfo to get the full path fixes that for us.
Locating a particular class
1 2 3 4 5 |
var iti = typeof(ISomeInterface).GetTypeInfo(); var classTypes = assembly .DefinedTypes .Where(type => type.IsClass && type.IsPublic && iti.IsAssignableFrom(type.AsType())) .ToList(); |
First we need to get type info of the interface, then look through all defined types of the assembly. We want only the ones that are a public class, and that implement our interface.
Instancing a class
1 2 3 4 |
foreach (var ct in classTypes) { var classInstance = (T)Activator.CreateInstance(ct); } |
Since we may have found multiple classes that implement our interface we have a list of types. To create an instance from a type we use Activator.CreateInstance. At this point we could add ctor parameters, but it may be cleaner to have a common interface that implements some sort of Init(params) instead and call that later.