Comfortably Dumb

Typical blog fare for family, work, gadgets, music, and games.

Windows Communication Foundation Extensibility

If Microsoft (MS) is nothing else, it is a platform company. At the core of almost everything it does, MS provides a foundation that can used simultaneously by end-users and vendors producing a wide-range of products. Windows Communication Foundation (WCF) is just such a platform. The WCF product team has gone through great lengths to streamline the development and configuration experience for their most frequent use-case: enterprise developers trying to hookup application tiers in a line-of-business application. In fact, they have made it almost trivial. These are the end-users. However, for developers looking to take advantage of everything the platform has to offer, like tool vendors, or enterprises with very precise requirements, they will have ample opportunity to get their hands dirty. The extensibility model and all it is has to offer is a great opportunity for system integrators to develop best practices and approaches for a multitude of scenarios. Service Behaviors and Operation Behaviors are both excellent mechanisms to plug into the WCF infrastructure. Service Behaviors are scoped at the contract level and are applied to all operations. The beauty here is that you can modify your service’s behavior without code changes. In this example, I’m going to create a custom Service Behavior, so I can watch when requests are being dispatched to my service. This will allow me to validate whether or not the Caching Operation Behavior I am going to create works properly or not. The benefit of Operation Behaviors is that they are scoped at the operation level allowing you to fine tune the way your service responds to requests. I’m going to implement my caching behavior as an attribute that won’t require additional code in my business logic or any additional configuration changes. Building upon my service that returns the list of metro markets in Avanade’s South Region, you can see what the developer experience is when using this CachingAspect attribute.

   1:          [CachingAspect]
   2:          public GetMetroListResponse GetMetroList(GetMetroListRequest request)
   3:          {
   4:              Console.WriteLine("MetroManagerService.GetMetroList");
   5:   
   6:              Database db = DatabaseFactory.CreateDatabase();
   7:   
   8:              string sqlCommand = "SELECT id, name FROM metro ORDER BY name;";
   9:              DbCommand dbCommand = db.GetSqlStringCommand(sqlCommand);
  10:   
  11:              GetMetroListResponse response = new GetMetroListResponse();
  12:              response.MetroList = new List<Metro>();
  13:              
  14:              using (IDataReader dataReader = db.ExecuteReader(dbCommand))
  15:              {
  16:                  while (dataReader.Read())
  17:                  {
  18:                      Metro metro = new Metro();
  19:                      metro.Id = dataReader.GetInt32(0);
  20:                      metro.Name = dataReader.GetString(1);
  21:   
  22:                      response.MetroList.Add(metro);
  23:                  }
  24:              }
  25:              
  26:              return response;
  27:          }
  28:      }

This implementation is straightforward and will simply cache the response from the first request using the Enterprise Library 2.0 Caching Application Block. Implementing different expiration strategies is an exercise left to the reader. On line 4 you can see we write to output to indicate that the method is being executed. The key take-away here is that there is no caching logic embedded with the business logic. When you run the server console application you can see that the service implementation is invoked on the first call only.

Even though the service implementation is called once, the client console continues to receive a valid response.

Even a simple example demonstrates the power of tapping into the WCF extension model. To make this happen we need to create our attribute:

   1:      public class CachingAspectInvoker : IOperationInvoker
   2:      {
   3:          CacheManager cache = CacheFactory.GetCacheManager();
   4:          IOperationInvoker innerOperationInvoker;
   5:   
   6:          public CachingAspectInvoker(IOperationInvoker innerOperationInvoker)
   7:          {
   8:              this.innerOperationInvoker = innerOperationInvoker;
   9:          }
  10:   
  11:          public object[] AllocateInputs()
  12:          {
  13:              return this.innerOperationInvoker.AllocateInputs();
  14:          }
  15:   
  16:          public object Invoke(object instance, 
  17:              object[] inputs, out object[] outputs)
  18:          {
  19:              string key = string.Empty;
  20:   
  21:              foreach (object obj in inputs)
  22:              {
  23:                  if (obj != null) key += obj.ToString();
  24:                  key += ";";
  25:              }
  26:   
  27:              object value = cache.GetData(key);
  28:   
  29:              if (value != null)
  30:              {
  31:                  outputs = new object[0];
  32:                  return value;
  33:              }
  34:   
  35:              value = this.innerOperationInvoker.Invoke(instance, inputs, out outputs);
  36:              cache.Add(key, value);
  37:              return value;
  38:          }
  39:   
  40:          public IAsyncResult InvokeBegin(object instance, object[] inputs, 
  41:              AsyncCallback callback, object state)
  42:          {
  43:              return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
  44:          }
  45:   
  46:          public object InvokeEnd(object instance, 
  47:              out object[] outputs, IAsyncResult result)
  48:          {
  49:              return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
  50:          }
  51:   
  52:          public bool IsSynchronous
  53:          {
  54:              get { return innerOperationInvoker.IsSynchronous; }
  55:          }
  56:      }

