Integrating Managed Extensibility Framework with the .NET Service Provider

Introduction

It seems I’m in the mood for Managed Extensibility Framework: second post in a week about it! This time, I’m going to talk about how we can integrate it with the .NET Core’s service provider/dependency injection (DI) library (Microsoft.Extensions.DependencyInjection).

Mind you, this will apply to both ASP.NET Core and .NET Core console apps.

Locating Services

We’ve seen before how we can find all types that match a given interface:

public static class ContainerConfigurationExtensions     {         public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)         {             return WithAssembliesInPath(configuration, path, null, searchOption);         }         public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)         {             var assemblyFiles = Directory                 .GetFiles(path, "*.dll", searchOption);             var assemblies = assemblyFiles                 .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);             configuration = configuration.WithAssemblies(assemblies, conventions);             return configuration;         }     }

Service Registration

The next step is picking up all of the found types and registering them with the DI:

public static class ServiceCollectionExtensions     {         public static IServiceCollection AddFromAssembliesInPath<T>(this IServiceCollection services, ServiceLifetime lifetime, string path = null) where T : class         {             var factory = new ExportFactory<T, object>(() => new Tuple<T, Action>(Activator.CreateInstance<T>(), () => { }), new object());             var conventions = new ConventionBuilder();             var builder = conventions                 .ForTypesDerivedFrom<T>()                 .Export<T>();             if (lifetime == ServiceLifetime.Singleton)             {                 builder = builder.Shared();             }             path = path ?? AppContext.BaseDirectory;             var configuration = new ContainerConfiguration()                 .WithAssembliesInPath(path, conventions);             using (var container = configuration.CreateContainer())             {                 var svcs = container.GetExports<Lazy<T>>();                 foreach (var svc in svcs)                 {                     services.Add(new ServiceDescriptor(typeof(T), sp => svc.Value, lifetime));                 }             }             return services;         }         public static IServiceCollection AddSingletonFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Singleton, path);         }         public static IServiceCollection AddScopedFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Scoped, path);         }         public static IServiceCollection AddTransientFromAssembliesInPath<T>(this IServiceCollection services, string path = null) where T : class         {             return AddFromAssembliesInPath<T>(services, ServiceLifetime.Transient, path);         }     }

The AddFromAssembliesInPath extension method is what does all the work; it leverages the previous WithAssembliesInPath method to locate all types that match a given interface, in the assemblies inside a specific folder (which can be the current one). AddSingletonFromAssembliesInPath, AddScopedFromAssembliesInPath and AddTransientFromAssembliesInPath are merely here to make your life a (little bit) easier. Although MEF only supports singletons (Shared) and transient (Non-shared) lifetimes, with this approach

Notice how MEF let’s us resolve Lazy<T> instances besides T. This is pretty cool, as we can delay object instantiation to a later stage, when the object is actually needed. A word of caution: the instantiation will actually be done by MEF, not by the .NET Core DI, so you won’t have constructor injection.

Putting it all Together

So, armed with these two extension methods, we can add this to the ConfigureServices method of your ASP.NET Core app (or wherever you populate your service provider):

services.AddTransientFromAssembliesInPath<IPlugin>();

Here IPlugin is just some interface, nothing to do with the one described in the previous post. After this, you should be able to inject all of the actual implementations:

public class HomeController : Controller

{

public HomeController(IEnumerable<IPlugin> plugins) { … }

}

Dynamically Loading Middleware in ASP.NET Core

Introduction

The concept of middleware has been around since ASP.NET MVC (pre-Core) and OWIN. Essentially, a middleware component lives in a pipeline and handles requests and acts as a chain of responsibility, delegating to any subsequent middleware components registered in the pipeline after itself. The following image (taken from the Microsoft site) shows this.

Image result for asp.net core middleware

MVC itself is implemented as a middleware component, as is redirection, exception handling, buffering, etc.

A middleware component can be added in several ways, but in ASP.NET Core, it all goes down to the Use method in IApplicationBuilder. Lots of API-specific methods rely on it to add their own middleware.

For the time being, we’ll make use of the IMiddleware interface that comes with ASP.NET Core. It provides a simple contract that has no dependencies other than the common HTTP abstractions.

One common request is the ability to load and inject middleware components dynamically into the pipeline. Let’s see how we can

Managed Extensibility Framework

.NET Core has Managed Extensibility Framework (MEF), and I previously blogged about it. MEF offers an API that can be used to find and instantiate plugins from assemblies, which makes it an interesting candidate for the discovery and instantiation of such middleware components.

Image result for managed extensibility frameworkWe’ll use the System.Composition NuGet package. As in my previous post, we’ll iterate through all the assemblies in a given path (normally, the ASP.NET Core’s bin folder) and try to find all implementations of our target interface. After that we’ll register them all to the MEF configuration.

