Recently in WCF Category

There is a lot of talk in the Geneva forum about caching and avoiding unnecessary round trips to the STS when calling downstream Web services. After all of it, I thought that reusing the same ChannelFactory<T> object would result in a single call to the STS for a token and that subsequent calls made with that factory would reuse the SAML token. I thought that a service type like this would suffice:

public class FooService : IFooService
{
    private static readonly ChannelFactory<IBarService> factory = new ChannelFactory<IBarService>();

     public string Echo(string message)
    {
        var proxy = factory.CreateChannel();

        return proxy.ComputeResponse(message);
    }
}

It turns out this is not correct, however. Every time a channel is created, invoking operations on that proxy will result in a call to the STS for a security token (which is really a few service calls for policy and whatnot). At first, I thought that the way to avoid this was to reuse the channel and not the channel factory. This may work if the channel remains open; however, if it is closed, it will get disposed and subsequent calls will fail. I don't know if it is a bad practice in this case to keep the channel open continuously, but, as a rule, holding onto resources for longer than absolutely necessary is to be avoided. Working off this assumption, I sought to find an alternative that would avoid the extra calls to the STS without having to hold onto an open channel object.

A couple of colleagues told me that I needed to cache the SAML token outside of the channel and retrieve them from there by adding an interceptor to the WCF pipeline. They referred me to Cibrax's blog, where he describes the process. After reading that article and Eric Quist's post, I understood that hooking into WCF to replace the default SAML token provider with one that does caching requires the definition of three classes:

  • CacheClientCredentials - Plumbing
  • CacheClientCredentialsSecurityTokenManager - Plumbing
  • CacheSecurityTokenProvider - Actual meat

The code that Cibrax and Eric wrote is really good, and it provides an excellent starting point for doing this in Geneva; however, I needed to tweak their code a bit to get it to work with this new framework. Specifically, I had to make the following changes:

CacheClientCredentials

  • Must inherit from FederatedClientCredentials
  • Must override CloneCore and return a new CacheClientCredentials object
  • May provide a static method for configuring a channel factory object to use CacheClientCredentials (similarly to FederatedClientCredentials - in beta 1 at least)

Something like this:

public class CacheClientCredentials : FederatedClientCredentials
{
    public
CacheClientCredentials() { }

    public CacheClientCredentials(ClientCredentials other) : base(other) { }

    protected override ClientCredentials CloneCore()
    {
        return
new CacheClientCredentials();
    }

    public override SecurityTokenManager CreateSecurityTokenManager()
    {
        return
new CacheClientCredentialsSecurityTokenManager(this);
    }

     public static void ConfigureChannelFactory<T>(ChannelFactory<T> channelFactory)
{
        var
other = channelFactory.Endpoint.Behaviors.Find<ClientCredentials>();

        if (other != null)
            channelFactory.Endpoint.Behaviors.Remove(other.GetType());

        FederatedClientCredentials item = null;

        if (other != null)
            item = new CacheClientCredentials(other);

        channelFactory.Endpoint.Behaviors.Add(item);
    }
}

CacheClientCredentialsSecurityTokenManager

  • Must inherit from FederatedClientCredentialsSecurityTokenManager
  • Has a constructor that accepts a FederatedClientCredentials object that is passed to the base's constructor

Something like this:

public class CacheClientCredentialsSecurityTokenManager : FederatedClientCredentialsSecurityTokenManager

{

    public CacheClientCredentialsSecurityTokenManager(FederatedClientCredentials federatedClientCredentials)

        : base(federatedClientCredentials) { }

 

    public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)

    {

        var provider = base.CreateSecurityTokenProvider(tokenRequirement);

        var federatedSecurityTokenProvider = provider as IssuedSecurityTokenProvider;

 

        if (federatedSecurityTokenProvider != null && IsIssuedSecurityTokenRequirement(tokenRequirement))

        {

            var federatedClientCredentialsParameters = FindIssuedTokenClientCredentialsParameters(tokenRequirement);

 

            provider = new CacheSecurityTokenProvider(federatedClientCredentialsParameters, federatedSecurityTokenProvider);

        }

 

        return provider;

    }

 

    // Lifted from FederatedClientCredentialsSecurityTokenManager's private method by the same name.

    private static FederatedClientCredentialsParameters FindIssuedTokenClientCredentialsParameters(SecurityTokenRequirement tokenRequirement)

    {

        var parameters = new FederatedClientCredentialsParameters();

        ChannelParameterCollection result;

 

        if (tokenRequirement.TryGetProperty(ServiceModelSecurityTokenRequirement.ChannelParametersCollectionProperty, out result) && result != null)

        {

            foreach (var obj2 in result)

            {

                if (obj2 is FederatedClientCredentialsParameters)

                {

                    break;

                }

            }

        }

 

        return parameters;

    }

}


CacheSecurityTokenProvider

  • Must inherit from FederatedSecurityTokenProvider
  • Must define a constructor that accepts a FederatedClientCredentialsParameters and an IssuedSecurityTokenProvider object
  • Does not need to clone the inner provider because FederatedSecurityTokenProvider does that already

Something like this:

public class CacheSecurityTokenProvider : FederatedSecurityTokenProvider, IDisposable

{

    private bool disposed;

    private readonly IssuedSecurityTokenProvider innerProvider;

 

    public CacheSecurityTokenProvider(FederatedClientCredentialsParameters federatedClientCredentialsParameters,

        IssuedSecurityTokenProvider federatedSecurityTokenProvider)

        : base(federatedClientCredentialsParameters, federatedSecurityTokenProvider)

    {

        innerProvider = federatedSecurityTokenProvider;

        innerProvider.Open();

    }

 

    protected override SecurityToken GetTokenCore(TimeSpan timeout)

