How to dynamically load assemblies in dotnet core - c#

I'm building a web application, where I would like separate concerns, i.e. having abstractions and implementations in different projects.
To achieve this, I've tried to implement a composition root concept, where all implementation must have an instance of ICompositionRootComposer to register services, types etc.
public interface ICompositionRootComposer
{
void Compose(ICompositionConfigurator configurator);
}
In projects that are referred directly in the build hierarchy, implementations of ICompositionRootComposer are called, and services are registered correct in the underlying IoC container.
The problem arises when I'm trying to register services in a project, where I've set up a post build task that copies the built dll to the web project's debug folder:
cp -R $(TargetDir)"assembly and symbol name"* $(SolutionDir)src/"webproject path"/bin/Debug/netcoreapp1.1
I'm loading the assembly with: (Inspiration: How to load assemblies located in a folder in .net core console app)
internal class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
internal AssemblyLoader(string folderPath)
{
this.folderPath = Path.GetDirectoryName(folderPath);
}
internal Assembly Load(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
AssemblyName assemblyName = new AssemblyName(fileInfo.Name.Replace(fileInfo.Extension, string.Empty));
return this.Load(assemblyName);
}
protected override Assembly Load(AssemblyName assemblyName)
{
var dependencyContext = DependencyContext.Default;
var ressource = dependencyContext.CompileLibraries.FirstOrDefault(r => r.Name.Contains(assemblyName.Name));
if(ressource != null)
{
return Assembly.Load(new AssemblyName(ressource.Name));
}
var fileInfo = this.LoadFileInfo(assemblyName.Name);
if(File.Exists(fileInfo.FullName))
{
Assembly assembly = null;
if(this.TryGetAssemblyFromAssemblyName(assemblyName, out assembly))
{
return assembly;
}
return this.LoadFromAssemblyPath(fileInfo.FullName);
}
return Assembly.Load(assemblyName);
}
private FileInfo LoadFileInfo(string assemblyName)
{
string fullPath = Path.Combine(this.folderPath, $"{assemblyName}.dll");
return new FileInfo(fullPath);
}
private bool TryGetAssemblyFromAssemblyName(AssemblyName assemblyName, out Assembly assembly)
{
try
{
assembly = Default.LoadFromAssemblyName(assemblyName);
return true;
}
catch
{
assembly = null;
return false;
}
}
}
With this I'm able to load the assembly and call the projects ICompositionRootComposer implementation.
But the problem is that it doesn't seem to recognize any of my types.
When calling my configurator with
configurator.RegisterTransiantService<IFoo, Foo>();
it should register IFoo and Foo in the IoC.
But when debugging I'm not able to get info of the types, i.e via typeof(Foo) in the debug console in Visual Studio Code.

Necromancing.
You can create a wrapper class for the old Assembly.LoadFile to do that.
This has the added benefit that you can stay backward-compatible with dotnet-none-core by applying search-and-replace changes in old code-bases.
namespace System.Reflection
{
public class Assembly2
{
public static System.Reflection.Assembly LoadFile(string path)
{
System.Reflection.Assembly assembly = null;
#if NET_CORE
// Requires nuget - System.Runtime.Loader
assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
#else
assembly = System.Reflection. Assembly.LoadFile(path);
#endif
return assembly;
}
}
}
You'll need to add System.Runtime.Loader via NuGet.

I found a solution to my problem.
It turned out, that I had the property PreserveCompilationContext set to true, and that's why the debugger wouldn't register my manually copied assembly.
When I removed the property from the web project csproj file, everything worked.

Are you aware that ASP.NET Core has it's own, built-in Dependency Injection mechanism? It can be easily switched to other IoC container for your needs, I don't think that you need to reinvent it.
What you need to do here is use a reflection to make a generic method and call after that, something like this:
public void ConfigureServices(IServiceCollection services)
{
var myAssembly = LoadAssembly();
// find first interface
var firstInterfaceType = myAssembly.DefinedTypes.FirstOrDefault(t => t.IsInterface).GetType();
// find it's implementation
var firstInterfaceImplementationType = myAssembly.DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(firstInterfaceType)).GetType();
// get general method info
MethodInfo method = typeof(IServiceCollection).GetMethod("AddTransient");
// provide types to generic method
MethodInfo generic = method.MakeGenericMethod(firstInterfaceType, firstInterfaceImplementationType);
// register your types
generic.Invoke(services, null);
}

