Dynamic Import

Jul 23, 2010 at 6:23 PM
I am developing a windows service for processing various tasks that are posted to MSMQ. Currently I'm using Reflection to determine the correct class and method to call, which means I have to recompile and push to production every time I add a new task processor. How can I accomplish this using MEF? This project is far from completion and I am at a stage where the architecture can be easily changed. Current code (take a look at msgQ_ReceiveCompleted): public partial class SPS : ServiceBase { Timer timer = new Timer(); private static MessageQueue msgQ = null; public SPS() { InitializeComponent(); } void timer_Elapsed(object sender, ElapsedEventArgs e) { try { timer.Stop(); StartProcessing(); } catch (Exception ex) { Log("Elapsed: " + ex.Message); } } protected override void OnStart(string[] args) { try { timer.Interval = 1000; timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); timer.AutoReset = true; timer.Enabled = true; timer.Start(); } catch (Exception e) { Log("OnStart: " + e.Message); } } protected override void OnStop() { try { msgQ.Close(); timer.AutoReset = false; timer.Enabled = false; } catch (Exception e) { Log("OnStop: " + e.Message); } //TODO: Finish anything that is currently being processed } public static void StartProcessing() { string queuePath = Sitewire.ConfigurationManager.AppSettings.Get("MSMQ"); InsureQueueExists(queuePath); msgQ = new MessageQueue(queuePath) { Formatter = new BinaryMessageFormatter() }; msgQ.MessageReadPropertyFilter.SetAll(); msgQ.ReceiveCompleted += msgQ_ReceiveCompleted; msgQ.BeginReceive(); } public static void InsureQueueExists(string queuePath) { if (!MessageQueue.Exists(queuePath)) { MessageQueue.Create(queuePath); } } static void msgQ_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e) { string[] messageLabelContents = e.Message.Label.Split(':'); Processor processor = new Processor(); //TODO: Error handling!!!! MethodInfo retrievedMethod = processor.GetType().GetMethod(messageLabelContents[0]); retrievedMethod.Invoke(processor, new[] { e.Message.Body }); //TODO: on error push to error queue. don't forget to check if queue exists!!!! // Listen for the next message. msgQ.BeginReceive(); } private void Log(string msg) { File.AppendAllText(@"C:\servicelog.txt", DateTime.Now + " " + msg + Environment.NewLine); } }
Jul 26, 2010 at 7:45 AM

MEF has a nice discovery mechanism which enables powerful plug-in/add-on scenarios. And if you established a chain of responsibility you could have MEF discover these processors and execute the appropriate one. 

Chain of responsibility is tricky though because it can end up with very greedy processors that tries to process things they shouldn't. This makes the add-on situation harder. Design your processors with that in mind.

It would look something like this:

[InheritedExport]
public interface IProcessor
{
    bool CanProcess();
    void Process();
}

class Program
{
    [ImportMany]
    IEnumerable<IProcessor> Processors { get; set; }

    static void Main()
    {
        foreach(var p in Processors)
        {
            if(p.CanProcess())
            {
                p.Process();
            }
        }
    }
}

The neat part is that you'll be able to add dependencies to the MEF container (CompositionContainer). When you construct these processors with MEF, they can access context and other helper objects through the container. Depending on how you design this you'll get a very flexible and testable plug-in architecture. You could do this:

class MyProcessor : IProcessor
{
    [Import]
    public IContext Context { get; set; }

    public bool CanProcess()
    { 
        return true; 
    }

    public void Process() 
    {
    }
}

The above class is constructed by the CompositionContainer which will satisfy the import (if possible). This way, by configuring the container you can wrap, or export additional functionality to your processors without big breaking code changes. 

I would avoid dynamic recomposition if you don't absolutely think you need it, it will only complicate things.

Jul 26, 2010 at 4:15 PM
Problem is, the class name will be coming in through an MSMQ message label. When the message is received, a new thread is spawned and the process begins from that thread. So, storing the modules in a collection is not thread safe. What I really need to do is have the ability to load a module by name (sent in the message label) then call the "Process" method using the MSMQ message body as an argument. It's multi-threaded so it will need a new "IProcessor" object each time a message is received; except in cases where a singleton is required. I currently have a global collection that keeps track of what is running and logic that makes sure certain tasks are not duplicated depending on a method attribute.
Jul 26, 2010 at 5:05 PM
Edited Jul 26, 2010 at 5:13 PM

In that case you need to use ExportFactory and metadata. This blog post by Nicholas Blumhardt about Dynamic Part Instantiation in MEF has a "RequestHandler" example which is probably almost exactly what you need.

Note that since that blog post was written, PartCreator was renamed to ExportFactory. The codeplex documentation currently doesn't have an example of importing ExportFactory+metadata, but it works much like importing System.Lazy<T, TMetadata>.

Jul 26, 2010 at 6:02 PM
Ok, I'll take a look. Thanks for the quick reply.