none
How to cache ChannelFactory?

    Question

  • We have an ASP.NET three tiered application consisting of: the presentation tier (ASP.NET), the Middle tier (WCF Services), and the data tier. ASP.NET Presentation tier is hosted in IIS.6.0 (Windows 2003). WCF middle tier is hosted in IIS 7.0 (Windows 2008). WCF NetTCPBinding is used to communicate WCF from ASp.NET.

    We use the default “per-call” WCF instance mode use the auto generated WCF proxies at client side (ASP.NET). At client side, the proxy object is closed after every WCF calls. Please see below the code snippet:

    The clients call WCF service using ClientBase<TChannel> (auto generated proxy).

                    client = new ServiceClient();
                    client.GetSomething();
    There are several articles recommends to cache the channel factory. Can someone provide sample code on how to cache channelfactory when we use auto generated proxy objects? Appreciate your help.
    Thursday, January 28, 2010 8:49 PM

Answers

  • wenlongs post has the details as when caching will be disabled and how Servicemodel decides to cache the factory.
    TO answer your question, each ClientBase<T> (the typed proxy) should have a ChannelFactory property on it..

    So you can do something like  , instanctiate typed proxy  & then add it to a dictionary or something similar which will throw an exception if the object already exists.
    Does that help?


    Sajay
    • Proposed as answer by After2050 Wednesday, February 03, 2010 5:24 AM
    • Marked as answer by Riquel_DongModerator Wednesday, February 03, 2010 7:33 AM
    Tuesday, February 02, 2010 7:31 AM
    Moderator
  • Hi Rasheed,

    I don't know exactly how to prove that in action. but I just dis-assemble the ClientBase<TChannel> through Red Gate's Reflector. I went through the constructors.

    Here I mention two constructors. One is a static constructor,

    static ClientBase()
    {
        ClientBase<TChannel>.factoryRefCache = new ChannelFactoryRefCache<TChannel>(0x20);
        ClientBase<TChannel>.staticLock = new object();
        ClientBase<TChannel>.onAsyncCallCompleted = DiagnosticUtility.Utility.ThunkCallback(new AsyncCallback(ClientBase<TChannel>.OnAsyncCallCompleted));
    }
    

    See in the constructor they are instantiating a static cache ChannelfactoryRefCache. This is type is nothing but inherits an MRU cache having key as an EndPointTrait and value as ChannelFactoryRef (this class just wraps our ChannelFactory).

    the other is the parameterless default constructor,

    protected ClientBase()
    {
        this.channel = default(TChannel);
        this.canShareFactory = true;
        this.syncRoot = new object();
        this.finalizeLock = new object();
        this.endpointTrait = new EndpointTrait<TChannel>("*", null, null);
        this.InitializeChannelFactoryRef();
    }
    If you see they are calling a method named InitializeChannelFactoryRef that does an important thing,

    private void InitializeChannelFactoryRef()
    {
        lock (ClientBase<TChannel>.staticLock)
        {
            ChannelFactoryRef<TChannel> ref2;
            if (ClientBase<TChannel>.factoryRefCache.TryGetValue(this.endpointTrait, out ref2))
            {
                if (ref2.ChannelFactory.State != CommunicationState.Opened)
                {
                    ClientBase<TChannel>.factoryRefCache.Remove(this.endpointTrait);
                }
                else
                {
                    this.channelFactoryRef = ref2;
                    this.channelFactoryRef.AddRef();
                    this.useCachedFactory = true;
                    return;
                }
            }
        }
        if (this.channelFactoryRef == null)
        {
            this.channelFactoryRef = ClientBase<TChannel>.CreateChannelFactoryRef(this.endpointTrait);
        }
    }
    
     

    In the method they are just checking whether the endpoint key already have a channel factory reference in the cache. If it has then they are getting the channel factory from the cache and incrementing the reference counter.

    If you call some other constructor say the one that takes binding element and endpoint address as inputs there they are disabling the caching. So caching of ChannelFactory is happening in auto-generated proxies on calling the particular constructors.

    Please see this blog also http://yedafeng.blogspot.com/2008/11/what-wcf-client-does.html

    Regards
    Dnana

    • Proposed as answer by After2050 Tuesday, February 02, 2010 9:20 AM
    • Marked as answer by Riquel_DongModerator Wednesday, February 03, 2010 7:33 AM
    Tuesday, February 02, 2010 9:14 AM
  • Hi Arasheed,

    One more thing I analyzed and found out, if you are not calling the Open() method of the proxy explicitly then the caching is not at all happening.
                Dictionary<ChannelFactory, DateTime> channels = new Dictionary<ChannelFactory, DateTime>();
                for (int count = 0; count < 10; count++)
                {
    
                    ServiceReferences.ServiceClient client = new OldClient.ServiceReferences.ServiceClient();
                    channels.Add(client.ChannelFactory, DateTime.Now);
                    client.Close();
                }
    In the above case no exception has been thrown.

    Before accessing the ChannelFactory of the proxy you have to Open it first, like below

                Dictionary<ChannelFactory, DateTime> channels = new Dictionary<ChannelFactory, DateTime>();
                for (int count = 0; count < 10; count++)
                {
    
                    ServiceReferences.ServiceClient client = new OldClient.ServiceReferences.ServiceClient();
                    client.Open();
                    channels.Add(client.ChannelFactory, DateTime.Now);
                    client.Close();
                }
    Make sure you are doing this. Just create the proxy with default constructor, open it and then do any thing.

    Regards
    Dnana
    • Marked as answer by arasheed Thursday, February 11, 2010 1:52 AM
    Wednesday, February 10, 2010 2:02 PM
  • That's cool. And one more point also I like to add from Wenlong's blog. WCF allows to change the settings of the ChannelFactory say it's endpoint or other things before the Channel get's opened that is part of extensibility. So if you access any of the public things of the ChannelFactory like Endpoint, State or Credentials before calling the Open() method the caching will get disabled.

    So before calling the Open() method don't access any public members of the ChannelFactory even GetHashCode().

    Dnana
    • Marked as answer by arasheed Friday, February 19, 2010 10:14 PM
    Thursday, February 11, 2010 6:08 AM

