WCF 在有状态应用程序(WPF/Silverlight)中的应用





5.00/5 (3投票s)
处理 WCF 服务在有状态应用程序中使用时出现的“已失效”状态和关闭问题。
介绍
本文探讨了在有状态应用程序中使用 WCF 服务时,处理其“已失效”状态和关闭问题的可能解决方案。
该解决方案采用动态代理方法来处理此问题,而不会破坏原始服务契约。这使得 ViewModel 可以依赖于服务接口,而无需知道它实际上正在使用 WCF。
背景
考虑一个 ViewModel,它将 WCF 服务注入其中。这存在一些陷阱:
- 当服务失效时,用户仍在继续使用该窗口,我们该如何处理?
- 如何确保服务被正确清理?
- 在处理服务接口时,如何实现以上两点?(即不知道它实际上是一个 WCF 服务。)
为了进一步阐述第三点,它希望利用服务公开的接口(无论是生成的还是来自共享 DLL),并且还希望方便单元测试(某些模拟框架无法与具体类一起工作)。
本文将通过动态代理探索一种解决方案。(如果您知道其他方法,请提供反馈。)
附注
一些 IoC 容器(如 Castle Windsor)为这一切提供了一个设施。 检查您的容器是否支持。如果它不支持处理 WCF 客户端通道,那么此解决方案应该对您有用。
又一个附注
Silverlight 要求用户实现带有异步方法的接口(有一些 解决方法,本文的解决方案也可以与它们一起使用)。本文将主要关注 WPF,Silverlight 版本包含在示例解决方案中。如何使用
如果您想直接使用,您需要做以下事情:
- 添加对 Castle.Core 的引用。
- 下载 WcfChannelProxy.zip 项目,并将两个文件复制到您的项目中。
- 在应用程序中创建视图时,创建所需的服务依赖项(直接通过 WcfClientFactory 或使用 IoC 容器),然后将实例注入到您的 ViewModel 中。
_service1 = new WcfProxy.WcfClientFactory().CreateClient<IService1>();
MainViewModel vm = new MainViewModel(_service1);
MainWindow window = new MainWindow(vm);
清理服务也很简单。
//make sure we dispose of our channel, this would normally be
//taken care of by an IoC container
((IDisposable) _service1).Dispose();
本文的其余部分将讨论该解决方案的工作原理并提供更多背景信息。
VS 示例解决方案概述
提供了一个 VS 示例解决方案来演示该解决方案的使用情况,并强调了应用限制。该解决方案由五个项目组成,如下所示:
- WCF host – 托管 WCF 服务的 Web 应用程序(这是一个全新的 WCF 服务项目,添加了客户端策略以允许 Silverlight 客户端访问)。
- WPF client – 一个简单的 WPF 客户端。
- Sl client – 简单的 Silverlight 客户端,使用版本 5。
- Sl host – 托管 Silverlight XAP 项目的 Web 应用程序。
- Test – 测试动态项目的拦截器。
两个客户端解决方案都使用动态代理(Wcf Proxy)来处理 Web 服务的调用。它们都使用了生成的服务引用,但该解决方案也适用于共享 DLL 方法(即单独的 DLL 包含服务接口和 DTO)。
解决方案概述
该解决方案使用 Castle 的动态代理(WcfProxy)来实现服务接口,并处理通道维护,从而屏蔽 ViewModel。
服务
目标是让客户端注入服务的接口,这样 ViewModel 就只需要知道其操作。下面是示例服务:
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
没有 Close
、Abort
或 Dispose
方法。可以通过生成 ServiceClient
类(它不是接口,更难模拟和测试)或直接使用 Channel 来使 Close 和 Abort 方法可供客户端使用(然而,使用此类将直接了解 WCF)。
ViewModel
ViewModel 所依赖的是关键;依赖接口将定义 ViewModel 可用的操作。鉴于 Service1
(如上定义)被注入到 ViewModel 中,这意味着 ViewModel 无法管理通道。
下面是 ViewModel,它通过构造函数注入依赖于 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
类。它用于创建和维护 ChannelFactories,并创建新的 Channel 实例。
该解决方案构建了一个实现服务接口(在本例中为 IService1
)的动态代理。代理实例有一个用于服务的 WCF 通道,它会维护该通道。当通道失效时,代理会清理失效的通道,并从 ChannelFactoryManager
请求一个新通道。
/// <summary>
/// Provides a way to create a WCF proxy which will regulate its own channel
/// </summary>
/// <remarks>
/// if you are using Windsor, you could easily create a facility around this</remarks>
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;
}
}
/// <summary>
/// Interceptor which manages the WCF channel, trying to ensure the channel is not faulted. this
/// is intended only to be used in state full applications. (for stateless then look at using the WCF Facility)
/// </summary>
/// <typeparam name="TService">the channel interface</typeparam>
public class WcfClientInterceptor<TService> : IInterceptor where TService : class
{
private readonly IChannelFactoryManager _manager;
private TService _instance;
private bool _instanceChanged;
public WcfClientInterceptor(TService service)
: this(service, new ChannelFactoryManager())
{
}
public WcfClientInterceptor(TService service, IChannelFactoryManager manager)
{
_manager = manager;
SetupInsance(service);
_instanceChanged = false;
}
public void Intercept(IInvocation invocation)
{
if (_instanceChanged)
{
//change the underpinning channel
//replaces a faulted channel with the new one
var cpt = (IChangeProxyTarget)invocation;
cpt.ChangeProxyTarget(_instance);
cpt.ChangeInvocationTarget(_instance);
_instanceChanged = false;
}
if (invocation.Method.Name == "Dispose")
{
//clean up the proxy
Dispose();
return;
}
invocation.Proceed();
}
/// <summary>
/// close or abort the channel (clean up)
/// </summary>
private void Dispose()
{
ICommunicationObject commObj = (ICommunicationObject)_instance;
commObj.Faulted -= OnFaulted;
try
{
if (commObj.State == CommunicationState.Faulted)
{
commObj.Abort();
}
else
{
commObj.Close();
}
}
catch
{
commObj.Abort();
}
_instance = null;
}
/// <summary>
/// set the current channel instance
/// </summary>
/// <remarks>
/// this attaches to the OnFaulted to try and clean up the proxy when it enters
/// a faulted state
/// </remarks>
private void SetupInsance(TService service)
{
_instance = service;
((ICommunicationObject)_instance).Faulted += OnFaulted;
_instanceChanged = true;
}
/// <summary>
/// create a new channel, using the channel factory manager
/// </summary>
private void CreateNewChannel()
{
var newInstance = _manager.CreateChannel<TService>();
SetupInsance(newInstance);
}
/// <summary>
/// Handle a faulted channel
/// </summary>
private void OnFaulted(object sender, EventArgs eventArgs)
{
Dispose();
CreateNewChannel();
}
}
代理确保 ViewModel 始终有一个有效的通道可以使用。
代理还扩展支持 IDisposable
,允许 IoC 容器在代理被释放时进行清理。
一些测试
以下屏幕截图显示了示例项目中包含的测试。它们侧重于代理,以确保拦截器能够正确工作。
值得关注的点
- 现在我们可以使用它们的接口注入我们的服务。
- 这使得模拟更容易。
- ViewModel 不知道任何通道维护。
- 我们有一个
IDisposable
接口实现。 - 这使得清理资源变得更加容易;此外,如果您正在使用 IoC 容器,它们也可以为您利用这一点。
该解决方案仅 intended 用于有状态的 WPF/Silverlight 应用程序。
历史
- 2012 年 12 月 2 日 - 初稿。
- 2012 年 11 月 27 日 - StackOverflow 问题,看看是否有已知的解决方法。