Related

How to run .netcore method in a separate process?

My problem is I am invoking a 3rd party method asynchronously and it can sometimes hang or often take a long time to complete but we need to kill it for whatever reason.
Since it's third party, tasks and cancellation tokens can't be used to
cancel the computation.
From what I read Thread.Abort() is evil. Especially since this method will be reading and writing files and making connections to our database
From what I've seen AppDomain is something I could have used, but this isn't available in .netcore
It seems the only way to execute this method in a way I can kill immediately and safely is to run it in a process and kill that process. But even this I'm not sure is possible. It seems only .exe can be ran by a process.
Any help is greatly appreciated.
There is a solution for AppDomain in .net core, it is explained in this article.
long story short:
In order to replace AppDomain Unload event use:
System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += context => InvokeBatchProcessors();
this is a part of System.Runtime.Loader library.
Appdomain GetAssemblies:
public static IEnumerable<Assembly> GetReferencingAssemblies(string assemblyName)
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
if (IsCandidateLibrary(library, assemblyName))
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
}
return assemblies;
}
private static bool IsCandidateLibrary(RuntimeLibrary library, assemblyName)
{
return library.Name == (assemblyName)
|| library.Dependencies.Any(d => d.Name.StartsWith(assemblyName));
}
This is part of Microsoft.Extensions.DependencyModel package.
Creating a Pollyfill for AppDomain.CurrentDomain.GetAssemblies:
public class AppDomain
{
public static AppDomain CurrentDomain { get; private set; }
static AppDomain()
{
CurrentDomain = new AppDomain();
}
public Assembly[] GetAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
if (IsCandidateCompilationLibrary(library))
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
}
return assemblies.ToArray();
}
private static bool IsCandidateCompilationLibrary(RuntimeLibrary compilationLibrary)
{
return compilationLibrary.Name == ("Specify")
|| compilationLibrary.Dependencies.Any(d => d.Name.StartsWith("Specify"));
}
}
Note that these are all examples on how to use AppDomain in .net core, for any how needs to use the functionality of AppDomain in .net core.

Implement plugin system using .Net Core Libraries with Autofac

A few months ago I built a very simply still effective plugin system with autofac modules in ASP.Net Core 1.1 like this
In startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.RegisterModule<AutofacModule>();
builder.Populate(services);
var ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
then in AutofacModule.cs
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<AuthMessageSender>().As<IEmailSender>();
builder.RegisterType<AuthMessageSender>().As<ISmsSender>();
builder.RegisterType<HttpClient>().As<HttpClient>();
foreach (var assembly in LoadAllAssemblies())
{
builder.RegisterAssemblyModules(assembly);
}
}
private IEnumerable<Assembly> LoadAllAssemblies()
{
string assemblyPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "");
var allAssemblies = new List<Assembly>();
foreach (string dll in Directory.GetFiles(assemblyPath, "*.dll"))
{
try
{
var assembly = Assembly.LoadFile(dll);
if (!assembly.GetTypes().Any(type => type.IsSubclassOf(typeof(Autofac.Module))))
{
continue;
}
allAssemblies.Add(assembly);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
continue;
}
}
return allAssemblies;
}
then I could load modules from assemblies like this:
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<UserService>().As<IUserService>();
builder.RegisterType<DashboardService>().As<IDashboardService>();
builder.RegisterType<DetailPageService>().As<IDetailPanelService>();
builder.RegisterType<PlanningService>().As<IPlanningService>();
builder.RegisterType<PlanningRepository>().As<IPlanningRepository>();
}
}
I had to copy assemblies to the running web app bin directory to work, or reference all assemblies in the main web project. All the assemblies were .Net Framework Class Libraries.
After a while I updated the whole solution to Asp.Net Core 2.0, and Autofac module containing assemblies changed to .Net Core Class Libraries. Now, autofac modules are still found, and registered via RegisterAssemblyModules, but load method does not called anymore. I realized, if I create another .Net Framework Library project with autofac module, and reference it, it is still loading as expected, and load method called. So it seems to me this problem is related to .Net Core Libraries.
Anybody can explain me this, knows any solution, workaround, or can recommend another architecture for plugin system with autofac?

