WCF - how to write a publisher\subscriber service that publishes only to specific clients? - wcf

I am writing a WCF service in a Publish-Subscribe pattern.
When someone publishes an event, I don't want to straight away send it to all the clients.
I want to be able to, for each client, check if that client needs to be notified about that publish.
Basically this will be done by accessing a database, and checking if that client has subscribed for that specific event with those parameters (cannot be done in advance, needs to be checked only against database).
Currently I am working using this List-Based Publish-Subscriber sample, but it works in such a way - that when an event is published - client session is triggered separatly to send the message.
So for now, I am changing this :
public void PriceChangeHandler(object sender, PriceChangeEventArgs e)
{
_callback.PriceChange(e.Item, e.Price, e.Change);
}
to this :
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);
}
Two Questions :
Is this the right way ? and how can I know what 'this' client is ? should the client send me credentials in the 'subscribe' method that I will store ?
Or should I implement a custom 'UsernameValidator' that will store the Principal ?
And shouldn't I have a static list of all the clients, that I will send to my BL, and the BL will return me only the ones I have to send the message to ?

I think answering this question first will make life a whole lot easier:
and how can I know what 'this' client is ?
OperationContext.Current.GetCallbackChannel<T>
For each call the service receives there will be a client channel through which the call is made, this will give you the callback channel of the client that made that call only, this is a simple way in which you're able to distinguish your clients.
Regarding the approach to your scenario as a whole, I would first store a list of subscribers in a static dictionary as you suggested yourself, but also keep each clients callback instance along with their username:
private static Dictionary<IPriceChangeCallback, string> subscribers = new Dictionary<IPriceChangeCallback, string>();
Where IPriceChangeCallback is your callback contract and the string could be a unique Username or any identifier. So you now have the basic ability to distinguish your clients, for example say you want to publish the last received message to every client except the one who sent it, you would:
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());
}
}
}
or distinguish your clients based on their usernames, which you should ideally have in your databse as well:
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());
}
}
}
And again, whenever you want to find out who called the service operation, and tell your clients who sent that particular message, use the GetCurrentClient() method I mentioned earlier:
private string GetCurrentClient()
{
return clients[OperationContext.Current.GetCallbackChannel<IPriceChangeNotification>()];
}
Is this the right way ?
I'm not sure how advisable the approach above is, but I've done it before whenever I've wanted to keep a list of clients and call some method on them.
should the client send me credentials in the 'subscribe' method that I will store ?
Yes this is one common way of doing it. Have a Subscribe() operation on your service, which will be the first method your clients will call when they want to join your service:
[OperationContract(IsOneWay = true)]
public void Subscribe(string username)
{
lock (subscribers)
{
subscribers.Add(OperationContext.Current.GetCallbackChannel<IPriceChangeNotification>(), username);
}
}
I was working on a Pub/Sub Silverlight service a couple months ago, and I found this article and it's accompanying video to be invaluable.

