65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012 年 12 月 2 日

CPOL

4分钟阅读

viewsIcon

28371

downloadIcon

784

处理 WCF 服务在有状态应用程序中使用时出现的“已失效”状态和关闭问题。

介绍 

本文探讨了在有状态应用程序中使用 WCF 服务时,处理其“已失效”状态和关闭问题的可能解决方案。 

该解决方案采用动态代理方法来处理此问题,而不会破坏原始服务契约。这使得 ViewModel 可以依赖于服务接口,而无需知道它实际上正在使用 WCF。

背景

考虑一个 ViewModel,它将 WCF 服务注入其中。这存在一些陷阱:

  1. 当服务失效时,用户仍在继续使用该窗口,我们该如何处理?
  2. 如何确保服务被正确清理?
  3. 在处理服务接口时,如何实现以上两点?(即不知道它实际上是一个 WCF 服务。)

为了进一步阐述第三点,它希望利用服务公开的接口(无论是生成的还是来自共享 DLL),并且还希望方便单元测试(某些模拟框架无法与具体类一起工作)。

本文将通过动态代理探索一种解决方案。(如果您知道其他方法,请提供反馈。)

附注

一些 IoC 容器(如 Castle Windsor)为这一切提供了一个设施。 检查您的容器是否支持。如果它不支持处理 WCF 客户端通道,那么此解决方案应该对您有用。

又一个附注

Silverlight 要求用户实现带有异步方法的接口(有一些 解决方法,本文的解决方案也可以与它们一起使用)。本文将主要关注 WPF,Silverlight 版本包含在示例解决方案中。

如何使用  

如果您想直接使用,您需要做以下事情:

  1. 添加对 Castle.Core 的引用。
  2. 下载 WcfChannelProxy.zip 项目,并将两个文件复制到您的项目中。
  3. 在应用程序中创建视图时,创建所需的服务依赖项(直接通过 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);
}  

没有 CloseAbortDispose 方法。可以通过生成 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 容器在代理被释放时进行清理。

一些测试 

以下屏幕截图显示了示例项目中包含的测试。它们侧重于代理,以确保拦截器能够正确工作。

值得关注的点  

  1. 现在我们可以使用它们的接口注入我们的服务。
    • 这使得模拟更容易。
    • ViewModel 不知道任何通道维护。
  2. 我们有一个 IDisposable 接口实现。
    • 这使得清理资源变得更加容易;此外,如果您正在使用 IoC 容器,它们也可以为您利用这一点。

该解决方案仅 intended 用于有状态的 WPF/Silverlight 应用程序。

历史  

  • 2012 年 12 月 2 日 - 初稿。
  • 2012 年 11 月 27 日 - StackOverflow 问题,看看是否有已知的解决方法。
© . All rights reserved.