Implementation

Our target interface will be called IPlugin and it actually inherits from IMiddleware. If we so wish, we can add more members to it, for now, it really doesn’t matter:

public interface IPlugin : IMiddleware
{
}

The IMiddleware offers an InvokeAsync method that can be called asynchronously and takes the current context and a pointer to the next delegate (or middleware component).

I wrote the following extension method for IApplicationBuilder:

public static class ApplicationBuilderExtensions

{

public static IApplicationBuilder UsePlugins(this IApplicationBuilder app, string path = null)        {

     var conventions = new ConventionBuilder();

        conventions

           .ForTypesDerivedFrom<IPlugin>()

           .Export<IPlugin>()

           .Shared();

           path = path ?? AppContext.BaseDirectory;

            var configuration = new ContainerConfiguration()

            .WithAssembliesInPath(path, conventions);

            using (var container = configuration.CreateContainer())

            {

           var plugins = container

                .GetExports<IPlugin>()

                    .OrderBy(p => p.GetType().GetCustomAttributes<ExportMetadataAttribute>(true)

.SingleOrDefault(x => x.Name == “Order”)?.Value as IComparable ?? int.MaxValue); 

               foreach (var plugin in plugins)

                {

                    app.Use(async (ctx, next) =>

                    {

                    await plugin.InvokeAsync(ctx, null);

                        await next();

                    });

                }

          }

          return app;

    }

}

We define a convention that for each type found that implements IPlugin we register it as shared, meaning, as a singleton.

As you can see, if the path parameter is not supplied, it will default to AppContext.BaseDirectory.

We can add to the plugin/middleware implementation an ExportMetadataAttribute with an Order value to specify the order by which our plugins will be loaded, more on this in a moment.

The WithAssembliesInPath extension method comes from my previous post but I’ll add it here for your convenience:

public static class ContainerConfigurationExtensions
{     public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)     {      return WithAssembliesInPath(configuration, path, null, searchOption);     }     public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)     {         var assemblyFiles = Directory          .GetFiles(path, "*.dll", searchOption);         var assemblies = assemblyFiles             .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);         configuration = configuration.WithAssemblies(assemblies, conventions);         return configuration;     }
}

If you want to search all assemblies in nested directories, you need to pass SearchOption.AllDirectories as the searchOption parameter, but this, of course, will have a performance penalty if you have a deep directory structure.

Putting it All Together

So, let’s write a few classes that implement the IPlugin interface and therefore are suitable to be used as middleware components:

[Export(typeof(IPlugin))] [ExportMetadata(“Order”, 1)] public class MyPlugin1 : IPlugin {     public async Task InvokeAsync(HttpContext context, RequestDelegate next)     {         //do something here

//this is needed because this can be the last middleware in the pipeline (next = null)         if (next != null)         {             await next(context);         }

//do something here     } }

Notice how we applied an ExportMetadataAttribute to the class with an Order value; this is not needed and if not supplied, it will default to the highest integer (int.MaxValue), which means it will load after all other plugins. These classes need to be public and have a public parameterless constructor. You can retrieve any registered services from the HttpContext’s RequestServices property.

Now, all we need to do is add a couple of assemblies to the web application’s bin path (or some other path that is passed to UsePlugins) and call this extension method inside Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

//rest goes here

app.UsePlugins(/*path: “some path”*/);

//rest goes here

}

And here you have it: ASP.NET Core will find middleware from any assemblies that it can find on the given path.

Hope you find this useful! Winking smile

Using MEF in .NET Core

For those who don’t know, the Managed Extensibility Framework (MEF) is alive and well, and has been ported to .NET Core as Microsoft.Composition (source here). Not all of MEF has been ported, just MEF 2 (System.Composition), not MEF 1 (System.ComponentModel.Composition), meaning, we don’t have the catalogs, which included, among others, the ability to load types from assemblies in a directory.

Using MEF is straightforward, and this time I will not go into details. What I will show is how we can load types from files in a directory.

First, a sample usage might be:

var conventions = new ConventionBuilder();

conventions

    .ForTypesDerivedFrom<IPlugin>()

    .Export<IPlugin>()

    .Shared();

 

var assemblies = new[] { typeof(MyPlugin).GetTypeInfo().Assembly };

 

var configuration = new ContainerConfiguration()

    .WithAssemblies(assemblies, conventions);

 

using (var container = configuration.CreateContainer())

{

    var plugins = container.GetExports<IPlugin>();

}

Notice that we have to build the assemblies list ourselves.

Now, let’s load assemblies from a folder instead. We’ll create an extension method for that purpose:

public static class ContainerConfigurationExtensions

{

    public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)

    {

        return WithAssembliesInPath(configuration, path, null, searchOption);

    }

 

    public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)

    {

        var assemblies = Directory

            .GetFiles(path, "*.dll", searchOption)

            .Select(AssemblyLoadContext.GetAssemblyName)

            .Select(AssemblyLoadContext.Default.LoadFromAssemblyName)

            .ToList();

 

        configuration = configuration.WithAssemblies(assemblies, conventions);

 

        return configuration;

    }

}

