Wednesday, May 06, 2009

Windsor WCF Facility: MessageAction and MessageEnvelopeAction

This is the fifth in a series of posts featuring the WCF Facility:

Windsor WCF Integration
WCF / Windsor Integration: Using the perWebRequest lifestyle
WCF / Windsor Integration: Adding Behaviours
Atom feeds with the Windsor WCF Facility

Download the code for this post here:
http://static.mikehadlow.com/Suteki.Blog.zip

MessageAction and MessageEnvelopeAction provide an easy way to intercept WCF service or client operations.

In a previous post, I showed how we could configure WCF Behaviours using the WCF Facility. I commented that WCF Behaviours have a difficult API:

“As an aside, I can't help feeling that the WCF behaviour API is overly complex, it hardly invites you in. There's no way I would have worked out how to do this without looking at the documentation. Not at all the pit of success.”

If you want to simply process a message, or do some action before and after each operation, you first have to create an instance of IEndpointBehavior and then use it to register an IClientMessageInspector or an IDispatchMessageInspector.

Now there’s a much easier way. Craig Neuwirt, the author of the WCF Facility, has added a number of new concepts; MessageAction, MessageEnvelopeAction and MessageLifecyle. These make it very easy to intercept WCF operations.

Let’s use a MessageAction to intercept a WCF operation. Here’s a simple example:

 

   1: using System;
   2: using System.Collections;
   3: using System.ServiceModel.Channels;
   4: using Castle.Facilities.WcfIntegration.Behaviors;
   5:  
   6: namespace Suteki.Blog.ConsoleService.Wcf
   7: {
   8:     public class LifestyleMessageAction : AbstractMessageAction
   9:     {
  10:         private readonly string stateToken = Guid.NewGuid().ToString();
  11:  
  12:         public LifestyleMessageAction()
  13:             : base(MessageLifecycle.All)
  14:         {
  15:         }
  16:  
  17:         public override bool Perform(ref Message message, MessageLifecycle lifecycle, IDictionary state)
  18:         {
  19:             if (lifecycle == MessageLifecycle.IncomingRequest)
  20:             {
  21:                 state.Add(stateToken, "This state has been stored for the duration of the request");
  22:             }
  23:             if (lifecycle == MessageLifecycle.OutgoingResponse)
  24:             {
  25:                 Console.WriteLine(state[stateToken]);
  26:             }
  27:  
  28:             Console.WriteLine("Perform called at lifecycle: {0}", lifecycle);
  29:             return true;
  30:         }
  31:     }
  32: }

Simply create a class that inherits AbstractMessageAction, pass in the MessageLifecyle you want to intercept and implement the Perform method. The MessageLifecycle has the following values:

   1: /// <summary>
   2: /// Specifies the lifecycle of a message.
   3: /// </summary>
   4: [Flags]
   5: public enum MessageLifecycle
   6: {
   7:     /// <summary>
   8:     /// The outgoing request.
   9:     /// </summary>
  10:     OutgoingRequest = 0x01,
  11:     /// <summary>
  12:     /// The incoming response.
  13:     /// </summary>
  14:     IncomingResponse = 0x02,
  15:     /// <summary>
  16:     /// The outgoing request.
  17:     /// </summary>
  18:     IncomingRequest = 0x04,
  19:     /// <summary>
  20:     /// The incoming response.
  21:     /// </summary>
  22:     OutgoingResponse = 0x08,
  23:     /// <summary>
  24:     /// All incoming messages.
  25:     /// </summary>
  26:     IncomingMessages = IncomingRequest | IncomingResponse,
  27:     /// <summary>
  28:     /// All outgoing messages.
  29:     /// </summary>
  30:     OutgoingMessages = OutgoingRequest | OutgoingResponse,
  31:     /// <summary>
  32:     /// A solitic/response exchange.
  33:     /// </summary>
  34:     OutgoingRequestResponse = OutgoingRequest | IncomingResponse,
  35:     /// <summary>
  36:     /// A request/response exchange.
  37:     /// </summary>
  38:     IncomingRequestResponse = IncomingRequest | OutgoingResponse,
  39:     /// <summary>
  40:     /// All requests.
  41:     /// </summary>
  42:     Requests = IncomingRequest | OutgoingRequest,
  43:     /// <summary>
  44:     /// All requests.
  45:     /// </summary>
  46:     Responses = IncomingResponse | OutgoingResponse,
  47:     /// <summary>
  48:     /// All message.
  49:     /// </summary>,
  50:     All = IncomingMessages | OutgoingMessages
  51: }