    {

        var userName = Thread.CurrentPrincipal.Identity.Name;

        var cacheKey = string.Concat(this.TargetAddress.Uri, IssuerAddress.Uri, userName);

        var securityToken = TokenCacheHelper.GetToken(cacheKey);

        var cacheMiss = securityToken == null || IsSecurityTokenExpired(securityToken);

 

        if (cacheMiss)

        {

            securityToken = innerProvider.GetToken(timeout);

 

            // Only add the token to the cache if caching has been turned on in web/app.config.

            if (CacheIssuedTokens)

            {

                TokenCacheHelper.AddToken(cacheKey, securityToken);

            }

        }

 

        return securityToken;

    }

 

    private static bool IsSecurityTokenExpired(SecurityToken serviceToken)

    {

        return DateTime.UtcNow >= serviceToken.ValidTo.ToUniversalTime();

    }

 

    ~CacheSecurityTokenProvider()

    {

        if (!disposed)

            ((IDisposable)this).Dispose();

    }

 

    void IDisposable.Dispose()

    {

        innerProvider.Close();

        disposed = true;

    }

}


Conclusion

Now, having said all this and listing a bunch of code, let's step back for a moment. Caching of SAML tokens is a must to avoid extra round trips to the STS. Doing so requires a bunch of plumbing and everyone will absolutely require this functionality. So, why are we doing this? Geneva is a framework, and as such, IMO, it should be proving things like caching of SAML tokens for us. It has been said by the Geneva team that support for caching of tokens is a top priority, so hopefully the code above will be obsolete very soon.

Microsoft announced at PDC '08 that .NET 4.0 would include an implementation of WS-Discovery. Conformance to this protocol would allow service consumers to locate providers dynamically at run-time. I'm sure I don't have to tell you that this capability in often needed when implementing large-scale connected systems. These new discovery capabilities provide two ways to locate services:

  1. Using a known, centralized repository (what Microsoft and others are calling the "managed model")
  2. Using ad-hoc discovery wherein services broadcast their arrival and departure from the network  

The latter is restricted to a single subnet IINM and is analogous to DHCP.

This new functionality is not brand new. It is based on the WS-Discovery sample code that was released on NetFX years ago (which is still available today on the Microsoft code gallery). It has been enhanced to support the new draft standard that has recently been submitted to OASIS for ratification, many bugs have been fixed of course, and other enhancements have been made.

Even with these improvements, I would advise against using WCF Discovery for the following reasons:

  1. Discoverable services cannot be hosted in WAS.
  2. There is no Windows Server role that can be added to provide a centralized repository for discovery.
  3. It depends on .NET 4.0 which currently has no ship date (to my knowledge).

Because there is no server role, we will all end up building the equivalent of such a role. Then, Microsoft will come along with Windows Server vNext and, ta-da, it will have a repository role for run-time service discovery just like what happed with WF and Dublin: A bunch of us wrote complex workflow hosts and suffered through all the pain points just to have Microsoft come along afterwards with their own enterprise-caliber workflow host. Lastly, the dependence on .NET 4.0 means that your shipping date has to be really soft. Given all this, I can't see why anyone would seriously consider using WCF Discovery at this time unless they are in the service repository business, already have a .NET-based product that is shipping in 2011 (or so), and need to be augment it to conform to WS-Discovery.

What do you do today if you need discovery capabilities like those that will eventually be provided by WCF Discovery? (For goodness sake, don't even think about UDDI; that is for design-time discoverability not run-time! ) I would look to one of the established vendors in this space. I've heard from colleagues that Software AG's run-time repository is unusable, so axe them from your list. Based on my research and product evaluation, I would suggest checking out SOA Software or AmberPoint. From a cursory investigation that I did in H1 of 2008, I would lean toward AmberPoint. Their run-time repo seemed to be a real differentiator.

If I haven't persuaded you, I know that Microsoft is looking for customers to participate in their TAP program for WCF Discovery.

There is a lot of new stuff coming down the pike that WCF developers will want to be aware of. Here's a shortlist that I've put together from various corners of the Net:

Programming WCF Services 2nd ed.

Juval Lowy's book on WCF that we've all read will be updated in November according to O'Reilly's Upcoming Title list. I wonder if he'll revise his stance on message contracts in the new edition.

Learning WCF 2nd ed.

Michele Leroux Bustamante was recently interviewed on .NET Rocks, and she said in that conversation that a new edition of her book is in the works. It isn't yes listed as an upcoming title on the publisher's site though.

New Edition of WCF Security Guidance

According to this roadmap document on MSDN, a new edition of the WCF Security Guidance e-book is due out next month. This strikes me as odd since the one that currently available on CodePlex is only two months old. I've posted a question on the community's forum to get some clarification, but haven't received a response yet.

Enterprise Library 4.1 and Unity 1.2

At the end of July, the entlib team started working on version 4.1 which would integrate Unity more deeply into the toolkit among other things. According to this roadmap and this page on MSDN, the work should be wrapping up this month.

Oslo CTP

Next week at PDC, Microsoft will let us see the first beta of Oslo. I don't know if the beta will be made available only to PDC attendees with a CTP to follow thereafter (kinda like they did with .NET way back in the day) or if the bits will be broadly available from the outset. If I find out more specifics, I'll update this post.

I'm sure there are other noteworthy, WCF-related releases coming soon. If you're aware of any, please post a comment.

I'm no lawyer, but this patent application makes you wonder if the WCF channel stack is patented.  If it is, what are the ramifications?  Is it a big deal?  Should businesses building on WCF be concerned that such a patent might (further) lock them into a Microsoft-only world?  I don't have answers to the questions, but I think they deserve asking.

This little e-book on WCF channels is a must read for any advanced WCF programmer.