在状态保持的应用程序中使用WCF服务时,可能会遇到服务出现故障状态或需要关闭的情况。本文将探讨一种可能的解决方案,即使用动态代理方法来处理这些问题,而不会破坏原有的服务契约。这样,视图模型(ViewModel)就可以依赖于服务接口,而不需要知道它实际上是在使用WCF。
考虑一个视图模型,它将WCF服务注入到其中。这里有几个需要注意的问题:
为了扩展第三点,希望利用服务暴露的接口(无论是生成的文件还是共享的DLL),并且希望提供方便的单元测试(一些模拟框架可能无法与具体类一起工作)。本文将探讨使用动态代理的解决方案。
如果想直接使用这个解决方案,以下是需要做的:
_service1 = new WcfProxy.WcfClientFactory().CreateClient<IService1>();
MainViewModel vm = new MainViewModel(_service1);
MainWindow window = new MainWindow(vm);
要处理服务的释放,也很简单:
// 确保释放通道,这通常会被IoC容器处理
((IDisposable) _service1).Dispose();
本文的其余部分讨论了解决方案的工作原理,并提供了一些更多的上下文。
提供了一个示例VS解决方案,以演示解决方案的使用,并突出显示了约束的应用。解决方案由五个项目组成,如下所示:
两个客户端解决方案都使用动态代理(WcfProxy)来处理调用Web服务。它们都使用生成的服务引用,但解决方案也可以与共享DLL方法一起工作(其中单独的DLL包含服务接口和DTO)。
解决方案使用Castle的动态代理(WcfProxy)来提供服务接口的实现,并保护视图模型免受通道维护的影响。
服务的目标是让客户端注入服务的接口,这样视图模型就只需要知道它们的操作。以下是一个示例服务:
[ServiceContract]
public interface IService1 {
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
没有Close、Abort或Dispose方法。Close和Abort可以通过生成ServiceClient类(这不是一个接口,更难模拟和测试)或直接使用通道(然而,消费类将直接知道WCF)。
视图模型依赖于关键点;依赖接口将定义视图模型可用的操作。鉴于已经将Service1(上面定义的)注入到视图模型中,这意味着视图模型无法调节通道。
以下是一个依赖于Service1的视图模型,通过构造函数注入:
public class MainViewModel : INotifyPropertyChanged {
private readonly IService1 _service1;
private string _result;
public MainViewModel(IService1 service1) {
_service1 = service1;
GetDataCmd = new DelegateCommand<object>(GetData);
PropertyChanged += delegate { };
}
public string Result {
get { return _result; }
set {
_result = value;
PropertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
public ICommand GetDataCmd { get; private set; }
private void GetData(object o) {
Result = _service1.GetData(123);
}
public event PropertyChangedEventHandler PropertyChanged;
}
使用稍微修改过的ChannelFactoryManager类。这个类用于创建和维护通道工厂,并创建新的通道实例。
解决方案构建了一个动态代理,实现了服务接口,在这种情况下是IService1。代理实例有一个WCF通道用于服务,它维护该通道。当通道出现故障时,代理将清理故障通道,并从ChannelFactoryManager请求一个新的通道。
public class WcfClientFactory {
private static readonly ProxyGenerator Generator = new ProxyGenerator();
private readonly IChannelFactoryManager _manager = new ChannelFactoryManager();
public T CreateClient<T>() where T : class {
var service = _manager.CreateChannel<T>();
var proxy = Generator.CreateInterfaceProxyWithTargetInterface(
typeof(T),
new[] { typeof(IDisposable) }, service,
new WcfClientInterceptor<T>(service));
return (T)proxy;
}
}