All replies

  • We have an ASP.NET three tiered application consisting of: the presentation tier (ASP.NET), the Middle tier (WCF Services), and the data tier. ASP.NET Presentation tier is hosted in IIS.6.0 (Windows 2003). WCF middle tier is hosted in IIS 7.0 (Windows 2008). WCF NetTCPBinding is used to communicate WCF from ASp.NET.

    We use the default “per-call” WCF instance mode use the auto generated WCF proxies at client side (ASP.NET). At client side, the proxy object is closed after every WCF calls. Please see below the code snippet:

    client = new ServiceClient();
    client.GetSomething();

    THere arWe have an ASP.NET three tiered application consisting of: the presentation tier (ASP.NET), the Middle tier (WCF Services), and the data tier. ASP.NET Presentation tier is hosted in IIS.6.0 (Windows 2003). WCF middle tier is hosted in IIS 7.0 (Windows 2008). WCF NetTCPBinding is used to communicate WCF from ASp.NET.

    We use the default “per-call” WCF instance mode use the auto generated WCF proxies at client side (ASP.NET). At client side, the proxy object is closed after every WCF calls. Please see below the code snippet:


    client = new ServiceClient();
    client.GetSomething();


    There are several articles suggest that it's goof practice to cache "ChannelFactory". We simply use the auto generated proxy objects and not sure if something needs to be done explicitly to enable caching. Please let me know what needs to be done. Appreciate your help!



    Friday, January 29, 2010 12:57 AM
  • Hi Arasheed,


    As mentioned in the first blog, .NET 3.0 SP1 that comes with .NET 3.5 has some improvements on caching ChannelFactory.

    I have verified this by creating a sample console application and created the auto-generated proxy for hundred times. I noticed that only the first it take more time from the next time it is very less.

    See this thread http://social.msdn.microsoft.com/Forums/en-US/wcfprerelease/thread/46548ac8-d2aa-4063-b750-f146c5371ade

    The ChannelFactory is getting cached in ClientBase<T>. So I don't think you want to go for one more time caching the ChannelFactory.

    (Mark as answer if this helps)

    Regards
    Dnana
    Friday, January 29, 2010 7:09 AM
  • Thank you. I have verified this by writing the hashcode of the channelfacory in proxy object's constructor. Unfortunatly, the hashcode is different for every calls. This tells me that channelfactory is not cached. How do we prove if caching really works? Please see below the code snippet and advice:

    //Client Code:
    
    client = new ServiceClient(); //Create proxy instance
    client.GetSomething(); //Access WCF operation
    //Auto Generated proxy: public ServiceClient() { string hc = this.ChannelFactory.GetHashCode().ToString(); // Get the hashcode of the channelfactory. string sSource = "Sample"; string sLog = "Application"; string sEvent=hc; if (!EventLog.SourceExists(sSource)) EventLog.CreateEventSource(sSource, sLog); EventLog.WriteEntry(sSource, sEvent); } //Event Viewer output 34625470 // 1 st time 2840889 //2 nd time 45374195 // 3rd time



    Friday, January 29, 2010 2:31 PM
  • Hi Arasheed,

    In SvcUtil generated proxy, there are some cases the caching will get disabled. When you are trying to pass the BindingElement in the constructor of proxy or when you try to access any public property of the ChannelFactory or EndPoint or ClientCredentials of the proxy.

    In the above constructor you are accessing the GetHashCode() public method of the channel factory. I think that's why you are getting a different Hash value every time. Do not access any public property of the ChannelFactory and try the sample I hope you get the same hash value.

    Thanks & Regards
    Dnana
    Monday, February 01, 2010 7:41 AM
  • There were quite a few optimization during 3.5. Your channel factories might already be cached if they match wrt to a certain set of criterias.
    http://blogs.msdn.com/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx

    you could just run a clrprofiler against and check how many channel factories you have to confirm this.


    Sajay
    Monday, February 01, 2010 5:59 PM
    Moderator
  • Hi Dnana,

    Thank you very much. My question is how do we prove that it's indeed caching? Btw, the hash code is *same* when I dynamically created the proxy using ChannelFactory<T>.CreateChannel() . 

    I just want to prove that caching works in auto generated proxy. Please advice.

     

    Thanks

    Rasheed




    Tuesday, February 02, 2010 12:04 AM
  • wenlongs post has the details as when caching will be disabled and how Servicemodel decides to cache the factory.
    TO answer your question, each ClientBase<T> (the typed proxy) should have a ChannelFactory property on it..

    So you can do something like  , instanctiate typed proxy  & then add it to a dictionary or something similar which will throw an exception if the object already exists.
    Does that help?


    Sajay
    • Proposed as answer by After2050 Wednesday, February 03, 2010 5:24 AM
    • Marked as answer by Riquel_DongModerator Wednesday, February 03, 2010 7:33 AM
    Tuesday, February 02, 2010 7:31 AM
    Moderator
  • Hi Rasheed,

    I don't know exactly how to prove that in action. but I just dis-assemble the ClientBase<TChannel> through Red Gate's Reflector. I went through the constructors.

    Here I mention two constructors. One is a static constructor,

    static ClientBase()
    {
        ClientBase<TChannel>.factoryRefCache = new ChannelFactoryRefCache<TChannel>(0x20);
        ClientBase<TChannel>.staticLock = new object();
        ClientBase<TChannel>.onAsyncCallCompleted = DiagnosticUtility.Utility.ThunkCallback(new AsyncCallback(ClientBase<TChannel>.OnAsyncCallCompleted));
    }
    

    See in the constructor they are instantiating a static cache ChannelfactoryRefCache. This is type is nothing but inherits an MRU cache having key as an EndPointTrait and value as ChannelFactoryRef (this class just wraps our ChannelFactory).

    the other is the parameterless default constructor,

    protected ClientBase()
    {
        this.channel = default(TChannel);
        this.canShareFactory = true;
        this.syncRoot = new object();
        this.finalizeLock = new object();
        this.endpointTrait = new EndpointTrait<TChannel>("*", null, null);
        this.InitializeChannelFactoryRef();
    }
    If you see they are calling a method named InitializeChannelFactoryRef that does an important thing,

    private void InitializeChannelFactoryRef()
    {
        lock (ClientBase<TChannel>.staticLock)
        {
            ChannelFactoryRef<TChannel> ref2;
            if (ClientBase<TChannel>.factoryRefCache.TryGetValue(this.endpointTrait, out ref2))
            {
                if (ref2.ChannelFactory.State != CommunicationState.Opened)
                {
                    ClientBase<TChannel>.factoryRefCache.Remove(this.endpointTrait);
                }
                else
                {
                    this.channelFactoryRef = ref2;
                    this.channelFactoryRef.AddRef();
                    this.useCachedFactory = true;
                    return;
                }
            }
        }
        if (this.channelFactoryRef == null)
        {
            this.channelFactoryRef = ClientBase<TChannel>.CreateChannelFactoryRef(this.endpointTrait);
        }
    }
    
     

    In the method they are just checking whether the endpoint key already have a channel factory reference in the cache. If it has then they are getting the channel factory from the cache and incrementing the reference counter.

    If you call some other constructor say the one that takes binding element and endpoint address as inputs there they are disabling the caching. So caching of ChannelFactory is happening in auto-generated proxies on calling the particular constructors.

    Please see this blog also http://yedafeng.blogspot.com/2008/11/what-wcf-client-does.html

    Regards
    Dnana

    • Proposed as answer by After2050 Tuesday, February 02, 2010 9:20 AM
    • Marked as answer by Riquel_DongModerator Wednesday, February 03, 2010 7:33 AM
    Tuesday, February 02, 2010 9:14 AM
  • Hi Rasheed,

    What Sajay suggest is one we can prove that. Here is the sample code

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Dictionary<ChannelFactory, string> channels = new Dictionary<ChannelFactory, string>();
                for (int count = 0; count < 2; count++)
                {
                    ServiceReference1.ServiceClient client = new ConsoleApplication1.ServiceReference1.ServiceClient();
                    client.Open();
                    channels.Add(client.ChannelFactory, DateTime.Now.ToString());
                    Console.WriteLine(client.DoWork("Sleep Well"));
                    client.Close();
                }
                Console.ReadLine();
            }
        }
    }
    when the code executes second time an exception will be thrown saying that same key already exists in the dictionary.
    Thanks Sajay.
    Wednesday, February 03, 2010 5:35 AM
  • Sajay,

    Thans a lot. Its a good idea. I tried this with the auto generated proxy. It does not throw any exception about duplicate objects being added to the dictionary. This tells me that channelfactory is not really cached (unfortunatly).

    Thanks
    Rasheed
    • Edited by arasheed Thursday, February 04, 2010 4:04 PM changed the description
    Thursday, February 04, 2010 4:03 PM
  • Dhana,

    Appreciate you detailed analysis. Thanks a lot. Unfortunatly caching does not seem to happen in reality. You may want to try the following code in the auto generated proxy. If the channelfactory is truly cached, you should get an exception. In my cases, it does not throw any exception meaning it does not seem to cache (Thanks to Sajay for this idea!). Please try and let me know your feedback.


    //Auto Generated Proxy
    
    static Dictionary<ChannelFactory, string> channels = new Dictionary<ChannelFactory, string>();
    
     public SampleServiceClient()
            {
                //int hc = this.ChannelFactory.GetHashCode();
                //System.Console.WriteLine(hc.ToString());
                
                channels.Add(this.ChannelFactory, System.DateTime.Now.ToString());
    
            }
    


    Thursday, February 04, 2010 4:09 PM
  • how are you create the proxies?
    Sajay
    Thursday, February 04, 2010 7:11 PM
    Moderator
  • We used svcutil.

    Thanks
    Thursday, February 04, 2010 11:45 PM
  • Sorry, I wasnt' clear enough earlier. How do you instantiate the proxy. The reason I ask, is because you are passing in some paramter that is causing the caching to break.

    If you pass in a binding etc then the channel factory will not be cached.
    Sajay
    Friday, February 05, 2010 1:20 AM
    Moderator
  • Sure. I'm not doing anything with the auto generated proxy. Please see below client = new ServiceClient(); client.GetSomething(); Thanks
    Friday, February 05, 2010 3:52 AM
  • Hi Arasheed,

    Why don't you give little more detailed code of what you are trying. I don't see in the above code you are creating proxies many times and adding into the dictionary. There is no need of caching an auto-generated proxy if you are creating them using it's default constructor and not accessing the it's channel factory.

    Just create a for loop and check it as i did. definitely it will through the duplicate key exception next time you add.

    Thanks & Regards
    Dnana
    Friday, February 05, 2010 6:04 AM
  • Hi Dnana,

    Please see below what I'm trying to do and let me know if I'm missing anything.

    Client code which creates proxy instance and accesses the service operation:

    public GetSomething()
            {
                
                ServiceClient client = null;
                try
                {
                    client = new ServiceClient();
                    String somedata = client.GetSomething(inputParams, ContentTypeRequested);                
                }
                catch (FaultException<ApplicationFault> appFault)
                {
    	// Handle Exception
                }
                catch (FaultException<SystemFault> sysFault)
                {
                 // Handle Exception
                }
                catch (Exception ex)
                {       
    
    	// Handle Exception
                }
                finally
                {
                    if (proxy != null)
                {
                    if (proxy.State != CommunicationState.Faulted)
                        proxy.Close();
                    else
                        proxy.Abort();
                }
    
                proxy = null;
                }
              
            }


    Auto generated proxy where I'm adding the channelfactory to dictionary:

    //Auto Generated Proxy
    
    static Dictionary<ChannelFactory, string> channels = new Dictionary<ChannelFactory, string>();
    
     public ServiceClient()
            {
                          
                channels.Add(this.ChannelFactory, System.DateTime.Now.ToString());
    
            }
    


    My expectation is for the dictionary to throw exception when client makes repeated proxy calls. Please let me know if I'm missing something.

    Thanks


     

    Friday, February 05, 2010 9:36 PM
  • Hi Arasheed,

    The problem is you are accessing the ChannelFactory in the constructor of ServiceClient. So the caching is getting disabled. Don't access the ChannelFactory in the constructor of proxy.


    Thanks & Regards
    Dnana
    Monday, February 08, 2010 9:12 AM
  • Dnana,

    Thanks. Even if I move this code, to client where proxy is called the behaviour is same. See the modified code:

    Client code where proxy is accessed:

    public class Client 
        {
    
    
        static Dictionary<ChannelFactory, string> channels = new Dictionary<ChannelFactory, string>();        
    
    
        public GetSomething()
            {
                
                ServiceClient client = null;
                try
                {
                    client = new ServiceClient();
                    String somedata = client.GetSomething(inputParams, ContentTypeRequested);      
    
    	      channels.Add(client.ChannelFactory, System.DateTime.Now.ToString());// I expect this to throw exception when GetSomething()called again     
                }
                catch (FaultException<ApplicationFault> appFault)
                {
    
                }
                catch (FaultException<SystemFault> sysFault)
                {
    
                }
                catch (Exception ex)
                {       
    
    
                }
                finally
                {
                    if (proxy != null)
                {
                    if (proxy.State != CommunicationState.Faulted)
                        proxy.Close();
                    else
                        proxy.Abort();
                }
    
    
                }
              
            }
    
       
        }

    Auto generated Proxy (Removed the dictinoary code to add the channelfactory):

     public ServiceClient()
            {
                          
            }
    

    Am I doing anything wrong here? Btw, the only way I can get dictionary throw the exception is by declaring the proxy instance "static" outside the scope of the method. This means the caching works only when the same proxy instance is used. Is it the expected behaviour? Do you have any sample code on how you create and access proxy object? Do you mind sharing to abdul_rashi@yahoo.com Appreciate your patience! Thanks for helping.




    Tuesday, February 09, 2010 2:20 AM
  • What version of the framework is this?
    Sajay
    Tuesday, February 09, 2010 3:10 AM
    Moderator
  • Hi Arasheed,

    Here is the sample code that works for me.

    namespace ConsoleApplication1
    {
        class Program
        {
            static Dictionary<ChannelFactory, string> channels = new Dictionary<ChannelFactory, string>();
            static void Main(string[] args)
            {
                for (int count = 1; count <= 5; count++)
                {
                    ServiceReference1.ServiceClient client = new ConsoleApplication1.ServiceReference1.ServiceClient();
                    client.Open();
                    channels.Add(client.ChannelFactory, DateTime.Now.ToString());
                    Console.WriteLine(client.DoWork("Do Something.."));
                    client.Close();
                }
                Console.ReadLine();
            }
        }
    }
    Dnana
    Tuesday, February 09, 2010 5:47 AM
  • .NET 3.5 SP1
    Tuesday, February 09, 2010 4:17 PM
  • Thanks Dnana. Your sample code does not throw any exception when executed with auto generated proxy. It is recogonizing all channelfactory objects as "new" and adding them to dictionary. I would expect this to throw exception if it's caching the channel factory. Am I missing something? Please advice.

    Tuesday, February 09, 2010 4:23 PM
  • Hi Arasheed,

    One more thing I analyzed and found out, if you are not calling the Open() method of the proxy explicitly then the caching is not at all happening.
                Dictionary<ChannelFactory, DateTime> channels = new Dictionary<ChannelFactory, DateTime>();
                for (int count = 0; count < 10; count++)
                {
    
                    ServiceReferences.ServiceClient client = new OldClient.ServiceReferences.ServiceClient();
                    channels.Add(client.ChannelFactory, DateTime.Now);
                    client.Close();
                }
    In the above case no exception has been thrown.

    Before accessing the ChannelFactory of the proxy you have to Open it first, like below

                Dictionary<ChannelFactory, DateTime> channels = new Dictionary<ChannelFactory, DateTime>();
                for (int count = 0; count < 10; count++)
                {
    
                    ServiceReferences.ServiceClient client = new OldClient.ServiceReferences.ServiceClient();
                    client.Open();
                    channels.Add(client.ChannelFactory, DateTime.Now);
                    client.Close();
                }
    Make sure you are doing this. Just create the proxy with default constructor, open it and then do any thing.

    Regards
    Dnana
    • Marked as answer by arasheed Thursday, February 11, 2010 1:52 AM
    Wednesday, February 10, 2010 2:02 PM
  • You are genius!! It worked finally after I had client.Open. I'm not sure why we had to open the proxy to be able to cache. This is great. Whatever the time we spent on this is very valuable. Thanks for all your help!!
    Thursday, February 11, 2010 1:51 AM
  • That's cool. And one more point also I like to add from Wenlong's blog. WCF allows to change the settings of the ChannelFactory say it's endpoint or other things before the Channel get's opened that is part of extensibility. So if you access any of the public things of the ChannelFactory like Endpoint, State or Credentials before calling the Open() method the caching will get disabled.

    So before calling the Open() method don't access any public members of the ChannelFactory even GetHashCode().

    Dnana
    • Marked as answer by arasheed Friday, February 19, 2010 10:14 PM
    Thursday, February 11, 2010 6:08 AM