WCF - 如何编写仅发布给特定客户的发布者\订阅者服务?

时间:2012-06-21 16:14:20

标签: wcf publish-subscribe

我正在以Publish-Subscribe模式编写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只会返回我发送消息的那些客户端吗?

2 个答案:

答案 0 :(得分:3)

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

  

我怎么知道'这个'客户是什么?

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'方法向我发送凭证?

是的,这是一种常见的做法。对您的服务进行Subscribe()操作,这将是您的客户在加入您的服务时会拨打的第一种方法:

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

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

答案 1 :(得分:0)

我想出的答案是实现'Custom UsernamePasswordValidator', 所以每个服务实例现在知道客户端连接到它(这样我就不必在Subscribe中传递任何内容)。

当'publish'事件到来时 - 我会检查它的用户(同一个用户可能从多台机器连接)。

然后我会向目标用户提出'PriceChangeEvent',并为所有客户端实例引发'PriceChangeHandler'事件。

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

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