How can we support modular and test patterns with ASP.NET MVC 4 and MEF 2?

We are trying to use MEF 2 with ASP.NET MVC 4 to support an extensible application. There are really two parts to this question (I hope everything is OK SO gods):

  • How to use Microsoft.Composition and MVC container code ( MEF / MVC demo source ) to replace Ninject with our DI for ICoreService , ICoreRepository , IUnitOfWork and IDbContext ?
    It seems like we cannot use both the Ninject and the MVC container at the same time (I am sure many say โ€œduhโ€), so we would like to go with MEF if possible. I tried to remove Ninject and set the [Export] attributes for each of the respective implementations, spanning two assemblies in addition to the web project, but Save() could not be saved without errors. I interpreted this as a singleton question, but couldn't figure out how to sort it (including [Shared] ).

  • How do we load multiple assemblies dynamically at runtime?
    I understand how to use CompositionContainer.AddAssemblies() to load certain DLLs, but for our application to be extensible enough, we need something more similar to how I (vaguely) understand directories in the "full" MEF that were deleted from Microsoft. Compositional package (I think?); so that we can download all IPluggable assemblies (or something else), which will include their own levels of user interface, service and repository, as well as bind to the main service / repo.

EDIT 1
A little more reading solved the first problem, which was really one problem. Attaching [Shared(Boundaries.HttpRequest)] to CoreDbContext solved the persistence problem. When I just tried [Shared] , it expanded "singletonization" to the Application (cross-request) level and threw an exception, indicating that the edited object was already in the EF cache.

EDIT 2
I used iterative assembly, loading the "meat" from Nick Blumhardt, to answer below, to update my Global.asax.cs code. The standard MEF 2 container from its code did not work in mine, possibly because I use the MEF 2 (?) MVC container. Summary: The code below now works as desired.


CoreDbContext.cs (Data.csproj)

 [Export(typeof(IDbContext))] [Shared(Boundaries.HttpRequest)] public class CoreDbContext : IDbContext { ... } 

CoreRepository.cs (Data.csproj)

 [Export(typeof(IUnitOfWork))] [Export(typeof(ICoreRepository))] public class CoreRepository : ICoreRepository, IUnitOfWork { [ImportingConstructor] public CoreRepository(IInsightDbContext context) { _context = context; } ... } 

CoreService.cs (Services.csproj)

 [Export(typeof(ICoreService))] public class CoreService : ICoreService { [ImportingConstructor] public CoreService(ICoreRepository repository, IUnitOfWork unitOfWork) { _repository = repository; _unitOfWork = unitOfWork; } ... } 

UserController.cs (Web.csproj)

 public class UsersController : Controller { [ImportingConstructor] public UsersController(ICoreService service) { _service = service; } ... } 

Global.asax.cs (Web.csproj)

 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { CompositionProvider.AddAssemblies( typeof(ICoreRepository).Assembly, typeof(ICoreService).Assembly, ); // EDIT 2 -- // updated code to answer my 2nd question based on Nick Blumhardt answer foreach (var file in System.IO.Directory.GetFiles(Server.MapPath("Plugins"), "*.dll")) { try { var name = System.Reflection.AssemblyName.GetAssemblyName(file); var assembly = System.Reflection.Assembly.Load(name); CompositionProvider.AddAssembly(assembly); } catch { // You'll need to craft exception handling to // your specific scenario. } } } } 
+4
source share
3 answers

If you understand correctly, you are looking for code that will load all the assemblies from the directory and load them into a container; here is the skeleton for this:

 var config = new ContainerConfiguration(); foreach (var file in Directory.GetFiles(@".\Plugins", "*.dll")) { try { var name = AssemblyName.GetAssemblyName(file); var assembly = Assembly.Load(name); config.WithAssembly(assembly); } catch { // You'll need to craft exception handling to // your specific scenario. } } var container = config.CreateContainer(); // ... 

Hammett discusses this scenario and shows a more complete version in F # here: http://hammett.castleproject.org/index.php/2011/12/a-decent-directorycatalog-implementation/

Note. This will not detect assemblies added to the directory after the application starts. Microsoft.Composition is not intended for such use, therefore, if the set of plug-ins changes, it is best to find that using the directory watcher and prompt the user to restart the application. NTN!

+1
source

MEF is not intended to be used as the basis of DI. This means that you must separate your โ€œpluginsโ€ (regardless of their composition) from the dependencies of your infrastructure and implement them using MEF and the latter using any DI framework that you prefer.

+1
source

I think there are a few misunderstandings about what MEF can and cannot do.

MEF was originally conceived as a pure extensibility architecture, but as the platform evolves to its first release, it can be fully supported as a DI container. MEF will handle the dependency injection for you and does this through this ExportProvider . It is also possible to use other DI frameworks with MEF . Thus, there are actually several ways to achieve this:

  • Create a NinjectExportProvider that you can connect to the MEF, so when the MEF is looking for an available export, it will be able to request your Ninject container.
  • Use the implementation of the shared services locator pattern to connect between MEF and Ninject, or vice versa.

Since you use MEF for extensibility, you probably want to use the first, as this provides your Ninject components for MEF, which in turn provides them to your plugins.

Another thing that is a little disappointing, in fact, there is not much room for automatically enabling the ala Wordpress features on ASP.NET. ASP.NET is a compiled and managed environment, and because of this, you either resort to late binding, loading assemblies manually at runtime, or reloading the application to pick up new plugins, which spoils the object, which is able to connect new extensions through the application.

My advice: plan your architecture to pick up all the extensibility points as a launch and assume that any kernel changes will require a restart of applications and applications.

In terms of direct questions:

  • CompositionProvider takes as an example the ContainerConfiguration , which is used internally to create the CompositionContainer used by the provider. That way, you can use this as a point with which you set up a way to instantiate your container. ContainerConfiguration supports the WithProvider method:

    var configuration = new ContainerConfiguration().WithProvider(new NinjectExportDescriptorProvider(kernel)); CompositionProvider.SetConfiguration(configuration);

Where NinjectExportDescriptorProvider can be:

 public class NinjectExportDescriptorProvider: ExportDescriptorProvider { private readonly IKernel _kernel; public NinjectExportDescriptorProvider(IKernel kernel) { if (kernel == null) throw new ArgumentNullException("kernel"); _kernel = kernel; } public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors( CompositionContract contract, DependencyAccessor dependencyAccessor) { var type = contract.ContractType; if (!_kernel.GetBindings(type).Any()) return NoExportDescriptors; return new[] { new ExportDescriptorPromise( contract, "Ninject Kernel", true, // Hmmm... need to consider this, setting it to true will create it as a shared part, false as new instance each time, NoDependencies, _ => ExportDescriptor.Create((c, o) => _kernel.Get(type), NoMetadata)) }; } } } 

Note. I have not tested this, this is all theory and is based on the example of AppSettingsExportDescriptorProvider at: http://mef.codeplex.com/wikipage?title=ProgrammingModelExtensions

It differs from the standard ExportProvider because the use of CompostionProvider built around a lightweight composition. But essentially you are ending access to your Ninject core and making it available to your CompositionContainer .

  • As with adding a specific new provider (see above), you can use ContainerConfiguration to read available assemblies, perhaps something like:

    var configuration = new ContainerConfiguration().WithAssemblies(AppDomain.GetAssemblies())

Again, I have not tested all of this, but hope this at least points you in the right direction.

+1
source

All Articles