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.
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.
Channel Factory is a global technology and data platform that maximizes both performance efficiency and contextual suitability, delivering contextual performance for advertisers on YouTube.
NET Core and . NET 5 support calling WCF services, but won't offer server-side support for hosting WCF. There are two recommended paths for modernizing WCF apps: gRPC is built on modern technologies and has emerged as the most popular choice across the developer community for RPC apps.
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>
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! :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With