Why do my assemblies need to be loaded in a specific order?

I am writing a simple plugin and stumbled upon contractType.IsAssignableFrom(pluginType) returning different results depending on the order of loading.
Calling IsAssignableFromon the Plugin returns True as expected.
But if I load the Contract assembly before loading the Plugin, IsAssignableFrom on the Plugin returns False.
I am running Win10 and dotnet4.7 but I doubt that has any relevancy.
Code
[TestMethod]
public void SimplyLoadingPlugin_Succeeds()
{
var plugin = Assembly.LoadFrom(PluginPathFilename);
var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());
Assert.IsTrue(res); // Succeeds.
}
[TestMethod]
public void LoadingContractAndThenPlugin_Fails()
{
var contract = Assembly.LoadFrom(ContractPathFilename);
var plugin = Assembly.LoadFrom(PluginPathFilename);
var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());
Assert.IsTrue(res); // Fails.
}
To make it harder to test:
If i run the LoadingContractAndThenPlugin_Fails test by itself is fails. But If i run the tests together it is dependent on order. Running SimplyLoadingPlugin_Succeeds first and LoadingContractAndThenPlugin_Fails last, makes both tests green but running them in the reverse order makes both fail.
So somehow the very loading of Contract before Plugin messes up something for me.
I can se nothing related in the GAC.
Below are all files needed. The paths in the probably have to be updated.
4 project with one file in each. 1 solution.
Contract.cs (a library project)
public abstract class Contract
{
public abstract int Version { get; set; }
}
Plugin.cs (a library project)
public class Plugin : Contract
{
public override int Version { get; set; }
}
Tests.cs (a test project)
[TestClass]
public class Tests
{
private const string PluginPath = #"C:\DATA\Projekt\LoadFromOrder\Plugin\bin\Debug";
private string PluginPathFilename = Path.Combine(PluginPath, "Plugin.dll");
private string ContractPathFilename = Path.Combine(PluginPath, "Contract.dll");
[TestMethod]
public void SimplyLoadingPlugin_Succeeds()
{
var plugin = Assembly.LoadFrom(PluginPathFilename);
var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());
Assert.IsTrue(res); // Succeeds.
}
[TestMethod]
public void LoadingContractAndThenPlugin_Fails()
{
var contract = Assembly.LoadFrom(ContractPathFilename);
var plugin = Assembly.LoadFrom(PluginPathFilename);
var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());
Assert.IsTrue(res); // Fails.
}
// BEGIN ---- Update. ----
[TestMethod]
public void LoadingPluginFromTestProject_Succeeds()
{
var contract = Assembly.LoadFrom(
#"C:\DATA\Projekt\LoadFromOrder\TestProject\bin\Debug\Contract.dll");
var plugin = Assembly.LoadFrom(PluginPathFilename);
var res = typeof(Contract.Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());
Assert.IsTrue(res); // Succeeds.
}
// END ---- Update. ----
}
Program.cs (a console project)
class Program
{
static void Main(string[] args)
{
var tests = new Tests();
try
{
System.Console.WriteLine("Press A for Success and B for Fail.");
switch (System.Console.ReadKey(true).Key)
{
case ConsoleKey.A:
tests.SimplyLoadingPlugin_Succeeds();
break;
case ConsoleKey.B:
tests.LoadingContractAndThenPlugin_Fails();
break;
}
System.Console.WriteLine("SUCCESS");
}
catch (Exception exc)
{
System.Console.WriteLine($"FAIL: {exc.Message}");
}
}
}
By loading the Contract using Assembly.LoadFrom you are creating a reference ambiguity. That library is already loaded that is why we can do typeof(Contract) no need to load it again...
My recommendation: use reflection to determine what references you have and only load those that are not already there, here is a sample code snippet:
var dllFiles = Directory.GetFiles(DIR, "*.DLL", SearchOption.AllDirectories);
var plugins = new HashSet<Assembly>();
var references = typeof(Program).Assembly.GetReferencedAssemblies();
foreach (var dllPath in dllFiles)
{
string name = Path.GetFileNameWithoutExtension(dllPath);
if (!references.Any(x => x.Name == name) && !plugins.Any(x => x.GetName().Name == name))
plugins.Add(Assembly.LoadFrom(dllPath));
}
On that sample we get every DLL for a given directory (subdirectories included) the DIR can be a relative path like ..\..\.. and only load those that are not already in the assembly references.
And here is the full solution with two plugin projects:
https://github.com/heldersepu/csharp-proj/tree/master/PluginSystem
As #HelderSepu purported, loading the contract assembly a second time may be the problem.
I'd suggest that you test in a different but easier way. Instead of manually (re)loading the assemblies in the tests, just add references to the assemblies/projects in the test project, and directly reference typeof(Contract) and typeof(Plugin) and check if typeof(Contract).IsAssignableFrom(typeof(Plugin)). Nothing complicated, just add the references to the test project.
You do not need to test whether the assembly is loaded correctly, the CLR will handle that. You need to test whether the plugin assembly contains a Contract definition. Whatever use cases you might have for your architecture, it's not whether assembly loading works that you should be worried about; it is whether the plugin has been implemented correctly.

