Связь между службами WCF

Я встраиваю службу в существующее приложение, где каждая служба была построена с намерением, чтобы она использовалась только одним клиентом, а клиент и сервер были настроены с дуплексными каналами связи.

Ограничения

  1. У меня нет возможности изменить дизайн существующей инфраструктуры
  2. Я не могу использовать общий сеанс

Требование:

  1. Мне нужно иметь возможность общаться между клиентскими службами (например, если пользователь нажимает на элемент и хочет поделиться этим элементом, клиент по какой-либо причине может не справиться с функцией "совместного использования" и ему необходимо пройти это другому клиенту для обработки - это должно быть сделано через службу)
  2. Общение между клиентами должно осуществляться службой.

Чтобы сначала это работало, я настроил канал IPC (netNamedPipeBinding) непосредственно между двумя клиентами, но мне сказали, что все нужно отправлять через сервер. "Сервер" в этом сценарии, в большинстве случаев, работает на той же машине, что и клиент, поэтому я пришел к этому очень грубому доказательству попытки концепции (см. Ниже блок кода).

Проблема: когда метод вызывается для подписывающейся службы, контекст операции для текущей службы (в пределах которой вызывается метод) является нулевым - это оставляет службу без какого-либо способа обратного вызова клиенту

Я рассматривал возможность использования платформы публикации / подписки Джувала Лёви, которую он предоставляет в своей среде ServiceModelEx, но это казалось ненужным, когда все клиенты уже имеют настройку дуплексной связи между собой и их соответствующими службами... так что целью является просто добавить простую Уровень публикации / подписки, который концептуально находится "под" этими службами и может общаться с любыми из них, которые хотят подписаться.

Консультации и конструктивная критика приветствуются!


    public static class SubscriptionManager<TEventArgs> 
    where TEventArgs : class
{
    private static ConcurrentDictionary<int, Action<TEventArgs>> _subscribers =
       new ConcurrentDictionary<int, Action<TEventArgs>>();

    // sessionId is NOT the WCF SessionId
    public static void FireEvent( int sessionId, TEventArgs eventArgs ) 
    {
        var subscribers = _subscribers.Where( s => s.Key == sessionId );
        var actions = subscribers.Select( keyValuePair => keyValuePair.Value );

        foreach ( var action in actions )
            action.BeginInvoke( eventArgs, action.EndInvoke, null );
    }

    public static void Subscribe(int sessionId, Action<TEventArgs> eventHandler)
    {
        _subscribers.TryAdd(sessionId, eventHandler);
    }

    public static Action<TEventArgs> Unsubscribe(int sessionId)
    {
        Action<TEventArgs> eventHandler;
        _subscribers.TryRemove(sessionId, out eventHandler);
        return eventHandler;
    }
}

1 ответ

Решение

Итак, во-первых, кажется, что шаблон, который я реализовывал, мог быть классифицирован как шаблон посредника.

Поэтому я решил эту проблему, передав текущий контекст экземпляра вызывающей службы FireEvent, который в моем случае должен иметь тот же контекст, что и служба подписки.

Рассматриваемый здесь случай - это отключенные клиентские приложения, которые работают в контексте одного и того же пользователя и от того же клиентского компьютера, но (согласно требованиям) они должны обмениваться данными через уровень обслуживания.

Я начал идти по пути использования контекста синхронизации WCF и Juval Lowy's ThreadPoolBehavior класс из его ServiceModelEx библиотека, но я привязан к реализации, где Ninject WCF Extensions мешали этому.

Так что это решение должно быть адаптировано на основе вашей собственной реализации, но чтобы дать вам представление о том, как я это сделал, вот суть моего обновленного FireEvent метод:

public static void FireEvent( int sessionId, TData eventData, InstanceContext context )
{
    var subscribers = Subscribers.Where( s => s.Key == sessionId );

    var eventArguments = subscribers.Select( kvp => kvp.Value );

    foreach ( var argument in eventArguments )
    {
        // set data associated with the event
        argument.SetData( eventData );

        NinjectInstanceProvider instanceProvider = null;
        Object instance = null;

        try
        {
            // get a "synchronized instance" of the service of type defined by event args
            instanceProvider = new NinjectInstanceProvider( argument.ServiceType );
            instance = instanceProvider.GetInstance( context );

            // get the method from our "synchronized instance"
            // filter by argument types so we don't run into any issues with ambiguity
            var argumentTypes = new[] { typeof ( TEventArgs ) };
            var method = instance.GetType().GetMethod( argument.Callback, argumentTypes );

            // our method arguments
            var arguments = new object[] { argument };

            // invoke the method on our "synchronized instance"
            method.Invoke( instance, arguments );

            // release the instance
            instanceProvider.ReleaseInstance( context, instance );
        }
        catch
        {
            // log
        }
        finally
        {
            if( provider != null )
            {
                if( instance != null ) { instanceProvider.ReleaseInstance( context, instance ); }
            }
        }
    }
}
Другие вопросы по тегам