Using metadata and reflection to dynamically manage message routing

 

Most routing systems have a transformation phase where, based on its current state, a message is transformed into a document and routed to an endpoint.  Systems such as BizTalk provide GUI's and designers to remove the need for cumbersome coding by making the rules and subsequent transformations configurable; here's an example of a switch statement in a listener class where the rules of the routing engine are hard-coded:

 

public static void MessageArrived( Message message ) {
  switch( message.MessageState ) {
    case MessageState.Initial:
        Console.Write( Transformer.CreateRequest( message ) );
        break ;
    case MessageState.Submitted:
        Console.Write( Transformer.CreateApproval( message ) );
        break ;
    case MessageState.Approved:
        Console.Write( Transformer.CreateInvoice( message ) );
        break ;
    case MessageState.Saved:
        Console.Write( Transformer.CreateReport( message ) );
        break ;
    default:
        Console.Write( Notifier.NotifyError( message ) );
    break ;

  }
}

 

If there's extra "noise" in the MessageArrived method, it can become hard to maintain as the length of the switch gets longer.  It can also become hard to maintain if there are repetitive code chunks within each case.

 

In the above cases you can - at a moderate performance cost - re-factor the common code away into a generic method.  Looking at the above example, one neat way to achieve this is to ascribe metadata to the MessageState enum so that it can be inspected at runtime and the routing lookup driven from that metadata.  First, let's create an attribute to contain our lookup data and add it to the MessageState enum:

 

[AttributeUsage(AttributeTargets.Field, Inherited=false, AllowMultiple=true)]
public class WorkflowAttribute : Attribute {
  public WorkflowAttribute(Type type, string methodName) {
    this.Type = type ;
    this.MethodName = methodName ;
  }

  public Type Type;
  public string MethodName ;
}


public enum MessageState : short {

  [WorkflowAttribute(typeof(Transformer), "CreateRequest")]
  Initial = 1,

  [WorkflowAttribute(typeof(Transformer), "CreateApproval")]
  Submitted = 2,

  [WorkflowAttribute(typeof(Transformer), "CreateInvoice"),

   WorkflowAttribute(typeof(Notifier), "NotifySalesGuy")]
  Approved = 3,

  [WorkflowAttribute(typeof(Transformer), "CreateReport")]
  Saved = 4,

  [WorkflowAttribute(typeof(Notifier), "NotifyError")]
  Unknown = short.MaxValue
}

 

Notice that I applied 2 worklow attributes to the MessageState.Submitted enum value.

 

Now I can re-factor the original MessageArrived method into a generic message handler routine:

 

public static void MessageArrived( Message message ) {

  MessageState state = message.MessageState ;
  FieldInfo field = state.GetType().GetField(state.ToString()) ;

  object[] attribs = field.GetCustomAttributes(typeof(WorkflowAttribute), false) ;

  for( int i=0; i<attribs.Length; i++ ) {
    WorkflowAttribute att = attribs[i] as WorkflowAttribute ;
    if( att != null ) {
      MethodInfo method = null ;

      if( att.Type.GetMethod(att.MethodName).IsStatic ) {
        method = att.Type.GetMethod(att.MethodName) ;
        Console.Write(method.Invoke(null, new object[] {message}));
      }else{
        object instance = Activator.CreateInstance(att.Type) ;
        method = instance.GetType().GetMethod(att.MethodName);
        Console.Write(method.Invoke(instance, new object[] {message}));
      }
    }
  }
}

 

 

I've uploaded a working demo of this to ProjectDistributor:

 

    http://projectdistributor.net/Releases/Release.aspx?releaseId=82

6 Comments

  • Hey man, your post reminded me of something I've been meaning to write about for a while. Hope you don't mind :)

  • Daren,



    There is a much more efficient (and elegant) solution to this if you plan to use Whidbey. Altough the reflection solution is very as well.



    You could use the new Lightweight Code Generation feature to &quot;dynamically&quot; create the switch statement at runtime. Once the dynamec method is created, you could call it without performance hit (assuming that you are as good as the compiler to generate the switch )



    DynamicMethod is the class name that implements LCG.

  • Sow nothing, reap nothing.

    -----------------------------------

  • The wealth of the mind is the only wealth.

    -----------------------------------

  • -----------------------------------------------------------
    "I happen to be studying your entries all through my afternoon escape, and i need to admit the whole post has been extremely enlightening and very properly composed. I considered I would let you know that for some reason this blog site doesn't exhibit nicely in Internet Explorer eight. I wish Microsoft would stop altering their software. I have a question for you personally. Do you thoughts exchanging blog roll links? That will be actually cool! "

  • 357029.. Huh, really? :)

Comments have been disabled for this content.