How to properly load an instance from a DLL implementing a specific base class in C#?

I have a problem where I have a program that should load a plugin (DLL) from a specific directory where the DLL implements a specific base class. The problem is that my program that loads the DLL has a reference to an other DLL which the DLL being loaded also references. I will show an example of how the problem arises. This simple tests consists of 3 different solutions and 3 separate projects. NOTE: If I have all projects in the same solution, the problem does not arise.
Solution 1 - Project that defines the Base class and an interface
AdapterBase.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
Solution 2 - Project that defines an implementation of the base class
MyAdapter.cs
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
Solution 3 - Main program which loads the DLL implementing AdapterBase*
**Program.cs
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
So now the main program MyProgram.cs will try to load the DLL from path C:\test\Adapter and this works fine if I ONLY put the file MyAdapter.dll in that folder. However Solution 2 (MyAdapter.cs), will put both MyAdapter.dll and AdapterBase.dll in the output bin/ directory. Now if copy both those files to c:\test\Adapter the instance from the DLL does not get loaded since the comparison
if (type.IsSubclassOf(typeof(AdapterBase))) { fails in MyProgram.cs.
Since MyProgram.cs already has a reference to AdapterBase.dll there seems to be some conflict in an other DLL gets loaded from a different path which references the same DLL. The loaded DLL seems to first resolve its dependencies against DLLs in the same folder. I think this has to do with assembly contexts and some problem with the LoadFrom method but I do not know how to get C# to realise that it actually is the same DLL which it already has loaded.
A solution is of course only to copy the only needed DLL but my program would be much more robust if it could handle that the other shared DLL also was there. I mean they are actually the same. So any solutions how to do this more robust?
Yes, this is a type identity problem. A .NET type's identity is not just the namespace and type name, it also includes the assembly it came from. Your plugin has a dependency on the assembly that contains IAdapter, when LoadFrom() loads the plugin it is also going to need that assembly. The CLR finds it in the LoadFrom context, in other words in the c:\test\adapter directory, normally highly desirable since that allows plugins to use their own DLL versions.
Just not in this case. This went wrong because you plugin solution dutifully copied the dependencies. Normally highly desirable, just not in this case.
You'll have to stop it from copying the IAdapter assembly:
Open the plugin solution and use Build > Clean.
Delete a remaining copy of the IAdapter assembly in the output directory with Explorer.
Select the IAdapter assembly in the plugin solution's References node. Set its Copy Local property to False.
Use Build > Build and verify that the IAdapter assembly indeed does not get copied anymore.
Copy Local is the essence, the rest of the bullets are there just to make sure that an old copy doesn't cause problems. Since the CLR can no longer find the IAdapter assembly the "easy way", it is forced to keep looking for it. Now finding it in the Load context, in other words, the directory where the host executable is installed. Already loaded, no need to load the one-and-only again. Problem solved.
I found a solution to my problem, although the path for the DLL can not be fully arbitrary. I was able to put the DLLs into, for example, bin/MyCustomFolder and load the DLL without getting the Type conflict problem.
The solution was to use the Assembly.Load() method which takes the full assembly name as argument. So first I find the name of the assembly by loadning all DLLs in the specified folder and the use Assembly.GetTypes() and checking if the Type is a subclass of AdapterBase. Then I use Assembly.Load() to actually load the assembly, which elegantly loads the DLL without any Type conflicts.
Program.cs
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
NOTE: It is also important to add the extra probing path for Assembly.Load() to find the assembly in bin/MyCustomFolder. The probing path must be a subdir of the executing assembly, therefore it is not possible to have the DLL in a completely arbitrary location. Update your App.config as below:
App.config
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="MyCustomFolder"/>
</assemblyBinding>
</runtime>
</configuration>
Tip: Actually I was having this problem for a Web application I had created. In that case you should update Web.config instead. Also in that case the probing path does not have the executing assembly as root but is actually the root of your web application. So in that case you can put you DLL folder MyCustomFolder directly in you web apps root folder, for example: inetpub\wwwroot\mywebapp\MyCustomFolder and then update Web.config as App.config above.

MVC4 MEF-based dynamically loaded plugins

updated: read below in this post for a minimal solution
I have some newbie questions about an MVC4 solution with plugins. I googled around a bit and found
some good stuff, but it does not exactly fit my requirements, so I'm asking here for some advice.
It seems that the best solution for widget-like plugins in MVC is portable areas (in the MvcContrib package). I found the basic guidance here:
http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/
and some useful tips here:
http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx
More stuff in this post:
How to create ASP.NET MVC area as a plugin DLL?
That's all cool but sadly my requirements are a bit different:
unfortunately, I need a system where plugins are added and discovered dynamically, and this is not the case with portable areas, which must be referenced by the main MVC site project. I'd like to just upload something to the site and get it discover and use new components, so I'm going to use MEF for this.
fortunately, my plugins will not be like widgets, which might be very complex and heterogeneous; rather, they are components which must follow a common, shared pattern. Think of them like specialized editors: for each data type I'll offer a component with editing functions: new, edit, delete. So I was thinking of plugin-controllers which implement a common interface and provide actions like New, Edit, Delete and the like.
I must use MVC4 and in the future I'll have to add localization and mobile customizations.
I must avoid dependencies from complex frameworks and keep the code as simple as possible.
So, whenever I want to add a new data type for editing in this website I'd just like to drop a DLL in its plugins folder for the logic stuff (controller etc), and some views in the correct locations, to get the site discover and use the new editor.
Eventually I could include the views in the DLL itself (I found this: http://razorgenerator.codeplex.com , and this tutorial: http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/, which I suppose I could use with the codeplex razorgenerator as the code it refers to is not compatible with VS2012), but probably I'll have better keep them separated (also because of the localization and mobile-awareness requirements); I was thinking of adding an upload mechanism to my site admin area, where you can upload a single zip with the DLL with controllers and folders with views, and then let the server unzip and store files where required. This would allow me to easily modify views without having to deploy again the whole add-in.
So I started looking for MEF and MVC, but most of the posts refer to MVC2 and are not compatible. I had better luck with this, which is mainly focused on web API, but looks promising and simple enough:
http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-and-webapi.html
This essentially adds a MEF-based dependency resolver and controller factory to the "standard" MVC application. Anyway the sample in the post refers to a single-assembly solution, while I need to deploy several different plugins. So I slightly modified the code to use a MEF DirectoryCatalog (rather than an AssemblyCatalog) pointing to my plugins folder and then created a test MVC solution, with a single plugin in a class library.
Anyway, when I try loading the plugin controller the framework calls my factory GetControllerInstance with a null type, so that of course MEF cannot proceed to composition. Probably I'm missing something obvious, but I'm new to MVC 4 and any suggestion or useful (MVC4-compliant) link are welcome. Thanks!
Here is the essential code:
public static class MefConfig
{
public static void RegisterMef()
{
CompositionContainer container = ConfigureContainer();
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver =
new MefDependencyResolver(container);
}
private static CompositionContainer ConfigureContainer()
{
//AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
DirectoryCatalog catalog = new DirectoryCatalog(
HostingEnvironment.MapPath("~/Plugins"));
CompositionContainer container = new CompositionContainer(catalog);
return container;
}
}
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
_container = container;
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
var export = _container.GetExports(serviceType, null, null).SingleOrDefault();
return (export != null ? export.Value : null);
}
public IEnumerable GetServices(Type serviceType)
{
var exports = _container.GetExports(serviceType, null, null);
List createdObjects = new List();
if (exports.Any())
createdObjects.AddRange(exports.Select(export => export.Value));
return createdObjects;
}
public void Dispose()
{
}
}
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _compositionContainer;
public MefControllerFactory(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}
protected override IController GetControllerInstance(
System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null) throw new ArgumentNullException("controllerType");
var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();
IController result;
if (null != export) result = export.Value as IController;
else
{
result = base.GetControllerInstance(requestContext, controllerType);
_compositionContainer.ComposeParts(result);
} //eelse
return result;
}
}
You can download the full test solution from here:
http://www.filedropper.com/mvcplugins
Edit: a first working minimal solution
Here are my findings, hope they can be useful for some other newbie starting with this stuff: I did not manage to succesfully run the framework quoted in the above reply, I suppose there must be something to be updated for VS2012 and MVC4. Anyway, I looked at the code and googled a bit more:
1) first of all, a source of confusion for me were the 2 different interfaces with the same name: IDependencyResolver. If I understand well, one (System.Web.Http.Dependencies.IDependencyResolver) is used for webapi, and another (System.Web.Mvc.IDependencyResolver) for generic DI. This post helped me here: http://lucid-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/.
2) also, a third component is the DefaultControllerFactory-derived controller factory, which is crucial to this post because it is the factory used for plugin-hosted controllers.
Here are my implementations for all these, slightly modified from several samples: first the HTTP resolver:
public sealed class MefHttpDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefHttpDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
public IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{
}
}
Then the MVC resolver, which is very similar, even if strictly not necessary for the dummy sample in this scenario:
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
}
And finally the controller factory:
[Export(typeof(IControllerFactory))]
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _container;
[ImportingConstructor]
public MefControllerFactory(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = _container
.GetExports()
.Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase))
.Select(c => c.Value)
.FirstOrDefault();
return controller ?? base.CreateController(requestContext, controllerName);
}
}
As for the sample controller, I created it into a class library project:
[Export(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata("Name", "Alpha")]
public sealed class AlphaController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello, this is the PLUGIN controller!";
return View();
}
}
In the main project (the MVC site) I have a Plugins folder where I copy this DLL, plus a "standard" set of views in their folders for this controller's views.
This is the simplest possible scenario, and probably there is much more to find out and refine, but I needed to be simple to start with. Anyway, any suggestion is welcome.
I'm currently working on the same issue. I've found this solution:
Blog post: http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/
Source code: https://bitbucket.org/darincreason/mvcapplication8.web
Basically it loads assemblies from specified location and with some name pattern on web application startup:
AssemblyInfo.cs:
[assembly: PreApplicationStartMethod(
typeof(PluginAreaBootstrapper), "Init")]
PluginAreaBootstrapper.cs:
public class PluginAreaBootstrapper
{
public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();
public static List<string> PluginNames()
{
return PluginAssemblies.Select(
pluginAssembly => pluginAssembly.GetName().Name)
.ToList();
}
public static void Init()
{
var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");
foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
PluginAssemblies.Add(Assembly.LoadFile(file));
PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
// Add assembly handler for strongly-typed view models
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
{
var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// Check we don't already have the assembly loaded
foreach (var assembly in currentAssemblies)
{
if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
{
return assembly;
}
}
return null;
}
}
But I believe you can create some directory observer which can dynamically load assemblies, so you don't even need to restart your web application.
In my opinion it meets your 1, 2 and 4 needs. It's very simple, doesn't require any frameworks, has minimal configuration, allows dynamic loading of the plugins and works with MVC 4.
This solution plugs assemblies into Area directory, but I believe you can quite easily tune it to play as you like using routing.
You can find more complete solutions, here, and more background here. Our requirements were also a DI so this will help a bit too, although not needed (first links provide solution for DI as well. Good luck with this, it's not hard. Also note that those are for MVC3 but easily convert/migrate to MVC 4

Categories

Resources