WCF shared IClientMessageInspector instance across multiple clients
I'm managing a shared auth cookie when making WCF service calls via this methodology outlined under the header "Centralized cookie management" located here: http://megakemp.com/2009/02/06/managing-shared-cookies-in-wcf/
I've set up a custom IClientMessageInspector
, IEndpointBehavior
, BehaviorExtensionElement
, the works. My endpoint behavior adds a message inspector as follows:
public class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
// yuck.. Wish I had an instance of MyClientMessageInspector
// (which has the auth cookie already) so I could just inject that
// instance here instead of creating a new instance
clientRuntime.MessageInspectors.Add(new MyClientMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
It all works flawlessly, but this solution breaks down when you want to share cookies over multiple clients. Beca开发者_运维问答use the ApplyDispatchBehavior()
method creates a new instance, any other client wouldn't get that message inspector instance, and thus, the auth ticket.
So then I thought of trying to create a custom constructor where I could inject the instance like so:
MyEndpointBehavior(MyClientMessageInspector msgInspector) { ... }
But, WCF needs parameter-less constructors. Weeding through the internets, WCF has hooks to allow for dependency injection, creating an IInstanceProvider
, IServiceBehavior
, etc. But I don't think that's what I'm looking for here.
Can anyone help guide me in the right direction?
You need only extend the concept so that you store the cookie outside of the message inspector itself so that all instances of the message inspector share the same storage.
The poor man's way, just to get started, would be to just use a static field instead of an instance field. Obviously if you have multiple threads you'll need to provide concurrency while updating the field. From there you can get even fancier if you extrapolate it out to a cookie container concept and then just make sure you share the same container with all clients. Sharing the container can be done by getting the ChannelParameterCollection
for the client channel and adding property to it and then your behavior looks for that property while it's inspecting the mssage and pulling the cookies out of that. That would look a little something like this:
App logic
// Hold onto a static cookie container
public static CookieContainer MyCookieContainer;
// When instantiating the client add the cookie container to the channel parameters
MyClient client = new MyClient();
client.InnerChannel.GetProperty<ChannelParameterCollection>().Add(MyCookieContainer);
Message inspector logic
public void BeforeSendMessage(ref Message, IClientChannel clientChannel)
{
// Find the cookie container for the current channel
CookieContainer cookieContainer = clientChannel.GetProperty<ChannelParameterCollection>().Select(p => p as CookieContainer).Where(cc => cc != null).First();
// ... use the cookie container to set header on outgoing context ...
}
You're correct, IInstanceProvider won't help in your case - it's used for providing service instances only. You don't need a parameterless constructor for your behavior. You need a paramterless constructor for the config element, and this class can use some dependency injection class (see below) to create the appropriate inspector class needed for the behavior.
namespace ConsoleApplication4
{
public class MyEndpointBehavior : IEndpointBehavior
{
IClientMessageInspector inspector;
public MyEndpointBehavior(IClientMessageInspector inspector)
{
this.inspector = inspector;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this.inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
public class MyEndpointBehaviorElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(MyEndpointBehavior); }
}
protected override object CreateBehavior()
{
return new MyEndpointBehavior(ClientInspectorFactory.GetClientInspector());
}
}
public class MyClientInspector : IClientMessageInspector
{
public MyClientInspector()
{
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
Console.WriteLine("AfterReceiveReply");
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Console.WriteLine("BeforeSendRequest");
return null;
}
}
public static class ClientInspectorFactory
{
static IClientMessageInspector instance;
public static IClientMessageInspector GetClientInspector()
{
if (instance == null)
{
instance = new MyClientInspector();
}
return instance;
}
}
[ServiceContract]
public interface ITest
{
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public int Add(int x, int y) { return x + y; }
}
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(Service));
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>("client1");
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Add(3, 4));
((IClientChannel)proxy).Close();
factory.Close();
factory = new ChannelFactory<ITest>("client2");
proxy = factory.CreateChannel();
Console.WriteLine(proxy.Add(5, 8));
((IClientChannel)proxy).Close();
factory.Close();
host.Close();
}
}
}
I liked the answers provided by @carlosfigueira and @drew, but I ultimately came up with a slightly different approach. I opted to configure my IEndpointBehavior PROGRAMMATICALLY, vs via config. Made things much simpler. I changed my endpoint behavior to store my client message inspector as follows:
public class MyEndpointBehavior : IEndpointBehavior
{
private MyClientMessageInspector_myClientMessageInspector;
public MyClientMessageInspector MyClientMessageInspector
{
get
{
if (_myClientMessageInspector == null)
{
_myClientMessageInspector = new MyClientMessageInspector();
}
return _myClientMessageInspector;
}
}
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(MyClientMessageInspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
Then I simply shared this behavior between clients, as follows:
var behavior = new MyEndpointBehavior();
client1.Endpoint.Behaviors.Add(behavior);
client2.Endpoint.Behaviors.Add(behavior);
Now both clients will share the same auth cookie.
精彩评论