You can choose to intercept any combination of client or server requests or responses. Here I’m passing MessageLifecycle.All, saying I want to listen to both requests and responses on both client and server components.

The Perform method is called on the MessageLifecycle steps you specify. Since I’m going to configure my LifestyleMessageAction on a server component and I’ve specified MessageLifecycle.All I’m expecting Perform to be called After the request is received by the server and before the reply is sent.

Perform takes the WCF Message, the MessageLifecycle and an IDictionary instance that you can use to pass state between message actions. Here I’ve stored a string value in the state dictionary when the request was received and retrieved it when the response was sent, so that we can see state being preserved through the lifetime of the call.

Here is the Windsor configuration:

   1: public static IWindsorContainer Build()
   2: {
   3:     return new WindsorContainer()
   4:         .AddFacility<WcfFacility>()
   5:         .Register(
   6:             Component.For<MessageLifecycleBehavior>(),
   7:             Component.For<LifestyleMessageAction>(), // adds this message action to all endpoints
   8:             Component
   9:                 .For<IBlogService>()
  10:                 .ImplementedBy<DefaultBlogService>()
  11:                 .Named("blogService")
  12:                 .LifeStyle.Transient
  13:                 .ActAs(new DefaultServiceModel()
  14:                     .AddEndpoints(WcfEndpoint
  15:                         .BoundTo(new NetTcpBinding())
  16:                         .At("net.tcp://localhost/BlogService")
  17:                         )),
  18:             Component
  19:                 .For<ILogger>()
  20:                 .ImplementedBy<DefaultLogger>()
  21:                 .LifeStyle.Transient
  22:         );
  23:    
  24: }

I’m serving my DefaultBlogService using the WcfFacility over a NetTcpBinding (see my previous WCF Facility posts for the details on the configuration). All MessageActions rely on the MessageLifecycleBehavior to register them with WCF. The MessageLifecycleBehavior is an IEndpointBehavior that hosts MessageActions so you need to register it with the container.

If you register your MessageAction as a component, as I have done here, it is applied to every operation of every service or client hosted by the WCF Facility. Alternatively, if you only want the MessageAction to apply to a particular endpoint, you can add it directly as an endpoint extension:

   1: public static IWindsorContainer Build()
   2: {
   3:     return new WindsorContainer()
   4:         .AddFacility<WcfFacility>()
   5:         .Register(
   6:             Component.For<MessageLifecycleBehavior>(),
   7:             Component
   8:                 .For<IBlogService>()
   9:                 .ImplementedBy<DefaultBlogService>()
  10:                 .Named("blogService")
  11:                 .LifeStyle.Transient
  12:                 .ActAs(new DefaultServiceModel()
  13:                     .AddEndpoints(WcfEndpoint
  14:                         .BoundTo(new NetTcpBinding())
  15:                         .At("net.tcp://localhost/BlogService")
  16:                         // adds this message action to this endpoint
  17:                         .AddExtensions(new LifestyleMessageAction()) 
  18:                         )),
  19:             Component
  20:                 .For<ILogger>()
  21:                 .ImplementedBy<DefaultLogger>()
  22:                 .LifeStyle.Transient
  23:         );
  24:    
  25: }

Running the simple console hosted service gives this result:

console_service

We can see that two operations were executed, one to add a post and one to return a post. We can see the console output for the IncomingRequest and the OutgoingResponse for each operation, and we can see that the state was persisted through the lifetime of the operation.

MessageAction provides the ideal place to implement operation lifetime concerns. I’m going to look at using it to implement a custom Windsor LifestyleManager and a UnitOfWork per operation component, much as Andreas Ohlund has done with WCF and StructureMap.

1 comment:

Anonymous said...

This might be easier but you'd need to look at documentation anyway since how else would you know about the functionality? :)