Recently, meaning yesterday, two of my colleagues, Kriss Scott and Matt Casto ran into an issue with one of WCF services.
To make a long story short, they needed to do some policy injection, which one or both of them will probably blog about at some point. The difficulty was getting access to the constructor of the WCF service object.
Luckily the ability to create custom behaviors in WCF makes this relatively easy.
The first step was to choose what type of functionality the behavior needed to provide. Since we are looking to effect the way the service object is created, IInstanceProvider was the obvious choice. Kriss and Matt also wanted this behavior to effect all calls to this service object, so IServiceBehavior was also to be used.
What's different?
In my last series of posts on behaviors, I created a behavior based on the IDispatchMessageInspector which, hence the name, attached to (and effected) the behavior of the message dispatcher in the WCF runtime. If you remember from my previous posts, the Dispatchers are responsible for passing messages around the runtime and controlling service runtime attributes (channel dispatcher is connected to the endpoint dispatcher, the endpoint dispatcher is connected to the operation dispatcher, the operation dispatcher is connected to the operation). This was implemented as an IEndpointBehavior which meant that it was applied to a specific endpoint. So, if a message came through the endpoint we applied the behavior to, the behavior logic would execute. If a message come into the same operation of the same service but through a different endpoint the behavior would not execute.
We "attached" our behavior (in code) thusly...
In the hosting code, before we opened the host, we added an instance of the behavior to the behavior collection for our endpoint:
host.Description.Endpoints[0].Behaviors.Add(new MyCustomMessageFormatter("message"));
This caused the runtime to call the ApplyDispatchBehavior of our custom behavior object:
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
This was the code actually responsible for adding our custom logic to the correct place in the WCF runtime, in this case the MessageInspectors collection for the EndpointDispatcher passed in from the WCF runtime. If this code wasn't here, the behavior would never have actually been implemented in our service.
But that was IDispatchMessageInspector and IEndpointBehavior.
IInstanceProvider and IServiceBehavior are different animals.
For starters, your implementation of IInstanceProvider has to be attached to the endpoint dispatchers instance provider. Unlike the message inspector, there can only be one instance provider per endpoint.
But there are a couple little "wrinkles" in this whole thing. Remember when I said that Matt and Kriss wanted to implement this at the service level and not the endpoint level? That means they need to use IServiceBehavior, not IEndpointBehavior. Well, like IEndpointBehavior, IServiceBehavior has an implementation of the ApplyDispatchBehavior:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
}
One thing you may notice is that unlike the endpoint behaviors version of ApplyDispatchBehavior, we get references to ServiceDescription and ServiceHostBase passed in; not ServiceEndpoint or EndpointDispatcher. But, we need to attach this behavior to an endpoint. Here's how we do it:
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher endpoint in channelDispatcher.Endpoints)
{
endpoint.DispatchRuntime.InstanceProvider = this;
}
}
}
}
Service host base gives us access to the collection of channel dispatchers, which in turn gives us a collection of endpoint dispatchers. From here it's a simple matter to iterate through each collection and attach the behavior as need.
This is the easy example. I'm just attaching the behavior to all the endpoints in all the channel dispatchers for this service. If I wanted to I could examine each endpoint and make some kind of determination about what kind of action I wanted to take on that endpoint. In this case we didn't need to.
From here it's a simple matter to add it to our runtime from code (remember to do this BEFORE you call host.Open()):
host.Description.Behaviors.Add(new MyInstanceProvider());
Or from configuration:
<service name="Sample.HelloWCF" behaviorConfiguration="MyInstanceProviderBehavior">
...
<behaviors>
<serviceBehaviors>
<behavior name="MyInstanceProviderBehavior">
<MyInstanceProvider/>
</behavior>
</serviceBehaviors>
</behaviors>
...
<extensions>
<behaviorExtensions>
<add name="MyInstanceProvider"
type="CustomBehaviorSample.InstanceProviderExtensionElement, Behavior,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
Remember, to add this behavior via configuration, you'll need a class based on BehaviorExtensionElement to do the "heavy lifting." I covered this here and a bit more here. This time around our implementation is much simpler since we aren't passing any information in at configuration and we are starting with our behavior extension element as a separate class:
class InstanceProviderExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new MyInstanceProvider();
}
public override Type BehaviorType
{
get { return typeof (MyInstanceProvider); }
}
}
The last step is to implement the methods from IInstanceProvider:
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return new HelloWCF();
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
return;
}
As you see, this is relatively trivial. We have two overrides of the GetInstance method; one that takes a message (in case we want to inspect the message before deciding what kind of object to return) and one that doesn't. We also have a ReleaseInstance method to allow us to perform any additional cleanup that is needed.
So, that's all there is too it! If you have any questions feel free to leave a comment or ping me on twitter.
Later!