WCF ChannelFactory and channels - caching, reusing, closing and recovery
作者:互联网
I have the following planned architecture for my WCF client library:
- using ChannelFactory instead of svcutil generated proxies because I need more control and also I want to keep the client in a separate assembly and avoid regenerating when my WCF service changes
- need to apply a behavior with a message inspector to my WCF endpoint, so each channel is able to send its own authentication token
- my client library will be used from a MVC front-end, so I'll have to think about possible threading issues
- I'm using .NET 4.5 (maybe it has some helpers or new approaches to implement WCF clients in some better way?)
I have read many articles about various separate bits but I'm still confused about how to put it all together the right way. I have the following questions:
- as I understand, it is recommended to cache ChannelFactory in a static variable and then get channels out of it, right?
- is endpoint behavior specific to the entire ChannelFactory or I can apply my authentication behavior for each channel separately? If the behavior is specific to the entire factory, this means that I cannot keep any state information in my endpoint behavior objects because the same auth token will get reused for every channel, but obviously I want each channel to have its own auth token for the current user. This means, that I'll have to calculate the token inside of my endpoint behavior (I can keep it in HttpContext, and my message inspector behavior will just add it to the outgoing messages).
- my client class is disposable (implements IDispose). How do I dispose the channel correctly, knowing that it might be in any possible state (not opened, opened, failed ...)? Do I just dispose it? Do I abort it and then dispose? Do I close it (but it might be not opened yet at all) and then dispose?
- what do I do if I get some fault when working with the channel? Is only the channel broken or entire ChannelFactory is broken?
I guess, a line of code speaks more than a thousand words, so here is my idea in code form. I have marked all my questions above with "???" in the code.
public class MyServiceClient : IDisposable { // channel factory cache private static ChannelFactory<IMyService> _factory; private static object _lock = new object(); private IMyService _client = null; private bool _isDisposed = false; /// <summary> /// Creates a channel for the service /// </summary> public MyServiceClient() { lock (_lock) { if (_factory == null) { // ... set up custom bindings here and get some config values var endpoint = new EndpointAddress(myServiceUrl); _factory = new ChannelFactory<IMyService>(binding, endpoint); // ???? do I add my auth behavior for entire ChannelFactory // or I can apply it for individual channels when I create them? } } _client = _factory.CreateChannel(); } public string MyMethod() { RequireClientInWorkingState(); try { return _client.MyMethod(); } catch { RecoverFromChannelFailure(); throw; } } private void RequireClientInWorkingState() { if (_isDisposed) throw new InvalidOperationException("This client was disposed. Create a new one."); // ??? is it enough to check for CommunicationState.Opened && Created? if (state != CommunicationState.Created && state != CommunicationState.Opened) throw new InvalidOperationException("The client channel is not ready to work. Create a new one."); } private void RecoverFromChannelFailure() { // ??? is it the best way to check if there was a problem with the channel? if (((IChannel)_client).State != CommunicationState.Opened) { // ??? is it safe to call Abort? won't it throw? ((IChannel)_client).Abort(); } // ??? and what about ChannelFactory? // will it still be able to create channels or it also might be broken and must be thrown away? // In that case, how do I clean up ChannelFactory correctly before creating a new one? } #region IDisposable public void Dispose() { // ??? is it how to free the channel correctly? // I've heard, broken channels might throw when closing // ??? what if it is not opened yet? // ??? what if it is in fault state? try { ((IChannel)_client).Close(); } catch { ((IChannel)_client).Abort(); } ((IDisposable)_client).Dispose(); _client = null; _isDisposed = true; } #endregion }
I guess better late then never... and looks like author has it working, this might help future WCF users.
1) ChannelFactory arranges the channel which includes all behaviors for the channel. Creating the channel via CreateChannel method "activates" the channel. Channel factories can be cached.
2) You shape the channel factory with bindings and behaviors. This shape is shared with everyone who creates this channel. As you noted in your comment you can attach message inspectors but more common case is to use Header to send custom state information to the service. You can attach headers via OperationContext.Current
using (var op = new OperationContextScope((IContextChannel)proxy))
{
var header = new MessageHeader<string>("Some State");
var hout = header.GetUntypedHeader("message", "urn:someNamespace");
OperationContext.Current.OutgoingMessageHeaders.Add(hout);
}
3) This is my general way of disposing the client channel and factory (this method is part of my ProxyBase class)
public virtual void Dispose()
{
CloseChannel();
CloseFactory();
}
protected void CloseChannel()
{
if (((IChannel)_client).State == CommunicationState.Opened)
{
try
{
((IChannel)_client).Close();
}
catch (TimeoutException /* timeout */)
{
// Handle the timeout exception
((IChannel)innerChannel).Abort();
}
catch (CommunicationException /* communicationException */)
{
// Handle the communication exception
((IChannel)_client).Abort();
}
}
}
protected void CloseFactory()
{
if (Factory.State == CommunicationState.Opened)
{
try
{
Factory.Close();
}
catch (TimeoutException /* timeout */)
{
// Handle the timeout exception
Factory.Abort();
}
catch (CommunicationException /* communicationException */)
{
// Handle the communication exception
Factory.Abort();
}
}
}
4) WCF will fault the channel not the factory. You can implement a re-connect logic but that would require that you create and derive your clients from some custom ProxyBase e.g.
protected I Channel
{
get
{
lock (_channelLock)
{
if (! object.Equals(innerChannel, default(I)))
{
ICommunicationObject channelObject = innerChannel as ICommunicationObject;
if ((channelObject.State == CommunicationState.Faulted) || (channelObject.State == CommunicationState.Closed))
{
// Channel is faulted or closing for some reason, attempt to recreate channel
innerChannel = default(I);
}
}
if (object.Equals(innerChannel, default(I)))
{
Debug.Assert(Factory != null);
innerChannel = Factory.CreateChannel();
((ICommunicationObject)innerChannel).Faulted += new EventHandler(Channel_Faulted);
}
}
return innerChannel;
}
}
5) Do not re-use channels. Open, do something, close is the normal usage pattern.
6) Create common proxy base class and derive all your clients from it. This can be helpful, like re-connecting, using pre-invoke/post invoke logic, consuming events from factory (e.g. Faulted, Opening)
7) Create your own CustomChannelFactory this gives you further control how factory behaves e.g. Set default timeouts, enforce various binding settings (MaxMessageSizes) etc.
public static void SetTimeouts(Binding binding, TimeSpan? timeout = null, TimeSpan? debugTimeout = null)
{
if (timeout == null)
{
timeout = new TimeSpan(0, 0, 1, 0);
}
if (debugTimeout == null)
{
debugTimeout = new TimeSpan(0, 0, 10, 0);
}
if (Debugger.IsAttached)
{
binding.ReceiveTimeout = debugTimeout.Value;
binding.SendTimeout = debugTimeout.Value;
}
else
{
binding.ReceiveTimeout = timeout.Value;
binding.SendTimeout = timeout.Value;
}
}
https://www.c-sharpcorner.com/UploadFile/ff2f08/channel-factory-in-wcf/
Introduction
A Channel Factory enables you to create a communication channel to the service without a proxy. A Channel Factory that creates and manages the various types of channels which are used by a client to send a message to various configured service endpoints. A Channel Factory is implemented by the IChannelFactory Interface and their associated channels are used by the initiators of a communication pattern. The Channel Factory class is useful when you want to share a common service contract DLL between the client and the server.When to use Channel Factory
The Channel Factory class is used to construct a channel between the client and the server without creating a Proxy. In some of the cases in which your service is tightly bound with to the client application, we can use an interface DLL directly and with the help of a Channel Factory, call a method. The advantage of the Channel Factory route is that it gives you access method(s) that wouldn't be available if you use svcutil.exe. Channel Factory is also useful when you do not share more than just a service contract with a client. If you know that your entity will not change frequently than it is better to use a Channel Factory than a Proxy. Using the ChannelFactory<T> class is an easier alternative to making calls to the WCF services to the laborious process of generating proxies via the SvcUtil.exe tool every time a service contract changes. Channel Factory is a factory for creating service communication channels at runtime. ChannelFactory<T> takes a generic parameter of the service type (it must be a service contract interface) to create a channel. Because ChannelFactory only requires knowledge of the service contract, it makes good design sense to put service/data contracts in separate assemblies from service implementations. This way, you can safely distribute contract assemblies to third parties who wish to consume services, without the risk of disclosing their actual implementation.- [ServiceContract]
- public interface IService1
- {
- [OperationContract]
- string GetData(int value);
- [OperationContract]
- CompositeType GetDataUsingDataContract(CompositeType composite);
- // TODO: Add your service operations here
- }
How to Call WCF Service Use Channel Factory
The first step is to call the WCF Service and create a service contract in your application; please make sure that the method name, parameters and return type are identical to the actual service. To do this we require the actual End Point Address.- BasicHttpBinding myBinding = new BasicHttpBinding();
- EndpointAddress myEndpoint = new EndpointAddress("http://localhost:3047/Service1.svc");
- ChannelFactory<IService1> myChannelFactory = new ChannelFactory<IService1>(myBinding, myEndpoint);
- IService1 instance = myChannelFactory.CreateChannel();
- // Call Service.
- Console.WriteLine(instance.GetData(10));
- myChannelFactory.Close();
Conclusion
Channel Factory is useful when you do not share more than just the service contract with the client. And also your service entity will not change frequently. You can also cache your channel using the static property. https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-use-the-channelfactoryThe generic ChannelFactory<TChannel> class is used in advanced scenarios that require the creation of a channel factory that can be used to create more than one channel.
To create and use the ChannelFactory class
-
Build and run an Windows Communication Foundation (WCF) service. For more information, see Designing and Implementing Services, Configuring Services, and Hosting Services.
-
Use the ServiceModel Metadata Utility Tool (Svcutil.exe) to generate the contract (interface) for the client.
-
In the client code, use the ChannelFactory<TChannel> class to create multiple endpoint listeners.
using System; using System.ServiceModel; // This code generated by svcutil.exe. [ServiceContract()] interface IMath { [OperationContract()] double Add(double A, double B); } public class Math : IMath { public double Add(double A, double B) { return A + B; } } public sealed class Test { static void Main() { // Code not shown. } public void Run() { // This code is written by an application developer. // Create a channel factory. BasicHttpBinding myBinding = new BasicHttpBinding(); EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1"); ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint); // Create a channel. IMath wcfClient1 = myChannelFactory.CreateChannel(); double s = wcfClient1.Add(3, 39); Console.WriteLine(s.ToString()); ((IClientChannel)wcfClient1).Close(); // Create another channel. IMath wcfClient2 = myChannelFactory.CreateChannel(); s = wcfClient2.Add(15, 27); Console.WriteLine(s.ToString()); ((IClientChannel)wcfClient2).Close(); myChannelFactory.Close(); } }
https://docs.microsoft.com/en-us/dotnet/framework/wcf/configuring-client-behaviors
public class Client { public static void Main() { try { // Picks up configuration from the config file. ChannelFactory<ISampleServiceChannel> factory = new ChannelFactory<ISampleServiceChannel>("WSHttpBinding_ISampleService"); // Add the client side behavior programmatically to all created channels. factory.Endpoint.Behaviors.Add(new EndpointBehaviorMessageInspector()); ISampleServiceChannel wcfClientChannel = factory.CreateChannel(); // Making calls. Console.WriteLine("Enter the greeting to send: "); string greeting = Console.ReadLine(); Console.WriteLine("The service responded: " + wcfClientChannel.SampleMethod(greeting)); Console.WriteLine("Press ENTER to exit:"); Console.ReadLine(); // Done with service. wcfClientChannel.Close(); Console.WriteLine("Done!"); } catch (TimeoutException timeProblem) { Console.WriteLine("The service operation timed out. " + timeProblem.Message); Console.Read(); } catch (FaultException<SampleFault> fault) { Console.WriteLine("SampleFault fault occurred: {0}", fault.Detail.FaultMessage); Console.Read(); } catch (CommunicationException commProblem) { Console.WriteLine("There was a communication problem. " + commProblem.Message); Console.Read(); } }
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/channel-factory-and-caching
WCF client applications use the ChannelFactory<TChannel> class to create a communication channel with a WCF service. Creating ChannelFactory<TChannel> instances incurs some overhead because it involves the following operations:
-
Constructing the ContractDescription tree
-
Reflecting all of the required CLR types
-
Constructing the channel stack
-
Disposing of resources
To help minimize this overhead, WCF can cache channel factories when you are using a WCF client proxy.
WCF client proxies generated with ServiceModel Metadata Utility Tool (Svcutil.exe) are derived from ClientBase<TChannel>. ClientBase<TChannel> defines a static CacheSetting property that defines channel factory caching behavior. Cache settings are made for a specific type. For example, setting ClientBase<ITest>.CacheSettings
to one of the values defined below will affect only those proxy/ClientBase of type ITest
. The cache setting for a particular ClientBase<TChannel> is immutable as soon as the first proxy/ClientBase instance is created.
class Program { static void Main(string[] args) { ClientBase<ITest>.CacheSettings = CacheSettings.AlwaysOn; foreach (string msg in messages) { using (TestClient proxy = new TestClient (new BasicHttpBinding(), new EndpointAddress(address))) { // ... proxy.Test(msg); // ... } } } } // Generated by SvcUtil.exe public partial class TestClient : System.ServiceModel.ClientBase, ITest { }
I have a client class that abstracts a WCF client proxy. This class currently does not take advantage of any pooling. Each instance of this client class uses its own channel factory to create a channel/proxy instance. When the client class is disposed, then the underlying channel/proxy is closed.
My question is, should I also be closing the ChannelFactory? Since each channel factory only has one channel/proxy, and it is already closed by the time I would potentially close the factory, am I gaining anything by explicitely closing the factory?
In some preliminary performance testing, when I explicitely closed the factory in the dispose method of my class, it added about 10% time compared to when I did not explicitely close this. This was over 12000 consecutive calls to the server, and the time went from about 150 seconds to about 165 seconds.
My understanding is that if I keep the factory around and create multiple channels/proxies from one factory instance, then I can take advantage of connection pooling, and then calling close on the factory would have the same affect as calling close on all of its current live channels/proxies.
I do plan on changing things in the future to take advantage of pooling, but for now can someone tell me is there any advantage in closing the factory explicitely if it will only ever have one channel/proxy that is already closed? I'm guessing that there is not, but I wanted to make sure there was nothing I'm unaware of.
Thanks
Considering you are restarting the factory cleaning up resources is critical if the perfornmance loss is an issue you should cache or use static factories.
I think the behaviour of not closing it and leaving it to dispose will depend on the transport in question.
Here is one i use note its not ideal as it requires the caller to close the connection ( some dispose support would be nice) but at leats its stronlgly typed so we are closing the right proxy. There are also more elgant solutions around that pool the connection object.
public static class ProxyHelper<T>
{
private static ChannelFactory<T> innerClient;
private static Object lockObj = new object();
private static string endPointName = "*";
static public T GetChannel()
{
ChannelFactory<T> cf = Client; // do not lock or will get dead lock
lock (lockObj)
{
T channel = default(T);
try
{
channel = cf.CreateChannel();
if (((ICommunicationObject)channel).State == CommunicationState.Faulted)
throw new ArgumentException("Channel is faulted");
((ICommunicationObject)channel).Open();
//#if Debug
// ((ICommunicationObject)channel).Closed += new EventHandler(ProxyHelper_Closed);
return channel;
}
catch (Exception)
{
if (cf.State != CommunicationState.Opened)
{
cf.Abort();
innerClient = null;
}
if (channel != null && ((ICommunicationObject)channel).State != CommunicationState.Opened)
{
((ICommunicationObject)channel).Abort();
}
throw;
}
}
}
static void cf_Faulted(object sender, EventArgs e)
{
if (innerClient != null)
innerClient.Faulted -= new EventHandler(cf_Faulted);
innerClient = null;
}
static public void Close(T channel)
{
if (channel != null)
{
// ((ICommunicationObject)channel).Closed -= new EventHandler(ProxyHelper_Closed); // remove handler
((ICommunicationObject)channel).Close();
}
}
static private ChannelFactory<T> CreateProxy()
{
ChannelFactory<T> cf = new ChannelFactory<T>(endPointName); // used reflector to find this
cf.Faulted += new EventHandler(cf_Faulted);
cf.Open();
return cf;
}
private static ChannelFactory<T> Client
{
get
{
lock (lockObj)
{
if (innerClient == null)
innerClient = CreateProxy();
}
return ProxyHelper<T>.innerClient;
}
}
static public string EndPointName
{
get { return endPointName; }
set { endPointName = value; }
}
public static ChannelFactory<T> InnerChannelFactory
{
get { return ProxyHelper<T>.innerClient; }
}
} //ProxyHelper<T>
Regards,
Ben
https://www.codeproject.com/tips/197531/do-not-use-using-for-wcf-clients
Traditional using() block disposes WCF clients incorrectly when there's a communication exception, eg dropping network connection. It raises exception during the dispose and thus the resources held by the WCF client aren't released properly. After some time, you end up with memory leaks. You know that any
IDisposable
object must be disposed using using. So, you have been using using to wrap WCF service’s ChannelFactory
and Clients like this:using(var client = new SomeClient()) { . . . }Or, if you are doing it the hard and slow way (without really knowing why), then:
using(var factory = new ChannelFactory<ISomeService>()) { var channel= factory.CreateChannel(); . . . }That’s what we have all learnt in school right? We have learnt it wrong! When there’s a network related error or the connection is broken, or the call is timed out before
Dispose
is called by the using keyword, then it results in the following exception when the using keyword tries to dispose the channel:failed: System.ServiceModel.CommunicationObjectFaultedException : The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state. Server stack trace: at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout) Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout) at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout) at System.ServiceModel.ClientBase`1.Close() at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
There are various reasons for which the underlying connection can be at broken state before the using block is completed and the
.Dispose()
is called. Common problems like network connection dropping, IIS doing an app pool recycle at that moment, some proxy sitting between you and the service dropping the connection for various reasons and so on. The point is, it might seem like a corner case, but it’s a likely corner case. If you are building a highly available client, you need to treat this properly before you go-live.So, do NOT use
using
on WCF Channel/Client/ChannelFactory
. Instead you need to use an alternative. Here’s what you can do:First create an extension method.
public static class WcfExtensions { public static void Using<T>(this T client, Action<T> work) where T : ICommunicationObject { try { work(client); client.Close(); } catch (CommunicationException e) { client.Abort(); } catch (TimeoutException e) { client.Abort(); } catch (Exception e) { client.Abort(); throw; } } }
Then use this instead of the using keyword:
new SomeClient().Using(channel => { channel.Login(username, password); });
Or if you are using
ChannelFactory
then:new ChannelFactory<ISomeService>().Using(channel => { channel.Login(username, password); });
Enjoy!
Using ChannelFactory with Credentials
Today in this article we shall see how to use Channel Factory to call service with Authentication enabled mainly using Network credentials using Basic Authentication i.e by providing UserName and Password credentials techniques etc.
Getting Started
We already covered basics in our last article. Please visit below article for more details.
I already have a sample WCF service with contract details as below. This service we will be consuming within the .NET Core WebAPI application.
However, you can use the below technique for other types of applications as well like C# .NET Core Console, Form application (.NET Core 3.0) or ASP.NET Core MVC app, etc.
WCF Service Contract
This service has a method GetOrderData() which returns an order data for a given order ID input.
Let’s run this WCF service locally. We shall try connecting this service using Channel factory from the .NET Core app.
Create ASP.NET Core API
Let’s start creating a WebAPI application (you can create any other type of project as required.)
Please add below code in the location of your choice usually repository or Infra layer of your module.
Adding ClientCredentials to the Channelfactory
Adding ClientCredentials instance and assign it to the Channelfactory asb below,
1 2 3 4 5 |
//Add credentials
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = "thecodebuzz" ;
loginCredentials.UserName.Password = "******" ;
myChannelFactory.Endpoint.Behaviors.Add(loginCredentials);
|
This is an example only, complete code within ASP.NET Core GET method,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[HttpGet( "{id}" )]
public ActionResult< string > Get( int id)
{
//Define address and binding
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new
EndpointAddress( "http://localhost:60141/Order.svc" );
//Create Channel Factory Instance
ChannelFactory<IOrderService> myChannelFactory =
new ChannelFactory<IOrderService>(myBinding, myEndpoint);
//Add credentials
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = "thecodebuzz" ;
loginCredentials.UserName.Password = "******" ;
myChannelFactory.Endpoint.Behaviors.Add(loginCredentials);
// Create a channel
IOrderService wcfClient = myChannelFactory.CreateChannel();
string result = wcfClient.GetOrderData(id);
return result;
}
|
That’s all. Simply execute your GET API, you shall see result as below.
Are you dealing with any complex scenarios? Please do let me know or sound off your comments below.
Other references:
- Consuming WCF Web services in .NET Core using Connected Services
- Consuming WCF Web Services in .NET Core using Global Tool
- Consume WCF services in .NET Core by Exposing them as REST API
Summary
Today in this article we shall learn how to use Channel Factory to call service with Authentication enabled mainly using Network credentials (ex.using Basic authentication) techniques etc.
Use ChannelFactory to Consume WCF Web Services in .NET Core
In our previous article, we learned a few techniques of consuming WCF services in .NET Core applications using proxy generations and for the same, we looked at few useful tools like using
Today we will see yet another simple approach of using a Channelfactory for consuming WCF( Windows Communication Foundation) services in the .NET Core applications
Let’s first see the pros and cons of this approach before digging into more details.
Advantages of ChannelFactory
- No need to create or maintain a proxy/client code
- No need to add a service reference or connected service reference
- This is the best option provided the contract definition is known.
Disadvantage
- Require prior knowledge service contract definition
Please note that Channel factory options can be leveraged if the service contract definition is known already. Perfect for an internal organization where service contract definition can be shared easily.
Getting Started
I already have a sample WCF service with contract details as below. This service we will be consuming within the .NET Core WebAPI application.
However, you can use the below technique for other types of applications as well like C# .NET Core Console, Form application (.NET Core 3.0) or ASP.NET Core MVC app, etc.
WCF Service Contract
This service has a method GetOrderData() which returns an order data for a given order ID input.
Let’s run this WCF service locally. We shall try connecting this service using Channel factory from the .NET Core app.
Create ASP.NET Core WebAPI
Let’s start creating a WebAPI application (you can create any other type of project as required.)
Please add below code in any of the Controller methods.
This is an example only, we will add code to the GET method,
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[HttpGet( "{id}" )]
public ActionResult< string > Get( int id)
{
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new
EndpointAddress( "http://localhost:60141/Order.svc" );
ChannelFactory<IOrderService> myChannelFactory =
new ChannelFactory<IOrderService>(myBinding, myEndpoint);
// Create a channel
IOrderService wcfClient1 = myChannelFactory.CreateChannel();
string result = wcfClient1.GetOrderData(id);
return result;
}
|
As shown above, ChannelFactory lets you define endpoint – ABC i.e Address, Binding, and Contract for service and helps you connect to the target WCF service from .NET Core framework.
The generic interface ChannelFactory<TChannel> class helps to create a communication channel with a WCF service. Please do also check cache-control techniques for channel factories to get better performance.
Next step is to add the contract/Interface definition in your .NET core project.
The Contract/Interface definition looks as below,
That’s all. Simply execute your GET API, you shall see result as below.
This was very much basic we covered in this article. I hope this helps you get started.
You can also use Channel Factory with Credentials if needed.
Are you dealing with any complex scenarios? Please do let me know or sound off your comments below.
Other references:
- Consuming WCF Web services in .NET Core using Connected Services
- Consuming WCF Web Services in .NET Core using Global Tool
- Consume WCF services in .NET Core by Exposing them as REST API
Summary:
WCF Web Service integration with modern technologies like .NET Core services is possible. In this post, we learned the simple approach of using ChannelFactory to connect any .NET Core application with WCF Web services. We understood with this approach we need not have to create a proxy (client-side) code or maintain service references etc. as ChannelFactory wraps up those things provided Service contract definition is provided.
https://stackoverflow.com/questions/3200197/creating-wcf-channelfactoryt
creating WCF ChannelFactory<T>
I'm trying to convert an existing .NET Remoting application to WCF. Both server and client share common interface and all objects are server-activated objects.
In WCF world, this would be similar to creating per-call service and using ChannelFactory<T>
to create a proxy. I'm struggling a bit with how to properly create ChannelFactory<T>
for an ASP.NET client.
For performance reasons, I want to cache ChannelFactory<T>
objects and just create channel every time I call the service. In .NET remoting days, there used to be RemotingConfiguration.GetRegisteredWellknownClientTypes()
method to get a collection of client objects that I could then cache. It appears, in WCF world there is no such thing, although I was able to get a collection of endpoints from config file.
Now here is what I think will work. I can create something like this:
public static ProxyHelper
{
static Dictionary<Type, object> lookup = new Dictionary<string, object>();
static public T GetChannel<T>()
{
Type type = typeof(T);
ChannelFactory<T> factory;
if (!lookup.ContainsKey(type))
{
factory = new ChannelFactory<T>();
lookup.Add(type, factory);
}
else
{
factory = (ChannelFactory<T>)lookup[type];
}
T proxy = factory.CreateChannel();
((IClientChannel)proxy).Open();
return proxy;
}
}
I think the above code will work, but I'm a bit worried about multiple threads trying to add new ChannelFactory<T>
objects if it's not in the lookup. Since I'm using .NET 4.0, I was thinking about using ConcurrentDictionary
and use GetOrAdd()
method or use TryGetValue()
method first to check if ChannelFactory<T>
exists and it does not exist, then use GetOrAdd()
method. Not sure about performance though of ConcurrentDictionary.TryGetValue()
and ConcurrentDictionary.GetOrAdd()
method.
Another minor question is whether I need to call ChannelFactory.Close()
method on channel factory objects after ASP.NET application ends or can I just let .NET framework dispose the channel factory objects on its own. The proxy channel will always be closed after calling service method by using ((IChannel)proxy).Close()
method.
Here's a helper class that I use to handle channel factories:
public class ChannelFactoryManager : IDisposable
{
private static Dictionary<Type, ChannelFactory> _factories = new Dictionary<Type,ChannelFactory>();
private static readonly object _syncRoot = new object();
public virtual T CreateChannel<T>() where T : class
{
return CreateChannel<T>("*", null);
}
public virtual T CreateChannel<T>(string endpointConfigurationName) where T : class
{
return CreateChannel<T>(endpointConfigurationName, null);
}
public virtual T CreateChannel<T>(string endpointConfigurationName, string endpointAddress) where T : class
{
T local = GetFactory<T>(endpointConfigurationName, endpointAddress).CreateChannel();
((IClientChannel)local).Faulted += ChannelFaulted;
return local;
}
protected virtual ChannelFactory<T> GetFactory<T>(string endpointConfigurationName, string endpointAddress) where T : class
{
lock (_syncRoot)
{
ChannelFactory factory;
if (!_factories.TryGetValue(typeof(T), out factory))
{
factory = CreateFactoryInstance<T>(endpointConfigurationName, endpointAddress);
_factories.Add(typeof(T), factory);
}
return (factory as ChannelFactory<T>);
}
}
private ChannelFactory CreateFactoryInstance<T>(string endpointConfigurationName, string endpointAddress)
{
ChannelFactory factory = null;
if (!string.IsNullOrEmpty(endpointAddress))
{
factory = new ChannelFactory<T>(endpointConfigurationName, new EndpointAddress(endpointAddress));
}
else
{
factory = new ChannelFactory<T>(endpointConfigurationName);
}
factory.Faulted += FactoryFaulted;
factory.Open();
return factory;
}
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
try
{
channel.Close();
}
catch
{
channel.Abort();
}
throw new ApplicationException("Exc_ChannelFailure");
}
private void FactoryFaulted(object sender, EventArgs args)
{
ChannelFactory factory = (ChannelFactory)sender;
try
{
factory.Close();
}
catch
{
factory.Abort();
}
Type[] genericArguments = factory.GetType().GetGenericArguments();
if ((genericArguments != null) && (genericArguments.Length == 1))
{
Type key = genericArguments[0];
if (_factories.ContainsKey(key))
{
_factories.Remove(key);
}
}
throw new ApplicationException("Exc_ChannelFactoryFailure");
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_syncRoot)
{
foreach (Type type in _factories.Keys)
{
ChannelFactory factory = _factories[type];
try
{
factory.Close();
continue;
}
catch
{
factory.Abort();
continue;
}
}
_factories.Clear();
}
}
}
}
Then I define a service invoker:
public interface IServiceInvoker
{
R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}
and an implementation:
public class WCFServiceInvoker : IServiceInvoker
{
private static ChannelFactoryManager _factoryManager = new ChannelFactoryManager();
private static ClientSection _clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
public R InvokeService<T, R>(Func<T, R> invokeHandler) where T : class
{
var endpointNameAddressPair = GetEndpointNameAddressPair(typeof(T));
T arg = _factoryManager.CreateChannel<T>(endpointNameAddressPair.Key, endpointNameAddressPair.Value);
ICommunicationObject obj2 = (ICommunicationObject)arg;
try
{
return invokeHandler(arg);
}
finally
{
try
{
if (obj2.State != CommunicationState.Faulted)
{
obj2.Close();
}
}
catch
{
obj2.Abort();
}
}
}
private KeyValuePair<string, string> GetEndpointNameAddressPair(Type serviceContractType)
{
var configException = new ConfigurationErrorsException(string.Format("No client endpoint found for type {0}. Please add the section <client><endpoint name=\"myservice\" address=\"http://address/\" binding=\"basicHttpBinding\" contract=\"{0}\"/></client> in the config file.", serviceContractType));
if (((_clientSection == null) || (_clientSection.Endpoints == null)) || (_clientSection.Endpoints.Count < 1))
{
throw configException;
}
foreach (ChannelEndpointElement element in _clientSection.Endpoints)
{
if (element.Contract == serviceContractType.ToString())
{
return new KeyValuePair<string, string>(element.Name, element.Address.AbsoluteUri);
}
}
throw configException;
}
}
Now every time you need to call a WCF service you could use this:
WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
proxy => proxy.SomeMethod()
);
This assumes that you've defined a client endpoint for the IMyServiceContract
service contract in the config file:
<client>
<endpoint
name="myservice"
address="http://example.com/"
binding="basicHttpBinding"
contract="IMyServiceContract" />
</client>
Share
Improve this answer
edited Nov 8 '10 at 8:17
answered Jul 8 '10 at 5:57
Darin Dimitrov
983k260260 gold badges32253225 silver badges28922892 bronze badges
Yes, if you want to create something like this - a static class to hold all those ChannelFactory<T>
instances - you definitely have to make sure this class is 100% thread-safe and cannot stumble when accessed concurrently. I haven't used .NET 4's features much yet, so I cannot comment on those specifically - but I would definitely recommend to make this as safe as possible.
As for your second (minor) question: the ChannelFactory itself is a static class - so you cannot really call a .Close()
method on it. If you meant to ask whether or not to call the .Close()
method on the actual IChannel
, then again: yes, try your best to be a good citizen and close those channels if you ever can. If you miss one, .NET will take care of it - but don't just toss your unused channels on the floor and go on - clean up after yourself! :-)
https://geeksarray.com/blog/creating-dynamic-proxy-using-wcf-channelfactory
Creating dynamic proxy using WCF ChannelFactory
This article helps you to generate a dynamic WCF service proxy using ChannelFactory. This will also describe what is ChannelFactory, the difference between ChannelFactory and Proxy, and when to use proxy or ChannelFactory.
What is WCF ChannelFactory
ChannelFactory enables you to dynamically create a proxy object based on the Service Contract alone. You do not require to explicitly generate a proxy class or manually define it. This is helpful in a scenario where your service address changes frequently or DataContract changes to a newer version and as a client, you have lot of references to those services. So after changes happen at the service side, the client must update proxy.
Difference between ChannelFactory and Proxy
Proxy | ChannelFactory |
You need service URL and metadata endpoint should be exposed from the service side. |
You have to have direct access to ServiceContracts, DataContracts, MessageContracts. |
Simple to use as you get the required configuration through the proxy. |
Comparing with Proxy, creatin ChannelFactory is complex as you need to configure at the code level. |
You can create a WCF service proxy using WSDL or by adding a Service Reference in Visual Studio Solution explorer |
You can create ChannelFactory by adding reference to assembly where required ServiceContract, DataContract, MessageContracts resides. |
Proxy has below restrictions
|
ChannelFactory has below restrictions
|
When to use Proxy?
Creating a proxy by Visual Studio or SvcUtil.exe tool will update the client configuration file to make necessary changes. If you know your services are generic enough and services should be loosely coupled you should use Proxy. As it depends on Service schema, any changes to existing service schema force client to rebuild.
When to use ChannelFactory
In some scenarios, services need to be tightly coupled and need to share not only ServiceContract but also other dependant methods, contracts etc. When you do not want to force clients to change or rebuild on frequent service schema, you have to share utility methods, different internal services, contracts along with ServiceContract then you should use ChannelFactory.
Below details explains about how to use ChannelFactory to create a dynamic proxy
Dynamically creating WCF Proxy using ChannelFactory
Create Shared WCF Contracts
As described in the introduction, for using ChannelFactory you need to have access to ServiceContract and related DataContract.
Go through Share WCF Service Contracts assembly which helps you to create a shared library which includes ServiceConracts and DataContracts which can be shared by WCF Services and its clients.
WCF Client with ChannelFactory
In this step, we will create a WCF client that uses ChannelFactory objects to call WCF Services.
Open NorthwindDynamicProxy solution which is created in the previous step. Add New Console Application to solution by right click on solution NorthwindDynamicProxy -> New Project -> Console Application. Name it as NorthwindClient and click Ok.
Now Add Reference to NorthwindContracts assembly to NorthwindClient console application so that you will be able to access required contracts.
CategoryServiceClient
In a shared assembly, we have a CategoryService contract which we will implement using ChannelFactory.
Add a new class to NorthwindClient console application and name it as CategoryServiceClient.
CategoryService has two method
1. GetCategoryName which returns category name based on the given category ID. Below is the implementation of this GetCategoryName.
public static string GetCategoryName(int categoryID)
{
string categoryName = string.Empty;
WSHttpBinding myBinding = new WSHttpBinding();
EndpointAddress myEndpoint = new
EndpointAddress("http://localhost:7741/CategoryServiceHost.svc");
ChannelFactory<ICategoryService> myChannelFactory =
new ChannelFactory<ICategoryService>(myBinding, myEndpoint);
ICategoryService instance = myChannelFactory.CreateChannel();
categoryName = instance.GetCategoryName(categoryID);
myChannelFactory.Close();
return categoryName;
}
2. GetCategoryDetails which returns category details as Category DataContract. Below is GetCategoryDetails of this method.
public static Category GetCategoryDetails(int categoryID)
{
Category category = new Category();
WSHttpBinding myBinding = new WSHttpBinding();
EndpointAddress myEndpoint = new
EndpointAddress("http://localhost:7741/CategoryServiceHost.svc");
ChannelFactory<ICategoryService> myChannelFactory
= new ChannelFactory<ICategoryService>(myBinding, myEndpoint);
ICategoryService instance = myChannelFactory.CreateChannel();
category = instance.GetCategoryDetails(categoryID);
myChannelFactory.Close();
return category;
}
We assume that you have created a WCF service and hosted in IIS as suggested in the first step. In this step we create a declartive endpoint to CategoryServiceHost.
ChannelFactory<ICategoryService> creates a new instance of CategoryService and ChannelFactory.CreateChannel() returns instance using which we can call service operations dynamically.
Call to dynamic service proxy class
Open Program.cs file of NorthwindClient console application. From Program.cs file we will call CategoryServiceClient's methods which call CategoryService with dynamic proxy and returns the output.
Add below code to Program.cs
using NorthwindContracts.DataContracts;
using NorthwindContracts.ServiceContracts;
namespace NorthwindClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(CategoryServiceClient.GetCategoryName(10));
Category category =
CategoryServiceClient.GetCategoryDetails(10);
if (category != null)
{
Console.WriteLine("Category ID " + category.CategoryID);
Console.WriteLine("Category Name: "
+ category.CategoryName);
Console.WriteLine("Category Description:"
+ category.CategoryDescription);
}
Console.Read();
}
}
}
Change existing WCF contracts
In this step, we will change the Category data contract and will test dependent clients. Open the NorthwindContracts class library project. Open Category DataContract from DataContract folder.
Add below new DataMember.
[DataMember]
public string CategoryURL { get; set; }
Now open NorthwindServices WCF Service Library project and CategoryService from CategoryServices folder.
Change implementation of GetCategoryDetails method as
public Category GetCategoryDetails(int categoryID)
{
Category category = new Category();
category.CategoryID = 1;
category.CategoryName = "Beverages";
category.CategoryDescription =
"Soft drinks, coffees, teas, beers, and ales";
category.CategoryURL = "http://northwind.com/Beverages";
return category;
}
Notice that we have added CategoryURL in addition to the existing implementation. Now run your client code without changing anything. Client code should give you the same output shown previously. As Proxy is created dynamically you do not need to make any changes or rebuild to client code.
Now change client code from Program.cs to display CategoryURL as
Category category = CategoryServiceClient.GetCategoryDetails(10);
if (category != null)
{
Console.WriteLine("Category ID " + category.CategoryID);
Console.WriteLine("Category Name: " + category.CategoryName);
Console.WriteLine("Category Description:" +
category.CategoryDescription);
Console.WriteLine("Category URL: " + category.CategoryURL);
}
Notice that we did not change anything to CategoryServiceClient which actually creates ChannelFactory and dynamic proxy to services.
How to use the ChannelFactory
https://stackoverflow.com/questions/7841748/wcf-channel-and-channelfactory-caching
So I've decided to up the performance a bit in my WCF application, and attempt to cache Channels and the ChannelFactory. There's two questions I have about all of this that I need to clear up before I get started.
1) Should the ChannelFactory be implemented as a singleton?
2) I'm kind of unsure about how to cache/reuse individual channels. Do you have any examples of how to do this you can share?
It's probably important to note that my WCF service is being deployed as a stand alone application, with only one endpoint.
EDIT:
Thank you for the responses. I still have a few questions though...
1)I guess I'm confused as to where the caching should occur. I'm delivering a client API that uses this code to another department in our company. Does this caching occur on the client?
2)The client API will be used as part of a Silverlight application, does this change anything? In particular, what caching mechanisms are available in such a scenario?
3)I'm still not clear about the design of the GetChannelFactory method. If I have only one service, should only one ChannelFactory ever be created and cached?
I still haven't implemented any caching feature (because I'm utterly confused about how it should be done!), but here's what I have for the client proxy so far:
namespace MyCompany.MyProject.Proxies
{
static readonly ChannelFactory<IMyService> channelFactory =
new ChannelFactory<IMyService>("IMyService");
public Response DoSomething(Request request)
{
var channel = channelFactory.CreateChannel();
try
{
Response response = channel.DoSomethingWithService(request);
((ICommunicationObject)channel).Close();
return response;
}
catch(Exception exception)
{
((ICommenicationObject)channel).Abort();
}
}
}
For #3, yes, only one channel factory should be created. Basically, you'll have one channel factory for each of service endpiont you have. In my case, we have about 6 so far, spread primarily across 2 tiers. In your case, if you're only ever going to have one service, use by one app, you can simply do what you're doing above. You're code above is on the right track. Caching will be based on the needs of the app.
– Tim
Oct 21 '11 at 17:20
- @Tim -thanks for all of your help. I really do appreciate it! I think in my case, the fact that my service was so "simple" was causing me confusion looking over these other examples where there were multiple endpoints. Me = less confused now = Tim did a great job explaining! Thanks dude! – Didaxis Oct 21 '11 at 18:31
- You're quite welcome. Glad I could help - happy coding! – Tim Oct 21 '11 at 18:36
Use the ChannelFactory to create an instance of the factory, then cache that instance. You can then create communicatino channels as needed/desired from the cached istance.
Do you have a need for multiple channel factories (i.e.., are there multiple services)? In my experience, that's where you'll see the biggest benefit in performance. Creating a channel is a fairly inexpensive task; it's setting everything up at the start that takes time.
I would not cache individual channels - I'd create them, use them for an operation, and then close them. If you cache them, they may time out and the channel will fault, then you'll have to abort it and create a new one anyway.
Not sure why you'd want to usea singleton to implement ChannelFactory, especially if you're going to create it and cache it, and there's only one endpoint.
I'll post some example code later when I have a bit more time.
UPDATE: Code Examples
Here is an example of how I implemented this for a project at work. I used ChannelFactory<T>
, as the application I was developing is an n-tier app with several services, and more will be added. The goal was to have a simple way to create a client once per life of the application, and then create communication channels as needed. The basics of the idea are not mine (I got it from an article on the web), though I modified the implementation for my needs.
I have a static helper class in my application, and within that class I have a dictionary and a method to create communication channels from the channelf factory.
The dictionary is as follows (object is the value as it will contain different channel factories, one for each service). I put "Cache" in the example as sort of a placeholder - replace the syntax with whatever caching mechanism you're using.
public static Dictionary<string, object> OpenChannels
{
get
{
if (Cache["OpenChannels"] == null)
{
Cache["OpenChannels"] = new Dictionary<string, object>();
}
return (Dictionary<string, object>)Cache["OpenChannels"];
}
set
{
Cache["OpenChannels"] = value;
}
}
Next is a method to create a communication channel from the factory instance. The method checks to see if the factory exists first - if it does not, it creates it, puts it in the dictionary and then generates the channel. Otherwise it simply generates a channel from the cached instance of the factory.
public static T GetFactoryChannel<T>(string address)
{
string key = typeof(T.Name);
if (!OpenChannels.ContainsKey(key))
{
ChannelFactory<T> factory = new ChannelFactory<T>();
factory.Endpoint.Address = new EndpointAddress(new System.Uri(address));
factory.Endpoint.Binding = new BasicHttpBinding();
OpenChannels.Add(key, factory);
}
T channel = ((ChannelFactory<T>)OpenChannels[key]).CreateChannel();
((IClientChannel)channel).Open();
return channel;
}
I've stripped this example down some from what I use at work. There's a lot you can do in this method - you can handle multiple bindings, assign credentials for authentication, etc. Its pretty much your one stop shopping center for generating a client.
Finally, when I use it in the application, I generally create a channel, do my business, and close it (or abort it if need be). For example:
IMyServiceContract client;
try
{
client = Helper.GetFactoryChannel<IMyServiceContract>("http://myserviceaddress");
client.DoSomething();
// This is another helper method that will safely close the channel,
// handling any exceptions that may occurr trying to close.
// Shouldn't be any, but it doesn't hurt.
Helper.CloseChannel(client);
}
catch (Exception ex)
{
// Something went wrong; need to abort the channel
// I also do logging of some sort here
Helper.AbortChannel(client);
}
Hopefully the above examples will give you something to go on. I've been using something similar to this for about a year now in a production environment and it's worked very well. 99% of any problems we've encountered have usually been related to something outside the application (either external clients or data sources not under our direct control).
Let me know if anything isn't clear or you have further questions.
Share Improve this answer edited Oct 21 '11 at 5:53 answered Oct 20 '11 at 20:36 Tim 27.6k88 gold badges5959 silver badges7474 bronze badges- Thanks Tim. You really did provide some valuable information. I'll definitely be looking out for your example! – Didaxis Oct 20 '11 at 23:13
- @user384080 - the code is in my answer. If that isn't clear, let me know. Thanks. – Tim Feb 21 '12 at 7:59
- 1 @Tim There is a bug in your implementation. You cache factories by contract type no matter what is the address. You should have a key which contain both contract type and address. – Anubis Dec 2 '17 at 13:42
- The exception handling seems lacking. You do not know who threw the exception (could be DoSomething) so why did you decide you need to abort the channel instead of just closing it ? – Cesar Sep 29 '20 at 11:30
-
@Cesar - This is stripped down code from what I used at work. At work, more is done, and I included a comment in sample code
// I also do logging of some sort here
. – Tim Sep 30 '20 at 6:08
I'm building a series of WCF Services that are going to be used by more than one application. Because of that I'm trying to define a common library to access WCF services.
Knowing that each service request made by different users should use a different Channel I'm thinking in cache the Channel per-request (HttpContext.Current.Items
) and cache the ChannelFactory used to create the channel per Application (HttpApplication.Items
) since I can create more than one channel with the same ChannelFactory
.
However, I have a question regarding this cache mechanism when it comes to closing the ChannelFactory and Channel.
- Do I need to close the Channel after it's used, at the end of the request, or is it ok to leave it there to be closed (?) when the context of that request dies?
- What about ChannelFactory? Since each channel is associated with the ChannelFactory that created it, is it safe to keep the same ChannelFactory during the life of the application process (AppDomain)?
This is the code I'm using to manage this:
public class ServiceFactory
{
private static Dictionary<string, object> ListOfOpenedChannels
{
get
{
if (null == HttpContext.Current.Items[HttpContext.Current.Session.SessionID + "_ListOfOpenedChannels"])
{
HttpContext.Current.Items[HttpContext.Current.Session.SessionID + "_ListOfOpenedChannels"] = new Dictionary<string, object>();
}
return (Dictionary<string, object>)HttpContext.Current.Items[HttpContext.Current.Session.SessionID + "_ListOfOpenedChannels"];
}
set
{
HttpContext.Current.Items[HttpContext.Current.Session.SessionID + "_ListOfOpenedChannels"] = value;
}
}
public static T CreateServiceChannel<T>()
{
string key = typeof(T).Name;
if (ListOfOpenedChannels.ContainsKey(key))
{
return (T)ListOfOpenedChannels[key];
}
else
{
ChannelFactory<T> channelF = new ChannelFactory<T>("IUsuarioService");
T channel = channelF.CreateChannel();
ListOfOpenedChannels.Add(key, channel);
return channel;
}
}
}
Thanks!
Update
As of .Net 4.5 there is a built in caching options for factories ChannelFactory Caching .NET 4.5
Share Improve this answer edited Sep 13 '16 at 9:28 answered Dec 1 '09 at 12:52 MattC 3,90411 gold badge3232 silver badges4747 bronze badges- 4 +1 exactly - cache the "expensive" part - the ChannelFactory - but create the channels as needed and close/dispose as early as possible – marc_s Dec 1 '09 at 13:11
- Great guys, so if I keep just ONE ChannelFactory cached during the lifetime of the application and reuse it in all channel creations I won't have problems, right? – tucaz Dec 1 '09 at 14:25
- One channel factory per service type, correct. This way you don't have to do all the reflection and type creation every call, which is why for performance you shouldn't use the proxy generated by VS as the class is gens uses a new factory every call. – MattC Dec 1 '09 at 16:59
- 1 "you shouldn't use the proxy generated by VS as the class is gens uses a new factory every call" - according to the link at the end that particular problem was solved with .NET 3.5 so that ChannelFactory is cached within ClientBase. blogs.msdn.com/wenlong/archive/2007/10/27/… – Xiaofu Mar 13 '10 at 15:45
-
@Xiaofu: It is true that
ClientBase<T>
will use an MRU cache on theChannelFactory<T>
in some cases. It will not cache the ChannelFactoy if theEndpoint
orClientCredentials
are changed or if theBinding
is programmatically defined. There is a way to force the caching of the ChannelFactory by setting the CacheSettings toCacheSettings.AlwaysOn
. msdn.microsoft.com/en-us/library/hh314046(v=vs.110).aspx – Derek W Aug 16 '14 at 1:19
ChannelFactory Caching in WCF 4.5 – An Insight
In this article I will walk you through the ChannelFactory caching feature introduced as part of WCF 4.5. This article will also provide a good insight about the various caching modes available along with suitable source code samples.
Channel Factory – A Look
ChannelFactory is a factory class for creating the channel instances in a WCF client. This class simplifies the task of creating the channel between a WCF service and a client. When an instantiation is done on the client it means a channel is established with the WCF service and creates the proxy of the service for further communications like that of a method call. A channel will be created over an address, binding and contract (service endpoint) combination. Following is a sample code where ChannelFactory is used for the communication with the WCF service.
namespace ChannelFactoryCachingDemo { class Program { static void Main(string[] args) { ChannelFactory<IMyWcfService> channelFactory = new ChannelFactory<IMyWcfService>("<MyServiceEndPointConfigName>"); var channel = channelFactory.CreateChannel(); string message = channel.GetAGreetingMessage(); Console.WriteLine(message); ((IClientChannel)channel).Close(); } } }
Channel Factory – Caching
As I mentioned in the previous section the channel will be created while creating the channel instance, it also takes a certain amount or resource overhead for achieving it on the fly. Prior to the .NET Framework 4.5 developers had to implement a custom channel factory caching mechanism but now Microsoft has introduced in build caching for ChannelFactory. WCF proxy, which is generated inherit from the class named ClientBase and this class holds the property names CacheSettings to hold the caching configuration for the ChannelFactory. Also the creation of the channel will be wrapped under the ClientBase creation.
ClientBase<IClientChannel>.CacheSetting = CacheSetting.AlwaysOn;
There are three modes of ChannelFactory caching available and they are as follows.
AlwaysOn
This mode will allow the developer to make the ChannelFactory to be cached always no matter if the endpoint or other security –sensitive properties are updated. The developer should have done proper analysis so that using this mode wouldn’t affect the service calls in any means. Mentioned below is a sample code.
class Program { static void Main(string[] args) { ClientBase<IClientChannel>.CacheSetting = CacheSetting.AlwaysOn; MyWcfServiceClient client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); //The channel will be cached for any future calls //This will make use of the cached channelfactory object client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); } }
Default
The Default mode will cache the ChannelFactory object and use it until any of the client’s security sensitive properties are accessed. The security sensitive properties are Endpoint, ClientCredentials and the ChannelFactory. Default mode example is as follows.
class Program { static void Main(string[] args) { ClientBase<IClientChannel>.CacheSetting = CacheSetting.Default; MyWcfServiceClient client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); //Access a security-sensitive member of the client UserNamePasswordClientCredential userName = client.ClientCredentials.UserName; //The following code will recreate the ChannelFactory object and disable the existing cache client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); } }
AlwaysOff
ChannelFactory is never cached and the instance is always newly created. Following is the source code sample.
class Program { static void Main(string[] args) { ClientBase<IClientChannel>.CacheSetting = CacheSetting.AlwaysOff; MyWcfServiceClient client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); //The following code will recreate the ChannelFactory object client = new MyWcfServiceClient(new WSHttpBinding(), new EndpointAddress("<MyEndPointAddress>")); client.GetAGreetingMessage(); } }
Though these options provide a good flexibility it is always on the developer to choose the right type of cache settings. I hope this article provided a nice insight about the ChannelFactory caching. Happy reading!
How to cache ChannelFactory?
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.
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
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
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
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
https://docs.microsoft.com/en-us/archive/blogs/wenlong/
标签:ChannelFactory,recovery,service,factory,channels,client,new,channel 来源: https://www.cnblogs.com/panpanwelcome/p/15812880.html
((IClientChannel)local).Faulted += ChannelFaulted
, thereturn invokeHandler(arg);
line won't throw an exception by default. I guess that's whythrow new ApplicationException("Exc_ChannelFailure");
was added. Has anyone found a way to get the original exception? – Nelson Rothermel Jan 24 '14 at 21:47