【问题标题】:WCF - how to write a publisher\subscriber service that publishes only to specific clients?WCF - 如何编写仅发布到特定客户端的发布者\订阅者服务?
【发布时间】:2012-06-21 16:14:20
【问题描述】:

我正在以发布-订阅模式编写 WCF 服务。

当有人发布事件时,我不想立即将其发送给所有客户。

我希望能够为每个客户检查是否需要通知该客户有关该发布的信息。

基本上,这将通过访问数据库并检查该客户端是否使用这些参数订阅了该特定事件来完成(不能提前完成,只需要针对数据库进行检查)。

目前我正在使用this List-Based Publish-Subscriber 示例,但它的工作方式是 - 当事件发布时 - 客户端会话被单独触发以发送消息。

所以现在,我正在改变这个:

 public void PriceChangeHandler(object sender, PriceChangeEventArgs e)
 {
     _callback.PriceChange(e.Item, e.Price, e.Change);
 }

到这里:

 public void PriceChangeHandler(object sender, PriceChangeEventArgs e)
 {
     // Perform some database checks using BL if this client needs to be notified about this event

     // Only if answer is YES - call the callback function on that client
     _callback.PriceChange(e.Item, e.Price, e.Change);

     // Also - send the client an EMAIL + SMS
     _emailServer.SendEmail(e.Item);
     _smsServer.SendSMS(e.Item);
 }

两个问题:

这是正确的方法吗?我怎么知道“这个”客户是什么?客户是否应该以我将存储的“订阅”方法向我发送凭据? 或者我应该实现一个自定义的“UsernameValidator”来存储 Principal 吗?

难道我不应该有一个所有客户的静态列表,我将发送给我的 BL,而 BL 只会返回我必须向我发送消息的那些客户吗?

【问题讨论】:

    标签: wcf publish-subscribe


    【解决方案1】:

    我认为首先回答这个问题会让生活变得更轻松:

    我怎么知道“这个”客户端是什么?

    OperationContext.Current.GetCallbackChannel<T>

    对于服务接收到的每个调用,都会有一个客户端通道,通过该通道进行调用,这将为您提供仅进行该调用的客户端的回调通道,这是您能够区分您的客户。

    关于整个场景的方法,我将首先按照您自己的建议将subscribers 列表存储在静态dictionary 中,但还要保留每个客户端回调实例及其用户名:

    private static Dictionary<IPriceChangeCallback, string> subscribers = new Dictionary<IPriceChangeCallback, string>();

    IPriceChangeCallback 是您的回调合约,字符串可以是唯一的用户名或任何标识符。因此,您现在具有区分客户的基本能力,例如,假设您要将最后收到的消息发布给除发送者之外的每个客户,您会:

            lock (subscribers)
            {
                foreach (var _subscriber in subscribers)
                {
                    if (OperationContext.Current.GetCallbackChannel<IPriceChangeNotification>() == _subscriber.Key)
                    {
                          //if the person who sent the last message is the current subscriber, there is no need to
                          //publish the message to him, so skip this iteration
                            continue;
                    }
                    else
                    {
                           //GetCurrrentClient is a handy method, you can optionally include this  
                           //in your callbacks just to let your clients know who exactly sent the publication
                            _subscriber.Key.PriceChangeCallback(e.Item, e.Price, e.Change, GetCurrentClient());
                    }
                 }
             }
    

    或根据用户名区分您的客户,理想情况下,您的数据库中也应包含该用户名:

                lock (subscribers)
                {
                    foreach (var _subscriber in subscribers)
                    {
                        if(_subscriber.Value == "Jimmy86"))
                        {
                            //Identify a specific client by their username and don't send the notification to him
                            //here we send the notification to everyone but jimmy86
                            continue;
                        }
                        else
                        {
                            _subscriber.Key.PriceChangeCallback(e.Item, e.Price, e.Change, GetCurrentClient());
                        }
                    }
                }
    

    再一次,当您想知道谁调用了服务操作,并告诉您的客户发送了该特定消息时,请使用我之前提到的 GetCurrentClient() 方法:

        private string GetCurrentClient()
        {
            return clients[OperationContext.Current.GetCallbackChannel<IPriceChangeNotification>()];
        } 
    

    这是正确的方法吗?

    我不确定上述方法是否可行,但我以前曾经做过,只要我想保留一个客户列表并在他们身上调用一些方法。

    客户是否应该以我将存储的“订阅”方法向我发送凭据?

    是的,这是一种常见的方法。对您的服务进行Subscribe() 操作,这将是您的客户想要加入您的服务时调用的第一个方法:

            [OperationContract(IsOneWay = true)]
            public void Subscribe(string username)
            {
                lock (subscribers)
                {
                    subscribers.Add(OperationContext.Current.GetCallbackChannel<IPriceChangeNotification>(), username);
                }
            }
    

    几个月前我正在开发 Pub/Sub Silverlight 服务,我发现 this articleaccompanying video 非常宝贵。

    【讨论】:

    • 在过去的几个小时里,我想到了另一种解决方案。我将实现“自定义用户名密码验证器”,因此每个服务实例现在都知道连接到它的客户端(这样我就不必在订阅中传递任何内容)。当“发布”事件到达时 - 我会检查它的目标用户(同一用户可能从多台机器连接)。然后,我将向目标用户提出“PriceChangeEvent”,并且将为所有客户端实例引发“PriceChangeHandler”事件。然后,在事件内部 -
    • 我会检查记录的主体是否是目标用户,如果是,我会在客户端机器上调用回调函数。这省去了保存已连接客户端列表的麻烦,而且我不需要在“订阅”方法中传递任何内容。您如何看待这种方法?
    • 这听起来像是一种更简洁的方式,但我以前没有这样做过。正如您所说,它使您不必管理已连接客户端的字典,也不必在客户端断开连接时清理您的字典。
    【解决方案2】:

    我想出的答案是实现“自定义用户名密码验证器”, 所以现在每个服务实例都知道连接到它的客户端是什么(这样我就不必在订阅中传递任何东西)。

    当“发布”事件到达时 - 我会检查它的目标用户(同一用户可能从多台机器连接)。

    然后,我会针对目标用户引发“PriceChangeEvent”,并且将为所有客户端实例引发“PriceChangeHandler”事件。

    然后,在事件内部——我会检查记录的主体是否是目标用户,如果是——我会在客户端机器上调用回调函数。

    这省去了保存已连接客户端列表的麻烦,而且我不需要在“订阅”方法中传递任何内容。

    【讨论】:

      猜你喜欢
      • 2012-02-04
      • 2019-07-01
      • 1970-01-01
      • 2012-11-30
      • 1970-01-01
      • 1970-01-01
      • 2012-07-23
      • 2016-12-14
      • 1970-01-01
      相关资源
      最近更新 更多