The key here is the AssemblyLoadContext class: this is what allows us to obtain an assembly from a file stream or a byte array. Notice that in .NET Core, things have changed significantly, and now we don’t have AppDomain or Assembly.LoadFrom, and assemblies are now loaded by an assembly loader, in a similar way to what Java does with class loaders.

So, now you can change the code to:

var path = @"c:\some\path";

 

var configuration = new ContainerConfiguration()

    .WithAssembliesInPath(path, conventions);

It even works for loading assemblies in all sub-folders.

Maybe I’ll write another post on MEF in the near future, so stay tuned!

Unity, Part 9: Integration With Managed Extensibility Framework

This time, I will be talking about integrating Unity with Managed Extensibility Framework (MEF). You can find the other posts in the series here (how to use Unity in a web application), here (adding Interfaces), here (registration by convention), here (injecting values), here (extensions), here (aspect-oriented programming), here (dependency injection) and the first one here (introduction).

The Managed Extensibility Framework (MEF) has been around since the release of .NET 4.0, and even before as a beta, stand-alone package. Basically, it provides an extensible mechanism for detecting and loading plugins. It’s easier to use than the similarly-named Managed Add-In Framework (MAF), and even if it’s not so feature-rich (it doesn’t support sandboxing, for once), unlike MAF, it is well alive!

So, what does MEF offer that can be of use to Unity? Well, MEF knows how to locate exports/plugins from a number of locations, like assemblies and file system directories. It’s just a matter of finding the exports we’re interested in and registering them with Unity.

An export in MEF is some class that is decorated with an ExportAttribute (technically speaking, this is just when using the Attributed Programming Model, since .NET 4.5 there is also the Convention-Based Programming Model). This attribute allows specifying the type to export (ContractType) and also the contract name (ContractName). This matches closely the Unity/IoC concept of contract type and name.

We could find all exports under a given path using MEF using an AssemblyCatalog, a particular implementation of a ComposablePartCatalog:

   1: var catalog = new AssemblyCatalog("some path");

A couple of helper functions for picking up the export’s contract type and name, by leveraging the ReflectionModelServices class:

   1: public static IDictionary<String, Type> GetExportedTypes<T>(this ComposablePartCatalog catalog)

   2: {

   3:     return (GetExportedTypes(catalog, typeof(T)));

   4: }

   5:  

   6: public static IDictionary<String, Type> GetExportedTypes(this ComposablePartCatalog catalog, Type type)

   7: {

   8:     return (catalog.Parts.Where(part => IsComposablePart(part, type) == true).ToDictionary(part => part.ExportDefinitions.First().ContractName, part => ReflectionModelServices.GetPartType(part).Value));

   9: }

  10:  

  11:  

  12: private static Boolean IsComposablePart(ComposablePartDefinition part, Type type)

  13: {

  14:     return (part.ExportDefinitions.Any(def => (def.Metadata.ContainsKey("ExportTypeIdentity") == true) && (def.Metadata["ExportTypeIdentity"].Equals(type.FullName) == true)));

  15: }

This will return a collection of key-value pairs, where the key is the contract name and the value the contract type; this is so there can be multiple contract names for a given contract type. After we have this, it’s just a matter of iterating the results and registering each occurrence:

   1: var type = typeof(ISomeType);

   2: var exports = catalog.GetExportedTypes(type);

   3:  

   4: foreach (var entry in exports)

   5: {

   6:     unity.RegisterType(type, entry.Value, entry.Key);

   7: }

So, given the following contract and implementations:

   1: public interface ISomeType

   2: {

   3:     void SomeMethod();

   4: }

   5:  

   6: [Export("Some", typeof(ISomeType))]

   7: public class SomeImplementation : ISomeType

   8: {

   9:     public void SomeMethod() { }

  10: }

  11:  

  12: [Export("Another", typeof(ISomeType))]

  13: public class AnotherImplementation : ISomeType

  14: {

  15:     public void SomeMethod() { }

  16: }

We can obtain a specific contract type implementation given it’s name:

   1: var myImplementation = unity.Resolve<ISomeType>("MyName");

And also all implementations of the contract that were found:

   1: var all = unity.ResolveAll<ISomeType>();

This can be enhanced in a couple of ways:

  • Use a Unity extension to automatically find and register exports at runtime;
  • Make use of MEF metadata to tell Unity which lifetime managers to use, and other useful properties, such as the default implementation for the contract type.

As usual, I’m looking forward for your comments!