Castle Windsor WCF Lifestyle – PerWcfMessageLifestyleManager

If you want your resolved instances bound to the life time of a WCF operation then you can use the PerWebRequest lifestyle that comes with Windsor.  However, this means making your services run in ASP.Net compatibility mode.  This might not be ideal for your set of circumstances (as it wasn’t for mine).  I did a great deal of googling around this subject and I couldn’t find anything someone had already done, so I thought I’d share this solution.  Be great to get some feedback…

Updated – thanks to Krzysztof Koźmic for pointing out that the WCF facility for Windsor already has this, see here (I obviously didn’t google that hard).

The solution works by plugging into to some of the extension points within WCF.  I created a service behaviour (IServiceBehavior) which applies a Message Inspector to all operations (IDispatchMessageInspector).  That message inspector does two things.  For each new call it adds in a extension (IExtension<OperationContext>) to the operation context.  This extension is where I’m going to stick all the instances which are bound to the operation.  The second is to evict all instances from the container once the operation has completed.

All the lifestyle manager has to do is to check if there’s already an instance registered with the operation context (by looking in our custom extension).  If it finds one, then it is returned, if not it will Resolve a new instance.  This should give the same result as using PerWebRequest + ASP.Net compatibility mode…

Here’s all the code.  Hope it helps!

Code for plugging into WCF:

   1: public class LifestyleManagerMessageInspector : IDispatchMessageInspector

   2: {

   3:     public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)

   4:     {

   5:         OperationContext.Current.Extensions.Add(new MessageLifespanInstancesExtension());

   6:         return null;

   7:     }

   8:  

   9:     public void BeforeSendReply(ref Message reply, object correlationState)

  10:     {

  11:         var extenstion = OperationContext.Current.Extensions.Find<MessageLifespanInstancesExtension>();

  12:         if (extenstion == null)

  13:             throw new NotSupportedException("Message reply is being sent but there is no MessageLifespanInstancesExtenstion registered.  This implies that AfterReceiveRequest was not callled...");

  14:         

  15:         extenstion.EvictAll();

  16:     }

  17: }

  18:  

  19: public class LifestyleManagerServiceBehaviour : BehaviorExtensionElement, IServiceBehavior

  20: {

  21:     public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

  22:     {

  23:     }

  24:  

  25:     public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)

  26:     {

  27:     }

  28:  

  29:     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

  30:     {

  31:         foreach (var endpointDispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>()

  32:             .SelectMany(channelDispatcher => channelDispatcher.Endpoints))

  33:         {

  34:             endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new LifestyleManagerMessageInspector());

  35:         }

  36:     }

  37:  

  38:     protected override object CreateBehavior()

  39:     {

  40:         return new LifestyleManagerServiceBehaviour();

  41:     }

  42:  

  43:     public override Type BehaviorType

  44:     {

  45:         get { return (typeof (LifestyleManagerServiceBehaviour)); }

  46:     }

  47: }

  48:  

  49: public class MessageLifespanInstancesExtension : IExtension<OperationContext>

  50: {

  51:     private readonly Dictionary<PerWcfMessageLifestyleManager, object> instances = new Dictionary<PerWcfMessageLifestyleManager, object>();

  52:  

  53:     public void AddInstance(PerWcfMessageLifestyleManager lifestyleManager, object instance)

  54:     {

  55:         instances.Add(lifestyleManager, instance);

  56:     }

  57:  

  58:     public object FindInstance(PerWcfMessageLifestyleManager lifestyleManager)

  59:     {

  60:         object instance;

  61:         instances.TryGetValue(lifestyleManager, out instance);

  62:         return instance;

  63:     }

  64:  

  65:     public void EvictAll()

  66:     {

  67:         foreach (var item in instances.ToArray())

  68:         {

  69:             item.Key.Evict(item.Value);

  70:         }

  71:     }

  72:  

  73:     public void Attach(OperationContext owner)

  74:     {

  75:     }

  76:  

  77:     public void Detach(OperationContext owner)

  78:     {

  79:     }

  80:  

  81:     public void RemoveInstance(PerWcfMessageLifestyleManager lifestyleManager)

  82:     {

  83:         instances.Remove(lifestyleManager);

  84:     }

  85: }

