Friday, November 4, 2011

Managing cookies in a WCF client

This article shows a way to manage cookies in an instance of a client proxy to a WCF service.

Background

Years ago, when we all were using the SoapHttpClientProtocol proxies, there was a property called CookieContainer. What we had to do was to create a new cookie container for the client instance and push/pull cookies from there.

Nowadays we use client proxies which derive from System.ServiceModel.ClientBase. Unfortunately, there’s no cookie container on such proxy – this is reasonable as the proxy could be used to contact a WCF Service over TCP or MSMQ where the term “http cookie” is meaningless.

Existing solutions

There are two well-known approaches to the problem, both clearly described in a blog entry by Enrico Campidoglio. First, you could take control over http headers using clumsy:

MyWebServiceClient client = new MyWebServiceClient();
 
using ( new OperationContextScope( client.InnerChannel ) )
{
    HttpRequestMessageProperty request = new HttpRequestMessageProperty();
    request.Headers["Cookie"] = "cookie header value";
 
    OperationContext.Current.OutgoingMessageProperties[
        HttpRequestMessageProperty.Name] = request;
 
    client.InvokeSomeMethod();
}

This way you can alter the way cookies are handled for a single request.

A more general approach involves writing a custom message inspector. Consult Enrico’s article for more details.

Another approach

Is it really such difficult? Is the WCF Client proxy so dumb that it cannot handle cookies on its own?

Well, it can. It seems that setting the AllowCookies property on a BasicHttpBinding to true turns on the automatic cookie management. From this moment, you can’t use any of the previously mentioned solutions because it is the client proxy to retrieve cookies and automatically append them to consecutive requests.

Wait! But you can’t append a new cookie on the client-side this way! And this was our primary requirement.

Well, you can. If you ask the server to do so.

[ServiceContract]
[ServiceBehavior(
    InstanceContextMode = InstanceContextMode.PerCall,
    ConcurrencyMode = ConcurrencyMode.Multiple,
    UseSynchronizationContext = false )]
[AspNetCompatibilityRequirements( 
   RequirementsMode = AspNetCompatibilityRequirementsMode.Required )]
 
public class TheService 
{
    // Append the cookie to the client
    [OperationContract]
    public void SetCookie( string Name, string Value )
    {
        HttpCookie cookie = new HttpCookie( Name, Value );
 
        HttpContext.Current.Response.AppendCookie( cookie );
    }
 
    // other service methods follow
}

And the client:

BasicHttpBinding binding = new BasicHttpBinding();
binding.AllowCookies = true;
 
EndpointAddress address = 
   new EndpointAddress( "http://localhost:12345/TheService.svc" );
 
TheServiceReference.TheServiceClient client = 
   new TheServiceReference.TheServiceClient( binding, address );
 
// ask the server to append the cookie
client.SetCookie( "foo", "bar" ); 

And that’s it. At the cost of one extra server request, the WCF client is able to take a full control over it’s cookies. Please add any additional methods as needed (for example to remove cookies).

No comments: