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
When to use Channel Factory
- [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
- 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
The 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
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!