我正在尝试将现有的.NET Remoting应用程序转换为WCF。服务器和客户端共享公共接口,所有对象都是服务器激活的对象。
在WCF世界中,这类似于创建每个呼叫服务并使用ChannelFactory<T>
创建代理。我正在为如何为ASP.NET客户端正确创建ChannelFactory<T>
而苦苦挣扎。
出于性能原因,我想缓存ChannelFactory<T>
个对象,并在每次调用服务时创建通道。在.NET远程处理时,曾经有RemotingConfiguration.GetRegisteredWellknownClientTypes()
方法来获取我可以缓存的客户端对象的集合。看来,在WCF世界中没有这样的东西,虽然我能够从配置文件中获得一组端点。
现在我认为这将起作用。我可以创建这样的东西:
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;
}
}
我认为上面的代码可行,但我有点担心多个线程试图添加新的ChannelFactory<T>
对象,如果它不在查找中。由于我使用的是.NET 4.0,我考虑使用ConcurrentDictionary
并使用GetOrAdd()
方法或首先使用TryGetValue()
方法检查ChannelFactory<T>
是否存在且不存在,然后使用GetOrAdd()
方法。不确定ConcurrentDictionary.TryGetValue()
和ConcurrentDictionary.GetOrAdd()
方法的性能。
另一个小问题是我是否需要在ASP.NET应用程序结束后在通道工厂对象上调用ChannelFactory.Close()
方法,或者我可以让.NET框架自己配置通道工厂对象。使用((IChannel)proxy).Close()
方法调用服务方法后,代理通道将始终关闭。
答案 0 :(得分:64)
这是我用来处理渠道工厂的辅助类:
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();
}
}
}
}
然后我定义了一个服务调用者:
public interface IServiceInvoker
{
R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}
和实施:
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;
}
}
现在每次需要调用WCF服务时都可以使用:
WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
proxy => proxy.SomeMethod()
);
这假设您已在配置文件中为IMyServiceContract
服务合同定义了客户端端点:
<client>
<endpoint
name="myservice"
address="http://example.com/"
binding="basicHttpBinding"
contract="IMyServiceContract" />
</client>
答案 1 :(得分:13)
是的,如果你想创建这样的东西 - 一个静态类来容纳所有那些ChannelFactory<T>
个实例 - 你必须确保这个类是100%线程安全的,并且在并发访问时不会绊倒。我还没有使用.NET 4的功能,所以我不能对这些功能发表评论 - 但我肯定会建议尽可能安全。
至于你的第二个(次要)问题:ChannelFactory本身是一个静态类 - 所以你不能真正调用它上面的.Close()
方法。如果您打算询问是否在实际的.Close()
上调用IChannel
方法,那么请再次:是的,尽量做一个好公民并尽可能关闭这些渠道。如果你错过了一个,.NET会照顾它 - 但不要只是把你未使用的频道丢在地板上继续 - 自己清理干净! :-)
答案 2 :(得分:2)
我不喜欢呼叫结构:
WCFServiceInvoker invoker = new WCFServiceInvoker();
var result = invoker.InvokeService<IClaimsService, ICollection<string>>(proxy => proxy.GetStringClaims());
此外,您不能两次使用同一频道。
我已经创建了这个解决方案:
using(var i = Connection<IClaimsService>.Instance)
{
var result = i.Channel.GetStringClaims();
}
现在,您可以重复使用相同的通道,直到using语句调用dispose。
GetChannel方法基本上是一个ChannelFactory.CreateChannel(),带有一些我正在使用的额外配置。
您可以像其他解决方案那样为ChannelFactory构建一些缓存。
Connnection类的代码:
public static class Connection<T>
{
public static ChannelHolder Instance
{
get
{
return new ChannelHolder();
}
}
public class ChannelHolder : IDisposable
{
public T Channel { get; set; }
public ChannelHolder()
{
this.Channel = GetChannel();
}
public void Dispose()
{
IChannel connection = null;
try
{
connection = (IChannel)Channel;
connection.Close();
}
catch (Exception)
{
if (connection != null)
{
connection.Abort();
}
}
}
}
}
答案 3 :(得分:0)
@NelsonRothermel,是的我走了之路 不在ChannelFactoryManager ChannelFaulted事件处理程序中使用try catch。 所以ChannelFaulted将成为
private void ChannelFaulted(object sender, EventArgs e)
{
IClientChannel channel = (IClientChannel)sender;
channel.Abort();
}
似乎允许原始异常冒泡。 也选择不使用channel.close,因为它似乎抛出异常 因为频道已经处于故障状态。 FactoryFaulted事件处理程序可能有类似的问题。 顺便说一句@Darin,代码很好......