1

A WCF service will consume another Wcf service. Now, i want to create channel factory object and cache it manually. I know performance will be good but concern any other issue will be raised or not.

I have found info as follows:

"Using ChannelFactory you can still achieve channel factory caching with your own custom MRU cache. This still implies an important restriction: calls to the same service endpoint that share the channel factory must also share the same credentials. That means you can t pass different credentials for each thread calling application services from the Web server tier. One scenario where this is not an issue is if you use the same certificate or Windows credential to authenticate to downstream services. In this case, if you need to pass information about the authenticated user, you can use custom headers rather than a security token."

Link: http://devproconnections.com/net-framework/wcf-proxies-cache-or-not-cache

I have found a sample code in Google as follows.

internal delegate void UseServiceDelegate<in T>(T proxy);



    internal static class Service<T>
   {

      private static readonly IDictionary<Type, string>  
                 cachedEndpointNames   =    new  Dictionary<Type, string>();


private static readonly IDictionary<string, ChannelFactory<T>> 
    cachedFactories =
    new Dictionary<string, ChannelFactory<T>>();


     internal static void Use(UseServiceDelegate<T> codeBlock)
     {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

      try
      {
        using (proxy)
        {
            codeBlock((T)proxy);
        }

        success = true;
    }
    finally
    {
        if (!success)
        {
            proxy.Abort();
        }
    }
}


     private static ChannelFactory<T> GetChannelFactory()
     {
      lock (cachedFactories)
      {
        var endpointName = GetEndpointName();

        if (cachedFactories.ContainsKey(endpointName))
        {
            return cachedFactories[endpointName];
        }

        var factory = new ChannelFactory<T>(endpointName);

        cachedFactories.Add(endpointName, factory);
        return factory;
        }
    }


     private static string GetEndpointName()
     {
        var type = typeof(T);
        var fullName = type.FullName;

    lock (cachedFactories)
    {
        if (cachedEndpointNames.ContainsKey(type))
        {
            return cachedEndpointNames[type];
        }

        var serviceModel =  

     ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
     .SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

        if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
        {
            foreach (var endpointName in  
    serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>()
   .Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint 
    => endpoint.Name))
            {
                cachedEndpointNames.Add(type, endpointName);
                return endpointName;
            }
        }
    }

    throw new InvalidOperationException("Could not find endpoint element   
       for type '" + fullName + "' in the ServiceModel client 
      configuration section. This might be because no configuration file 
       was found for your application, or because no endpoint element 
       matching this name could be found in the client element.");
    }
 }

I am totally confused what should i do. Can anyone give me a best practice guideline?

Morshed
  • 165
  • 3
  • 16

1 Answers1

9

This is a complex topic with a lot of details to go over, but here it goes.

First, as a general rule you should be caching a ChannelFactory and not an individual Channel. A ChannelFactory is expensive to construct as well as thread-safe so it is a great candidate for caching. A Channel is cheap to construct and it is generally recommended to only create channels on an as-needed basis and to close them as early as possible. Additionally, when you cache a Channel then you have to worry about it timing out which will cause it to fault which invalidates the entire benefit of caching it in the first place.

The article you linked to by Michele Leroux Bustamante is one of the best resources out there. As she states, there are differences to consider between Windows clients and server-side clients. Mostly only Windows clients benefit from caching as typically the credentials differ from thread to thread on server-side clients. For your typical Windows clients, there are two main options: Caching the references yourself or leveraging the MRU cache.

Leveraging the MRU cache: Essentially this means that you are letting Microsoft take the wheel. The ClientBase class will use an MRU cache for the internal ChannelFactory instance. The caching behavior is controlled via a CacheSetting property and by default caching will be disabled if any of the "security-sensitive" properties are accessed. ClientBase properties which will invalidate and remove a ChannelFactory from the MRU cache when accessed include the Endpoint, ClientCredentials or the ChannelFactory itself. There is a way to override this behavior by setting the CacheSettings property to CacheSettings.AlwaysOn. Additionally, if the Binding is run-time defined then the ChannelFactory is no longer a candidate for the MRU cache. See more details here.

Caching the references yourself: This means that you are going to keep a collection of ChannelFactory references yourself. The snippet you provide in your question uses this approach. The best approach I have ever seen and admittedly use a modified version of at work is by Darin Dimitrov via this related SO question. For those of us who like to have more fine-grained control over the caching mechanism then this is the approach to use. This is typically used when credentials must be set at run-time like is often required by internet services.

Quite similarly, client proxies can be cached to improve performance - Wenlong Dong has an article about this topic.

(Update) Server-side clients as noted before are quite limited in their options when it comes to ChannelFactory caching. For this brief discussion, we will assume that our deployment scenario looks like this:

Client -> Service A -> Service B

The most likely method to use in order to leverage ChannelFactory caching in this scenario is to cache the references yourself for the session between the Client and Service A. This way Service A does not have to construct a different ChannelFactory instance every time Service A needs to call into Service B. However, if the properties of the ChannelFactory need change for each call, then this is no longer going to be appropriate.

Of course this also holds if Service A is a Singleton and each call to the downstream service (Service B) does not require new credentials, but Singleton services have their own set of performance problems.

Community
  • 1
  • 1
Derek W
  • 9,708
  • 5
  • 58
  • 67
  • Thank you Derek for your details info and nice links. I have read code which you gave link. I have another question. When should i call dispose() method to clean up dictionary? – Morshed Jun 10 '15 at 12:53
  • Preferably when the lifetime of your application is coming to an end. Say you have a Main Form, then when this form is closing - Trigger this to call `Dispose()` on your Factory Manager object. – Derek W Jun 10 '15 at 16:47
  • I do not have any client app. For example, i have 2 WCF services. Service A will consume Service B. So, i will use this code in Service A to consume Service B operations. In short, service A will act as a client for service B. In this case when and where should i call Dispose() method? – Morshed Jun 10 '15 at 21:26
  • As long as Service A does not require different credentials for each thread calling Service B - Then you should be alright to use your own caching mechanism. At the end of Service A object's lifetime (end of session or close of service depending on the `InstanceContextMode`) you should have this trigger a call to `Dispose()` to clean up resources. This is typically going to be more useful when your service objects are multithreaded and each thread calling Service B from A uses it's own channel rooted from your cached Channel Factory. – Derek W Jun 11 '15 at 14:14
  • Just to be clear - If Service A is `InstanceContextMode.PerCall` then this will grant you no benefit at all as each call to Service A will result in a new Factory Manager being constructed. At that point, if Service A needs to make multiple calls to Service B within the same method invocation, then just keep the Channel Factory entirely within the scope of that method. – Derek W Jun 11 '15 at 14:23
  • I know this post is an old one, but hopefully you can still answer: Can you confirm that using ClientBase for Caching means that the caching is done in the background by Windows and is not done directly by the Proxy? Meaning that if multiple Applications use a different copy of the Proxy dll, they will still all be able to share that same cache? Essentially, are we talking here about distributed caching, allowing to access cache across multiple applications? Thanks – progLearner Jan 26 '21 at 10:24
  • 1
    @progLearner: No, the `ClientBase` caching approach only works "within in the AppDomain" and to my knowledge there is no way to share an AppDomain between multiple processes. I am assuming here that each application is running in it's own separate process and therefore they will not be within the same AppDomain. Even though each application may be pointing to the same DLL on the file system, each application will load their own separate copy into memory. – Derek W Feb 21 '21 at 17:48