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