The answer I have come up with is to implement the 'Custom UsernamePasswordValidator',
and so each service instance now KNOWS what client is connected to it (this way I don't have to pass anything in Subscribe).
When a 'publish' event arrives - I would check which user it is intended to (the same user might connect from several machines).
I would then raise a 'PriceChangeEvent' with the targeted user, and the 'PriceChangeHandler' event would be raised for all client instances.
Then, inside the event - I would check if the logged principal is the targeted user, and if so - I would call the callback function on the client machine.
This saves me the trouble of saving a list of connected clients, and also I don't need to pass anything in the 'Subscribe' method.

Related

How to get the source address in Rebus?

How do I get the source address in a message received?
The context is that I'm designing a monitor for a service bus implemented with Rebus. I use the publish - subscribe pattern thus a message is always published on a topic. The monitor subscribes to all topics in order to supervise that a service has send something and so is alive and healthy. Though in a message handler the received message don't contain any source address or information identifying the service publishing. This means it's not possible to supervise which services are alive and healthy. Of course I can create an attribute "Service" identifying the service publishing in all messages. This implies that each service have to set the attribute before publishing a message, which I find a bit cumbersome. The source address is there and can identify the service publishing.
When you're in a Rebus message handler, you can access the IMessageContext - either by having it injected by your IoC container (which is the preferrent way, because of the improved testability), or by accessing the static MessageContext.Current property.
The message context gives you access to a couple of things, where the headers of the incoming transport message can be used to get the return address of the message (which, by default, is set to the sender's input queue).
Something like this should do the trick:
public class SomeHandler : IHandleMessages<SomeMessage>
{
readonly IMessageContext _messageContext;
public class SomeHandler(IMessageContext messageContext)
{
_messageContext = messageContext;
}
public async Task Handle(SomeMessage message)
{
var headers = _messageContext.TransportMessage.Headers;
var returnAddress = headers[Headers.ReturnAddress];
// .. have fun with return address here
}
}

WCF sessions are not specific to specific user

WCF function
public void SetSession(string name)
{
HttpContext.Current.Session["abc"]=name;
}
public string GetSession(string name)
{
return HttpContext.Current.Session["abc"].ToString();
}
Proxy
using (ServiceReference1.BlackjackClient proxy = new ServiceReference1.BlackjackClient())
{
proxy.SetSession("Hello");
}
my problem is when multiple clients are accessing the service then last set session is accessed by the each client. Session are not browser request based and not recognizing the client. Which client has sent which request. What should i do to make them specific to each client. means each client must have his own session.
Please help
The service can not know which client is calling the service. Regular asp.net use of Session uses a cookie, that identifies each request and makes some internal voodoo to map the request to the correct session.
In your case, you would have to either use login from the clients to ensure that the service could identify requests, but this would not in it self solve the problem.
Since you have access to the service implementation the simplest solution would probably be to store a session identifier (a Guid) in the client, and then send this along each request to the web service thus altering
public void SetSession(string name)
{
HttpContext.Current.Session["abc"]=name;
}
public string GetSession(string name)
{
return HttpContext.Current.Session["abc"].ToString();
}
to something like
public void SetSession(string name, Guid sessionId)
{
HttpContext.Current.Session[sessionId + "_abc"]=name;
}
public string GetSession(string name, Guid sessionId)
{
return HttpContext.Current.Session[sessionId + "_abc"].ToString();
}
Modifying the method signature like this is rather ugly though, but the idea would be, that the client aids the server in identifying the caller and thus the session.
It would be cleaner to use the transport protocol to identify the caller, so if you are creating a HTTP service, you could use some http header (perhaps authorization) to contain the session identifier. If you are using SOAP the message header could contain identical information.
The session identifier could also be created at the service by a new method named something like Guid CreateSession(). But a Guid could as well be created in the client.
But again: You will need to store some unique session id or user credentials in the client and communicate them to the server in each request.

How can I callback the client and expose a new Channel with instance context

I'm making a WCF service with netTcpBinding which has a main lobby with multiple chatrooms which the clients can enter. The Lobby class implements ILobby as the service contract.
When a client wishes to enter a room I want to callback the client exposing a new Channel containing the InstanceContext for the room he just entered but after much searching I am doubting that this is possible.
For example on the Service side I might have
class Lobby : ILobby
{
Dictionary<string, Chatroom> rooms;
public void JoinRoom(string roomname)
{
if (rooms[roomname].TryEnter()) {}
}
}
class ChatRoom : IChatRoom
{
public bool TryEnter(string username)
{
ILobbyCallback callback =
OperationContext.Current.GetCallbackChannel<ILobbyCallback>();
// How do I do this next bit?
callback.JoinedRoom(pass some instance context here);
return true;
}
}
On the client side callback method I want
public void JoinedRoom(InstanceContext for the room on the service side)
{
// Create a new WCF proxy using above InstanceContext
// Create a WPF UI for the new room passing the proxy so it can communicate
// with the room class directly without going via the root service
}
Is this possible? What's the best practice for spawning new classes with their own contracts on the service side? Or do I just have to bundle everything into one massive MyService class and handle everything myself?
You cannot pass instance context as parameter to any operation contract. It doesn't make sense because that context has local scope. It is called "instance context" = it is context of current service instance. In duplex scenario both client and server has its own service:
Clients calls server's service through its proxy
Server calls client' service through received callback channel
Server's service instance context has meaning only on the server. It is not clear what you are trying to achieve (except very complex architecture).
If you want to share context on client you can try to pass around the instance context used for the very first proxy you created - I'm not sure if it will work but you can try it
If you want to share service instance context between multiple proxies you must develop your own IInstanceContextProvider and perhaps also your own IInstanceProvider (depending on what you want to achieve), wrap them in behavior and add them to the service. That will put whole complexity of session handling and correct instance releasing under your control (it obviously has its pros and cons).
But is it really needed? When I look at your code I see that one service and one proxy is enough. Also your JoinRoom operation doesn't need to use callback at all, it can be just request response method.

NHibernate + WCF + Windows Service and WcfOperationSessionContext class

I have a Windows Service Application
in which i create WCF services in it.
One of the services is data
services: add, delete,
read , updatte data via
WCF.
WCF use NHibernate for data manipulation
So my guestions are:
Any advice (best practice) for session management for Hibernate using with WCF?
Anybody knows anything about
WcfOperationSessionContext (hibernate 3.0) class?
how to use it with WCF?
Well to make it concrete :
Suppose that i have WCF Service called DataServices
class WCFDataService .....
{
void SaveMyEntity(MyEntity entity)
{
.....................?? // How to do? Best Way
// Should i take one session and use it all times
// Should i take session and dipsose when operation finished then get
//new session for new operations?
// If many clients call my WCF service function at the same time?
// what may go wrong?
// etc....
}
}
And I need a NHibernateServiceProvider class
class NHibernateServiceProvider ....
{
// How to get Session ?? Best way
ISession GetCurrentSession(){.... }
DisposeSession(){ ....}
}
Best Wishes
PS: I have read similiar entries here and other web pages. But can not see "concrete" answers.
The WcfOperationSessionContext, similar to ThreadStaticSessionContext and WebRequestSessionContext is an implementation for a session context. The session context is used to bind (associate) a ISession instance to a particular context.
The session in the current context can be retrieved by calling ISessionFactory.GetCurrentSession().
You can find more information about session context here.
The WcfOperationSessionContext represents a context that spans for the entire duration of a WCF operation. You still need to handle the binding of the session in the begining of the operation and the unbinding/commiting/disposal of the session at the end of the operation.
To get access to the begin/end actions in the wcf pipeline you need to implement a IDispatchMessageInspector. You can see a sample here.
Also regarding WCF integration: if you use ThreadStatic session context it will appear to work on development, but you will hit the wall in production when various components (ex: authorization, authentication ) from the wcf pipeline are executed on different threads.
As for best practices you almost nailed it: Use WcfOperationSessionContext to store the current session and the IDispatchMessageInspector to begin/complete your unit of work.
EDIT - to address the details you added:
If you configured WcfOperationSessionContext and do the binding/unbinding as i explained above, all you have to do to is inject the ISessionFactory into your service and just use factory.GetCurrentSession(). I'll post a sample prj if time permits.
Here is the sample project
The model we use for managing NHibernate sessions with WCF is as follows:
1) We have our own ServiceHost class that inherits from System.ServiceModel.ServiceHost which also implements ICallContextInitializer. We add the service host instance to each of the operations in our service as follows:
protected override void InitializeRuntime()
{
base.InitializeRuntime();
foreach (ChannelDispatcher cd in this.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
foreach (DispatchOperation op in ed.DispatchRuntime.Operations)
{
op.CallContextInitializers.Add(this);
}
}
}
}
public void AfterInvoke(object correlationState)
{
// We don't do anything after the invoke
}
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
OperationContext.Current.Extensions.Add(new SessionOperationContext());
return null;
}
The BeforeInvoke simply makes sure that the OperationContext for each WCF call has it's own session. We have found problems with IDispatchMessageInspector where the session is not available during response serialisation - a problem if you use lazy loading.
2) Our SessionOperationContext will then be called to attach itself and we use the OperationCompleted event to remove ourselves. This way we can be sure the session will be available for response serialisation.
public class SessionOperationContext : IExtension<OperationContext>
{
public ISession Session { get; private set; }
public static SessionOperationContext Current
{
get
{
OperationContext oc = OperationContext.Current;
if (oc == null) throw new InvalidOperationException("Must be in an operation context.");
return oc.Extensions.Find<SessionOperationContext>();
}
}
public void Attach(OperationContext owner)
{
// Create the session and do anything else you required
this.Session = ... // Whatever instantiation method you use
// Hook into the OperationCompleted event which will be raised
// after the operation has completed and the response serialised.
owner.OperationCompleted += new EventHandler(OperationCompleted);
}
void OperationCompleted(object sender, EventArgs e)
{
// Tell WCF this extension is done
((OperationContext)sender).Extensions.Remove(this);
}
public void Detach(OperationContext owner)
{
// Close our session, do any cleanup, even auto commit
// transactions if required.
this.Session.Dispose();
this.Session = null;
}
}
We've used the above pattern successfully in high-load applications and it seems to work well.
In summary this is similar to what the new WcfOperationSessionContext does (it wasn't around when we figured out the pattern above;-)) but also overcomes issues surrounding lazy loading.
Regarding the additional questions asked: If you use the model outlined above you would simply do the following:
void SaveMyEntity(MyEntity entity)
{
SessionOperationContext.Current.Session.Save(entity);
}
You are guaranteed that the session is always there and that it will be disposed once the WCF operation is completed. You can use transactions if required in the normal way.
Here is a post describing, in detail, all the steps for registering and using the WcfOperationSessionContext. It also includes instructions for using it with the agatha-rrsl project.
Ok, after few days of reading internet posts etc. all approaches shown in the internets seems to be wrong. When we are using UnitOfWork pattern with NH 3^ with nhibernate transaction this all aprochaes are producing exceptions. To test it and proof that we need to create test enviroment with MSMQ transaction queue, special interface with OneWay operation contract with transaction required set on it. This approach should works like this:
1. We put transactionally message in queue.
2. Service is getting transactionally messege from queue.
3. Everything works queue is empty.
In some cases not so obious with internet approaches this does not work properly. So here are expamples which we tested that are wrong and why:
Fabio Maulo approach: Use ICallContextInitializer - open NH session/transaction on BeforeCall, after that WCF is executing service method, on AfterCall in context initializer we call session.Flush + transaction.commit. Automaticly session will be saved when transaction scope will commit operation. In situation when on calling transaction.Complete exception will be thrown WCF service will shutdown! Question can be ok, so take transaction.Complete in try/catch clausule - great! - NO wrong! Then transaction scope will commit transaction and message will be taken from queue but data will not be saved !
Another approach is to use IDispatchMessageInspector - yesterday I thought this is best approach. Here we need to open session/transaction in method AfterReceiveRequest, after WCF invoke service operation on message dispatcher inspector BeforeSendReply is called. In this method we have info about [reply] which in OneWay operation is null, but filled with fault information if it occured on invoking service method. Great I thought - this is this ! but NOT! Problem is that at this point in WCF processing pipe we have no transaction ! So if transaction.Complete throw error or session.Flush will throw it we will have not data saved in database and message will not come back to queue what is wrong.
What is the solution?
IOperationInvoker and only this!
You need to implement this interface as a decorator pattern on default invoker. In method Invoke before call we are openning session/transaction open then we call invoke default invoker and after that call transaction.complete in finally clausule we call session.flush. What types of problem this solves:
1. We have transaction scope on this level so when complete throws exception message will go back to queue and WCF will not shutdown.
2. When invocation will throw exception transaction.complete will not be called what will not change database state
I hope this will clear everyones missinformation.
In some free time I will try to write some example.

Wcf Duplex: Retrieve Client Connection?

Hi
Maybe this look like ridiculous but this is problem at least for me
I wrote duplex WCF service, in my service I need to get active client service and save them, and when with occurred special event I call specific client and send some values for it. So I define dictionary and save client in that. (With this method client calls)
public static Dictionary<int, IServiceCallbak> ActiveClients;
public void IConnect(int SenderId)
{
if (ActiveClients == null)
ActiveClients = new Dictionary<int, IServiceCallbak>();
Client = OperationContext.Current.GetCallbackChannel<IServiceCallbak>();
if (ActiveClients.Count(ac => ac.Key == SenderId) > 0)
ActiveClients.Remove(SenderId);
ActiveClients.Add(SenderId, Client);
}
So then when I need find client from that dictionary and call specific method : Client.DoSomthing().
Also when Client wants to exit, it calls IDisconnect method which will remove client from dictionary.
so I manage Active-client in service!!!
But there is problem in client for managing themselves
After a period time which define in app.config service connection will be closed and you should renew that and then open the service.
So in this case:
1)Is there any solution for recreate and open the service object automatically in client.
2)Or when in server side when service want call clients, check state of client-service-object from that dictionary, and reopen connection from server-side (Ridiculous-solution)
Edit
I think better solution is to handle Suggestion 1, I don't know how!!!.
So for now the question is: Is way exist to do Suggestion 1 Or not? Previously I describe Suggestion 1 in Comment:
"And automatically refer to event for this case(like closing or aborting), but I don't find anything for doing this in Service-Client"
In order to prevent the server side from closing the connection you could set up a Heartbeat() method in the contract that the client could call periodically. This is not ideal however, for one thing because the underlying socket could drop and this does nothing to remedy that.
As far as your suggestion 1) if on the client side you are inheriting from ClientBase you are somewhat stuck in that no indication of a problem may be given until you call a method to route to the service. You would have to wrap the call in a try / catch and then employ some reconnect logic:
public class MyClass : ClientBase<IContract>, IContract
{
public void ServiceMethod(String data) {
try {
base.Channel.ServiceMethod(data);
}
catch (CommunicationException ce) {
// Perform some reconnect logic here
base.Channel.ServiceMethod(data);
}
}
}
Your comment for suggestion 2) is correct, if there are any firewalls between the server side and client they would most likely not allow the connection
Edit:
To expand on my suggestion for 1), you would need to create a new connection when the call to the service fails with a CommunicationException. The simplest approach would be to create the service channel in the constructor and then create another when the call fails:
class ServiceClient {
Service1Client mService; // Class generated by VS tool
public ServiceClient()
: base() {
mService = new Service1Client();
}
#region IService1 Members
public string GetData(int value) {
CommunicationState state = mService.State;
if (state == CommunicationState.Closed || state == CommunicationState.Faulted) {
mService = new Service1Client();
}
try {
// Note: The state checked above may not be accurate,
// hence the try...catch
return mService.GetData(value);
}
catch (CommunicationException) {
mService = new Service1Client(); // Reconnect logic
return mService.GetData(value); // If it fails again we are out of luck...
}
}
#endregion
}
Edit2:
In WCF the session is handled by the client, if the session between the client and the service is lost, I know of no way to restore that session, either from the client or the service. You are, unfortunately, stuck here.
If the service wants to send via the callback with a broken session, simply put, it can't. Because of the way networks work the service may not know the actual client address. This and various other issues (like firewalls) mean that trying to reestablish a connection to the client from the service just isn't practical. The only approach for the service is to store what data it wanted to send to the client and send it when the service detects that the client has reconnected.
There is no guarantee that the client will know of the underlying socket dropping, until the client tries to send something over the socket, hence the try...catch. Recreating the channel from the client once it becomes aware of a broken connection is the only way I know of to handle the issue; which is what the code example does.
The heartbeat idea is a way to proactively deal with broken connection. Its efficiency depends on your requirements as to how fast you need to detect a broken connection and how many clients are present. The more clients connected the longer the heartbeat would have to be so that you don't put a load on the network at the service.
Edit3:
After some additional digging there may be a way to do what you want automatically. You can create what is known as a Reliable Session. Activating this involves creating additional entries in the config:
<netTcpBinding>
<binding>
<reliableSession ordered="Boolean"
inactivityTimeout="TimeSpan"
enabled="Boolean" />
</binding>
</netTcpBinding>
It is also available for Http related bindings, check out the link to the Microsoft documentation on the feature.