Связь между службами WCF
Я встраиваю службу в существующее приложение, где каждая служба была построена с намерением, чтобы она использовалась только одним клиентом, а клиент и сервер были настроены с дуплексными каналами связи.
Ограничения
- У меня нет возможности изменить дизайн существующей инфраструктуры
- Я не могу использовать общий сеанс
Требование:
- Мне нужно иметь возможность общаться между клиентскими службами (например, если пользователь нажимает на элемент и хочет поделиться этим элементом, клиент по какой-либо причине может не справиться с функцией "совместного использования" и ему необходимо пройти это другому клиенту для обработки - это должно быть сделано через службу)
- Общение между клиентами должно осуществляться службой.
Чтобы сначала это работало, я настроил канал 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 ); }
}
}
}
}