We generate a key in the Invoke method and check for its existence in the cache. If it is there, then we return the value immediately otherwise we invoke the service implementation and store that response in the cache. In a real implementation you would probably want to serialize your request instead, but this IS just a demo. Next, use the Enterprise Library (EntLib) 2.0 Configuration Tool to add the Caching Application Block to your App.config.

By all accounts, our service seems to work in that it return the data we are expecting. However, we still need to validate that our request is actually being cached. Now, to validate that our caching is working we are going to use a Service Behavior. To do this, we need to create a class that understands the behavior extension element we are going to use in our configuration file.

   1:      public class MessageInspectorConfig : BehaviorExtensionSection
   2:      {
   3:          protected override object CreateBehavior()
   4:          {
   5:              return new MessageInspectorBehavior();
   6:          }
   7:   
   8:          protected override void DeserializeElement(XmlReader reader, 
   9:              bool serializeCollectionKey)
  10:          {
  11:              base.DeserializeElement(reader, serializeCollectionKey);
  12:          }
  13:   
  14:          public override string ConfiguredSectionName
  15:          {
  16:              get { return "messageInspector"; }
  17:          }
  18:      }

The MessageInspectorConfig class is solely responsible for understanding the XML configuration and serving as a factory for the actual behavior. This design allows you easily manage extremely complex behaviors with several different strategy implementations. Here I’m going to apply an implementation of message inspector to all the contracts implemented by this service.

   1:      public class MessageInspectorBehavior : IServiceBehavior
   2:      {
   3:          public void ApplyBehavior(ServiceDescription description, 
   4:              ServiceHostBase serviceHostBase, 
   5:              Collection<DispatchBehavior> behaviors, 
   6:              Collection<BindingParameterCollection> parameters)
   7:          {
   8:              foreach (DispatchBehavior behavior in behaviors)
   9:              {
  10:                  Console.WriteLine(behavior.ContractName);
  11:                  behavior.MessageInspectors.Add(new MessageInspector());
  12:              }
  13:          }
  14:      }

This is a message inspector for the stub and their is a separate interface, IProxyMessageInspector, for the proxy.

   1:      public class MessageInspector : IStubMessageInspector
   2:      {
   3:          public object AfterReceiveRequest(ref Message request, 
   4:              IClientChannel channel, InstanceContext instanceContext)
   5:          {
   6:              Console.WriteLine("AfterReceiveRequest");
   7:              return null;
   8:          }
   9:   
  10:          public void BeforeSendReply(ref Message reply, object correlationState)
  11:          {
  12:              Console.WriteLine("BeforeSendReply");
  13:              Console.WriteLine();
  14:          }
  15:      }

The AfterReceiveRequest method is called after WCF receives the request but before it is dispatched to your service. The BeforeSendReply method is called after your service has executed the request but before WCF sends the reply. Obvious stuff, but shows the care that went into designing the object model. To wire all this up, we need to modify our App.config file.

   1:    <system.serviceModel>
   2:      <services>
   3:        <service type="Test00.Services.MetroManagerService" 
   4:                 behaviorConfiguration="test00Behavior">
   5:          <endpoint contract="Test00.Contracts.Services.IMetroManagerService" 
   6:                    binding="basicHttpBinding" />
   7:        </service>
   8:      </services>
   9:      <behaviors>
  10:        <behavior name="test00Behavior">
  11:          <messageInspector />
  12:        </behavior>
  13:      </behaviors>
  14:      <extensions>
  15:        <behaviorExtensions>
  16:          <add name="messageInspector" 
  17:               type="Test00.WinFxExtensions.MessageInspectorConfig, Test00.WinFxExtensions" />
  18:        </behaviorExtensions>
  19:      </extensions>
  20:    </system.serviceModel>

As part of setting up our service we specify a behaviorConfiguration which tells WCF to grab the element on line 8. As WCF iterates over those elements it does not understand it looks at the behaviorExtensions element to see if there is an extension that can understand that element. Extending WCF is extremely powerful and there is a lot of opportunity to innovate on top of what Microsoft has provided out of the box. In this short example we created an Operation Behavior and a Service Behavior to implement response caching and to verify that our cache works properly.

-CP

Technorati Profile
Published Sunday, January 22, 2006 11:12 AM by Chris
Filed under:

Comments

# re: Windows Communication Foundation Extensibility@ Friday, February 23, 2007 6:25 AM

Great article Chris! Do you have the demo code?

by Luis

# re: Windows Communication Foundation Extensibility@ Friday, February 23, 2007 10:28 AM

If you send an email to chris@<whateverthisdomainis>.com I'll remember to hunt for it when I get home.

by Chris

# re: Windows Communication Foundation Extensibility@ Friday, February 23, 2007 10:29 AM

I just noticed I posted this over a year ago. You may need to refactor a bit to get it to work with the RTM bits.

by Chris

# Links for 2007-07-09 [del.icio.us]@ Tuesday, January 22, 2008 5:25 PM

Windows Communication Foundation Extensibility - The Board

# re: Windows Communication Foundation Extensibility@ Sunday, August 17, 2008 10:12 PM

Your blog is interesting!

Keep up the good work!

by AlexM

Leave a Comment

(required) 
(required) 
(optional)
(required)