Customization with MEF or Reflection

Component: NServiceBus | Nuget: NServiceBus (Version: 5.x)

Shared

The Shared project defines all the messages used in the sample.

It also contains the custom extension point definitions that both the endpoints and the implementations use to communicate. For example the extension point for running code after the endpoint has started is:

Edit
public interface IRunAfterEndpointStart
{
    void Run(IBus bus);
}

The complete list of extension points in this solution are

  • ICustomizeConfiguration
  • IRunBeforeEndpointStart
  • IRunAfterEndpointStart
  • IRunBeforeEndpointStop
  • IRunAfterEndpointStop

Approaches to executing extension points

Both approaches have a similar parts.

  • An endpoint project that starts the endpoint and loads + executes the specific extension points in the Shared project.
  • An extensions project that contains the implementations for the extension points in the Shared project.

Custom Reflection

This approach uses directory scanning and Reflection to discover and execute the extension points.

Endpoint Startup

Edit
static void Main()
{
    Console.Title = "Samples.CustomExtensionEndpoint";
    var busConfiguration = new BusConfiguration();
    busConfiguration.EndpointName("Samples.CustomExtensionEndpoint");
    busConfiguration.UsePersistence<InMemoryPersistence>();
    busConfiguration.EnableInstallers();
    RunCustomizeConfiguration(busConfiguration);
    RunBeforeEndpointStart();
    using (var bus = Bus.Create(busConfiguration).Start())
    {
        RunAfterEndpointStart(bus);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
        RunBeforeEndpointStop(bus);
    }
    RunAfterEndpointStop();
}

static void RunBeforeEndpointStart()
{
    Resolver.Execute<IRunBeforeEndpointStart>(_ => _.Run());
}

// Other injection points excluded, but follow the same pattern as above

Helpers

Some common scanning and reflection helpers.

Edit
public static class Resolver
{
    static List<Assembly> assemblies;

    static Resolver()
    {
        var codebase = typeof(Resolver).Assembly.CodeBase.Remove(0, 8);
        var currentDirectory = Path.GetDirectoryName(codebase);
        assemblies = Directory.GetFiles(currentDirectory, "*.dll")
            .Select(Assembly.LoadFrom)
            .Where(ReferencesShared)
            .ToList();
    }

    static bool ReferencesShared(Assembly assembly)
    {
        return assembly.GetReferencedAssemblies()
            .Any(name => name.Name == "Shared");
    }

    public static void Execute<T>(Action<T> action)
    {
        foreach (var assembly in assemblies)
        {
            foreach (var type in assembly.GetImplementationTypes<T>())
            {
                var instance = (T) Activator.CreateInstance(type);
                action(instance);
            }
        }
    }

    static IEnumerable<Type> GetImplementationTypes<TInterface>(this Assembly assembly)
    {
        return assembly.GetTypes().Where(IsConcreteClass<TInterface>);
    }

    static bool IsConcreteClass<TInterface>(Type type)
    {
        return typeof(TInterface).IsAssignableFrom(type) &&
               !type.IsAbstract &&
               !type.ContainsGenericParameters &&
               type.IsClass;
    }
}

Example extension implementations

Edit
public class CustomizeConfiguration :
    ICustomizeConfiguration
{
    static ILog log = LogManager.GetLogger<CustomizeConfiguration>();

    public void Run(BusConfiguration busConfiguration)
    {
        log.Info("Setting serializer to JSON in an extension");
        busConfiguration.UseSerialization<JsonSerializer>();
    }
}
Edit
public class RunAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<RunAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Endpoint Started.");
    }
}
Edit
public class SendMessageAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<SendMessageAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Sending Message.");
        var myMessage = new MyMessage();
        bus.SendLocal(myMessage);
    }
}

Managed Extensibility Framework (MEF)

This approach uses MEF to discover and execute the extension points.

Endpoint Startup

Edit
static void Main()
{
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new DirectoryCatalog("."));

    var compositionContainer = new CompositionContainer(catalog);

    Console.Title = "Samples.MefExtensionEndpoint";
    var busConfiguration = new BusConfiguration();
    busConfiguration.EndpointName("Samples.MefExtensionEndpoint");
    busConfiguration.UsePersistence<InMemoryPersistence>();
    busConfiguration.EnableInstallers();
    RunCustomizeConfiguration(compositionContainer, busConfiguration);
    RunBeforeEndpointStart(compositionContainer);
    using (var bus = Bus.Create(busConfiguration).Start())
    {
        RunAfterEndpointStart(compositionContainer, bus);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
        RunBeforeEndpointStop(compositionContainer, bus);
    }
    RunAfterEndpointStop(compositionContainer);
}

static void RunBeforeEndpointStart(CompositionContainer compositionContainer)
{
    compositionContainer.ExecuteExports<IRunBeforeEndpointStart>(_ => _.Run());
}

// Other injection points excluded, but follow the same pattern as above

Helpers

Some common MEF helpers.

Edit
public static class MefExtensions
{
    public static void ExecuteExports<T>(this CompositionContainer container, Action<T> action)
    {
        foreach (var export in container.GetAllExports<T>())
        {
            action(export);
        }
    }
    static IEnumerable<T> GetAllExports<T>(this CompositionContainer container)
    {
        return container.GetExports<T>().Select(x => x.Value);
    }
}

Example extension implementations

Edit
[Export(typeof(ICustomizeConfiguration))]
public class CustomizeConfiguration :
    ICustomizeConfiguration
{
    static ILog log = LogManager.GetLogger<CustomizeConfiguration>();

    public void Run(BusConfiguration busConfiguration)
    {
        log.Info("Setting serializer to JSON in an extension");
        busConfiguration.UseSerialization<JsonSerializer>();
    }
}
Edit
[Export(typeof(IRunAfterEndpointStart))]
public class RunAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<RunAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Endpoint Started.");
    }
}
Edit
[Export(typeof(IRunAfterEndpointStart))]
public class SendMessageAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<SendMessageAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Sending Message.");
        var myMessage = new MyMessage();
        bus.SendLocal(myMessage);
    }
}

Other notes

Using an IOC container

Both the above approaches could be made more extensible and versatile by leveraging a container. In the case of MEF most containers have custom integrations, for example Autofac.Mef. For the custom reflection approach the standard reflection based APIs of a given container would be used.

Adding more extensions points

More extension points could be defined based on requirements. The extensions could be plugged into part of the NServiceBus lifecycle or the pipeline. Also more context could be passed, as parameters, to any give extension point.

Related Articles


Last modified