如何从WCF服务向WPF客户端传输ObservableCollection(实时)?

时间:2014-06-30 19:30:32

标签: c# wpf wcf

我开发了下载管理器应用程序。该解决方案由两部分组成: 1)代表下载管理器本身的WCF服务。它可以同时执行多次下载。每次下载都在独立的TPL任务中执行。有关每次下载的信息由Downloader类表示。并且服务中有一个ObservableCollection实例用于存储所有现有下载(Downloader实例)。 2)WPF客户端,其(实时地)显示从WCF服务接收的下载状态信息,例如下载的字节数。 请参阅下面的合同接口的condenced版本和Downloader类的精简版本:

// "IDownloadManagerService.cs" file where service contracts are defined.

[ServiceContract]
public interface IDownloadManagerService
{
    /// <summary>
    /// Returns a collection of executing or waiting downloads.
    /// </summary>
    /// <returns>a collection of executing or waiting downloads</returns>
    [OperationContract]
    ObservableCollection<Downloader> GetDownloads();
}

/// <summary>
/// Represents information about status of specified internet-resource downloading.
/// </summary>
[DataContract]
public class Downloader : INotifyPropertyChanged
{
    /// <summary>
    /// Downloaded bytes quantity.
    /// </summary>
    private Int64 _downloadedBytesQuantity = 0;

    /// <summary>
    /// Gets or sets downloaded bytes quantity.
    /// </summary>
    [DataMember]
    public Int64 DownloadedBytesQuantity
    {
        get { return this._downloadedBytesQuantity; }
        set
        {
            if (value != this._downloadedBytesQuantity)
            {
                this._downloadedBytesQuantity = value;
                NotifyPropertyChanged();
            }
        }
    }
}

每次下载更改其DownloadedBytesQuantity属性时,我都希望将ObservableCollection从服务传输到客户端,以便传输必须实时执行。下面我显示了一个实现IDownloadManagerService接口的DownloadManagerService类的condenced版本:

/// <summary>
/// Implements IDownloadManagerService interface.
/// </summary>
public class DownloadManagerService : IDownloadManagerService
{
    #region Fields
    /// <summary>
    /// Collection of executing or waiting downloads.
    /// </summary>
    private ObservableCollection<Downloader> _downloads = new ObservableCollection<Downloader>();
    #endregion

    #region Methods
    /// <summary>
    /// Returns the collection of existing downloads to client.
    /// </summary>
    /// <returns>Collection of existing downloads</returns>
    public ObservableCollection<Downloader> GetDownloads()
    {
         return this._downloads;
    }
    #endregion
}

因此,每当此集合中包含的任何下载更改其DownloadedBytesQuantity属性的值时,我都有兴趣将_downloads ObservableCollection传输到客户端。客户端必须显示(在DataGrid中)收到的更改。我该怎么做?这可行吗?

1 个答案:

答案 0 :(得分:1)

很难知道完全您正在寻找什么,但我会猜测。

您想要服务的方式自动告知客户端当前任何下载是否已更改其DownloadBytesQuantity属性,因此客户端可以相应地更新其视图以告知用户。

是?大!没有?忽略以下答案。

免责声明 - 这尚未经过测试,仅仅是我的头脑。如果您编写代码并且IDE抱怨一堆东西,请不要太惊讶。希望它足以让你至少走上正确的轨道。

呼叫客户端的服务方式是使用回调。 WCF服务回调(与很多其他WCF的东西一样)可能变得非常复杂,因此你在网上找到的很多信息都可能是压倒性的。

基本上,您需要WCF 客户端来实现专用的回调接口。

public interface IDownloadManagerServiceCallback
{
    [OperationContract]
    void OnDownloadBytesQuantityChanged(Downloader downloader);
}

这最终会为您的服务提供一个端点,以通知客户端下次更改了给定的Downloader

您的客户端将实现此方法,可能会以某种方式更新UI ...

public class DownloadManager : IDownloadManagerServiceCallback
{
    #region IDownloadManagerServiceCallback members...

    public void OnDownloadBytesQuantityChanged(Downloader downloader)
    {
        // handle download status change here...
    }

    #endregion
}

现在,根据您生成(或创建)WCF服务客户端的方式,您应该拥有一个接受InstanceContext实例的构造函数。

class DownloadManagerServiceClient : DuplexClientBase, IDownloadManagerService
{
    public DownloadManagerServiceClient(InstanceContext instanceContext)
        : base(instanceContext)
    {
    }

    //...
}

这是您在创建DownloadManagerServiceClient代理实例时应该使用的内容。

从上方扩展我的DownloadManager ...

public class DownloadManager : IDownloadManagerServiceCallback
{
    #region IDownloadManagerServiceCallback members...

    public void OnDownloadBytesQuantityChanged(Downloader downloader)
    {
        // handle download status change here...
    }

    #endregion

    private IDownloadManagerService _proxy;

    public IDownloadManagerService Proxy
    {
        get
        {
            if (_proxy == null)
            {
                // get the instance context from ourselves as we are the callback client
                var instanceContext = new InstanceContext(this);

                _proxy = new DownloadManagerServiceClient(instanceContext);
            }

            return _proxy;
        }
    }
}

尚未完成。服务器端的一些细微变化。

您的ServiceContract需要使用已创建的CallbackContract进行标记。

[ServiceContract(CallbackContract = typeof(IDownloadManagerServiceCallback)]
public interface IDownloadManagerService
{
    [OperationContract]
    ObservableCollection<Downloader> GetDownloads();
}

现在,在您的服务中,当客户端调用时,您可以保存它的回调上下文。

public class DownloadManagerService : IDownloadManagerService
{
     // all the other stuff....

     // NOTE: this assumes only one client calling in
     private IDownloadManagerServiceCallback _callback;

     public ObservableCollection<Downloader> GetDownloads()
     {
         // first store the clients callback context so we can notify it upon a change
         _callback = OperationContext.Current.GetCallbackChannel();

         return this.Downloads;
     }

     // this is just for demonstration purposes
     // somehow you'll know when a Downloader.DownloadedBytesQuantity has changed and that
     // is where this logic will go
     private void OnDownloadChanged(Downloader downloader)
     {
         _callback.OnDownloadBytesQuantityChanged(downloader);
     }
}

虽然看起来好像在这里发生了很多,但这实际上是一个非常不完整的例子。

您可能希望处理调用同一服务的多个不同客户端,如果通道出现故障,您可能需要输入一些try-catch块。