You’ll need this bit of XML config.  I will at some point change my stuff to use the WCF Facility in Castle, once I’ve done that I can add the behaviour programmatically…

   1: <behaviors>

   2:   <serviceBehaviors>

   3:     <behavior>

   4: ...

   5:       <lifestyleManagerServiceBehaviour />

   6:     </behavior>

   7:   </serviceBehaviors>

   8: </behaviors>

   9: <extensions>

  10:   <behaviorExtensions>

  11:     <add name="lifestyleManagerServiceBehaviour" type="IEMobile.Ioc.WCF.LifestyleManagerServiceBehaviour, IEMobile.Ioc" />

  12:   </behaviorExtensions>

  13: </extensions>

  14: ...

And plugging into castle:

   1: public class PerWcfMessageLifestyleManager : AbstractLifestyleManager

   2: {

   3:     private readonly string perMessageKey = "PerWcfMessage_" + Guid.NewGuid();

   4:     private bool evicting = false;

   5:  

   6:     public MessageLifespanInstancesExtension Extension

   7:     {

   8:         get

   9:         {

  10:             var extenstion = OperationContext.Current.Extensions.Find<MessageLifespanInstancesExtension>();

  11:             if (extenstion == null)

  12:                 throw new NotSupportedException("Cannot find MessageLifespanInstancesExtenstion, check you've registered the LifestyleManagerMessageInspector in the web/app.config");

  13:             return extenstion;

  14:         }

  15:     }

  16:  

  17:     public override object Resolve(Castle.MicroKernel.CreationContext context)

  18:     {

  19:         var instance = Extension.FindInstance(this);

  20:  

  21:         if (instance == null)

  22:         {

  23:             instance = base.Resolve(context);

  24:             Extension.AddInstance(this, instance);

  25:         }

  26:  

  27:         return instance;

  28:     }

  29:  

  30:     public override bool Release(object instance)

  31:     {

  32:         if (!evicting) return false;

  33:  

  34:         Extension.RemoveInstance(this);

  35:  

  36:         return base.Release(instance);

  37:     }

  38:  

  39:     public void Evict(object value)

  40:     {

  41:         using (new EvictionContext(this))

  42:         {

  43:             Release(value);

  44:         }

  45:     }

  46:  

  47:     private class EvictionContext : IDisposable

  48:     {

  49:         private readonly PerWcfMessageLifestyleManager lifestyleManager;

  50:  

  51:         public EvictionContext(PerWcfMessageLifestyleManager lifestyleManager)

  52:         {

  53:             this.lifestyleManager = lifestyleManager;

  54:             lifestyleManager.evicting = true;

  55:         }

  56:  

  57:         public void Dispose()

  58:         {

  59:             lifestyleManager.evicting = false;

  60:         }

  61:     }

  62:  

  63:     public override void Dispose()

  64:     {

  65:     }

  66:  

  67:     public bool Equals(PerWcfMessageLifestyleManager other)

  68:     {

  69:         if (ReferenceEquals(null, other)) return false;

  70:         if (ReferenceEquals(this, other)) return true;

  71:         return Equals(other.perMessageKey, perMessageKey);

  72:     }

  73:  

  74:     public override bool Equals(object obj)

  75:     {

  76:         if (ReferenceEquals(null, obj)) return false;

  77:         if (ReferenceEquals(this, obj)) return true;

  78:         if (obj.GetType() != typeof (PerWcfMessageLifestyleManager)) return false;

  79:         return Equals((PerWcfMessageLifestyleManager) obj);

  80:     }

  81:  

  82:     public override int GetHashCode()

  83:     {

  84:         return (perMessageKey != null ? perMessageKey.GetHashCode() : 0);

  85:     }

  86: }

When you register the lifestyle with the container be sure to add it so it has a transient lifestyle itself (this means that there will be one WCF lifestyle manager per service type):

container.AddComponentLifeStyle("PerWcfMessage",typeof (PerWcfMessageLifestyleManager), LifestyleType.Transient)

Finally, just for a bit of context, the main thing which uses this lifestyle is my entity framework session…  The lifestyle manager is a big win for me because it not only ensures there is only one session per service call, but that it is actually disposed (therefore, I don’t leak memory or connections to the DB).

Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

4 Responses to Castle Windsor WCF Lifestyle – PerWcfMessageLifestyleManager

  1. Pingback: Castle Windsor NServiceBus lifestyle manager – PerMessageLifestyleManager | pep => lowdown

  2. You are aware that WCF Facility comes with this lifestyle out of the box, right? http://stw.castleproject.org/Windsor.WCF-Facility-Lifestyles.ashx

  3. tpeplow says:

    No – but I am now! Thanks, I shall use that instead 🙂

  4. max says:

    I must admit that I found this post before castle documentation. Official facility is so well hidden that I could’t find it even now that I’m aware of it : /

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s