Customization with MEF or Reflection

Component: NServiceBus
NuGet Package NServiceBus (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:

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

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.

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

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

    public void Run(BusConfiguration busConfiguration)
    {
        log.Info("Setting serializer to XML in an extension");
        busConfiguration.UseSerialization<XmlSerializer>();
    }
}
public class RunAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<RunAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Endpoint Started.");
    }
}
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

static void Main()
{
    Console.Title = "Samples.MefExtensionEndpoint";

    var containerConfiguration = new ContainerConfiguration();
    var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    var assemblies = Directory.EnumerateFiles(location, "*.dll")
        .Select(Assembly.LoadFrom);
    containerConfiguration.WithAssemblies(assemblies);
    var compositionHost = containerConfiguration.CreateContainer();

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

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

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

Helpers

Some common MEF helpers.

public static class MefExtensions
{
    public static void ExecuteExports<T>(this CompositionHost compositionHost, Action<T> action)
    {
        foreach (var export in compositionHost.GetExports<T>())
        {
            action(export);
        }
    }
}

Example extension implementations

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

    public void Run(BusConfiguration busConfiguration)
    {
        log.Info("Setting serializer to XML in an extension");
        busConfiguration.UseSerialization<XmlSerializer>();
    }
}
[Export(typeof(IRunAfterEndpointStart))]
public class RunAfterEndpointStart :
    IRunAfterEndpointStart
{
    static ILog log = LogManager.GetLogger<RunAfterEndpointStart>();
    public void Run(IBus bus)
    {
        log.Info("Endpoint Started.");
    }
}
[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 dependency injection

Both the above approaches could be made more extensible and versatile by leveraging dependency injection. 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