How can we detect when a WCF client has disconnected? - wcf

Is there any way of finding out when a WCF client has disconnected. Currently the only approach seems to be to wait until a call on the client from the service eventually times out.
I have tried subscribing to the OperationContext.Current.Channel.Faulted event but unfortunately it is never called; my understanding was that this event should be fired when the client disappears. On the other hand, when things close down gracefully OperationContext.Current.Channel.Closed is called.
In our application we only support a single client connection at a time, hence when somebody closes and re-starts the client app it would be nice if the server could be made aware of the the disconnection, tidy up gracefully and then accept another connection.
Yes, clients will disconnect gracefully most of the time, but this can't be guaranteed. Currently the only option seems to be to poll the client and wait for a CommunicationTimeout, which is hardly ideal.
Any suggestions greatly appreciated.

Theoretically, a service need not have knowledge of client's state. But it can insist on whom to serve for by dictating the authentication needs, concurrency limitation etc.
If you intention is to make sure only one client is served at a time, you can simply opt for Single Concurrency mode.
For example.
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single)]
public class CalculatorService : ICalculatorConcurrency
This will ensure only one client request is served at a time. Following link may help you as well.
http://msdn.microsoft.com/en-us/library/ms731193.aspx
EDIT
If you think an user's action of keeping the channel open does disturb the other user's work, it may not be the usual case.
Because each user's call is considered to be a different session. By default WCF calls are considered to be instantiated per call.
If you would like to persist data between user's calls, you may opt for perSession instancing mode.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorInstance
This would make sure that each user would have an instance of the service which would not inturrupt servicing the other user.
You can set the concurrency mode accordingly i.e Multiple or Reentrant if you wish. Even if the concurrency mode is single, when a response is sent back to the user the service would be ready to serve the next user. It won't wait for the client to close the connection. User's connection would be useful only to keep the session live.

You can use IChannelInitializer and hook up Channel Close and Channel faulted events to detect graceful or abrupt closing of the client. Refer to a very nice post on this by Carlos - http://blogs.msdn.com/b/carlosfigueira/archive/2012/02/14/wcf-extensibility-initializers-instance-context-channel-call-context.aspx

You could use Callback Operations to make a call to the client to see if its still connected.
Take a look at this article on MSDN magazine

if (HttpContext.Current.Response.IsClientConnected == false
{
...
}
it can help you

I've had success using a "disconnection detector" like this:
// Code based on https://blogs.msdn.microsoft.com/carlosfigueira/2012/02/13/wcf-extensibility-initializers-instance-context-channel-call-context/
public class WcfDisconnectionDetector : IEndpointBehavior, IChannelInitializer
{
public event Action Disconnected;
public int ConnectionCount { get; set; } = 0;
public WcfDisconnectionDetector() { }
public WcfDisconnectionDetector(Action onDisconnected) => Disconnected += onDisconnected;
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr)
=> cr.ChannelInitializers.Add(this);
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
=> ed.ChannelDispatcher.ChannelInitializers.Add(this);
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
void IChannelInitializer.Initialize(IClientChannel channel)
{
ConnectionCount++;
Trace.WriteLine($"Client {channel.SessionId} initialized");
channel.Closed += OnDisconnect;
channel.Faulted += OnDisconnect;
}
void OnDisconnect(object sender, EventArgs e)
{
ConnectionCount--;
Disconnected?.Invoke();
}
}
Install it before calling ServiceHost.Open:
var detector = new WcfDisconnectionDetector();
serviceHost.Description.Endpoints.Single().EndpointBehaviors.Add(
new WcfDisconnectionDetector(() => {/*disconnected*/}));

Related

Does Rebus support web app publishing message and subscribing to message

I am new to Rebus.
There are one questions i want to ask:
It is a good idea to make web app publish message and subscribe to message. And does Rebus support this features.
I test Server mode , however it does not work. It handles the message only one message(from pubsubsample.websubscriber1.input queue) when web app starts.
BTW,It works well on One-way client mode.(Send message only)
Here is my code segment for server modeļ¼š
public class CheckStatus : IHandleMessages<NewTradeRecorded>
{
readonly IBus bus;
public CheckStatus(IBus bus)
{
this.bus = bus;
}
public void Handle(NewTradeRecorded message)
{
}
}
Asp.net MVC
protected void Application_Start()
{
using (var adapter = new BuiltinContainerAdapter())
{
adapter.Register(() => new CheckStatus(adapter.Bus));
Configure.With(adapter)
.Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
.MessageOwnership(o => o.FromRebusConfigurationSection())
.CreateBus()
.Start();
adapter.Bus.Subscribe<NewTradeRecorded>();
}
}
web.config
<rebus inputQueue="pubsubsample.websubscriber1.input" errorQueue="pubsubsample.websubscriber1.error" workers="1" maxRetries="5">
<endpoints>
<add messages="Trading.Messages" endpoint="trading.input"/>
</endpoints>
To answer your first question, whether Rebus supports publishing and subscribing from the same process, the answer is yes - there's no technical reason why you cannot subscribe to messages and publish the same messages from the same process, and that includes your web application.
Whether you should is another thing :)
Web applications in .NET are kind of transient in nature, i.e. they're recycled when IIS decides that it's time to recycle, and then it's usually not the best idea to subscribe to messages, because your application might not be running when an event is published, so it's not around to handle it.
And then, when it wakes up because IIS dispatches a web request to it, you might have 1,000,000 events waiting to be handled by your application, which will take quite a while to chew through.
In some cases, I've heard of people wanting to use Rebus pub/sub in web applications to keep a cache updated in the web app - but then they had severe issues, coming from the fact that IIS supports overlapping two instances of the same web application - iow, suddenly, for a short while, two instances of the same web applications were running, thus allowing a web application about to shut down to snatch a few events that should have been handled by the new instance.
For these reasons, in general I would not recomment doing pub/sub in web applications.
So, why doesn't your pub/sub thing work? Well - first thing: Don't dispose the container adapter immediately after creating it! :)
Do this instead:
static readonly _stuffToDispose = new List<IDisposable>();
protected void Application_Start()
{
var adapter = new BuiltinContainerAdapter();
_stuffToDispose.Add(adapter);
adapter.Register(() => new CheckStatus(adapter.Bus));
Configure.With(adapter)
.Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
.MessageOwnership(o => o.FromRebusConfigurationSection())
.CreateBus()
.Start();
adapter.Bus.Subscribe<NewTradeRecorded>();
}
protected void Application_End()
{
_stuffToDispose.ForEach(d => d.Dispose());
}
This way, you bus will not stop handling messages immediately after your web app has started.

Close and Abort in custom service channel

My client is using one WCF service which is throwing an exception
(EXCEPTION: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state).
All subsequent calls throwing an same exception.
I read on internet that client need to close()/Abort() channel, this will solve the problem. is it completely right?
Also I am using customer serviceChannel factory provided by service developers. When I create channel it does not show the close and abort methods. So how do I get these close and abort methods when I create custom service channel instance on client side?
Assuming that you have a proxy instance that implements the IClientChannel interface, here is a way (hopefully the right way) to use it.
IClientChannel clientChannel = (IClientChannel)proxy;
bool success = false;
try
{
// do something with the proxy
clientChannel.Close();
success = true;
}
finally
{
if (!success)
{
clientChannel.Abort();
}
}
You may also want to check this. You can wrap your operations using a shared class or function.

Do I need to Close and/or Dispose callback channels acquired through OperationContext.Current.GetCallbackChannel?

I'm using OperationContext.Current.GetCallbackChannel to get a channel to the client that called a WCF service operation.
Do I need to worry about closing / disposing these callback channels or is this taken care of by the framework?
Well, I just tried it myself and it turns out that if you Close & Dispose the callback channel (after casting to IClientChannel) the entire Service channel becomes useless and when called throws a ProtocolException saying:
"This channel can no longer be used to send messages as the output session was auto-closed due to a server-initiated shutdown. Either disable auto-close by setting the DispatchRuntime.AutomaticInputSessionShutdown to false, or consider modifying the shutdown protocol with the remote server."
I assume that this is an unwelcome consequence or side effect of attempting to close & dispose the callback channel, meaning that this should not be done.
In my opinion you should.
The callback mechanism supplies nothing like a higher-level protocol for managing the
connection between the service and the callback endpoint. It is up to the developer to
come up with some application-level protocol or a consistent pattern for managing the
lifecycle of the connection. The service can only call back to the client if the client-side channel is still open, which is typically achieved by not closing the proxy. Keeping the proxy open will also prevent the callback object from being garbage-collected. If the service maintains a reference on a callback endpoint and the client-side proxy is closed or the client application itself is gone, when the service invokes the callback it will get an ObjectDisposedException from the service channel. It is therefore preferable for the client to inform the service when it no longer wishes to receive callbacks or when the client application is shutting down. To that end, you can add an explicit Disconnect() method to the service contract. Since every method call carries the callback reference with it, in the Disconnect() method the service can remove the callback reference from its internal store.
here is an exemple :
class MyService : IServiceContract
{
static List<IServiceContractCallback> m_Callbacks = new List<IServiceContractCallback>();
public void Connect()
{
IServiceContractCallbackcallback = OperationContext.Current.GetCallbackChannel<IServiceContractCallback>();
if(m_Callbacks.Contains(callback) == false)
{
m_Callbacks.Add(callback);
}
}
public void Disconnect()
{
IServiceContractCallback callback = OperationContext.Current.GetCallbackChannel<IServiceContractCallback>();
if(m_Callbacks.Contains(callback))
{
m_Callbacks.Remove(callback);
}
else
{
throw new InvalidOperationException("Cannot find callback");
}
}
In such a way a client can inform the service that the callback is no longer needed. Does it answer your question ?

WCF Publish / Subscribe: How to handle client side timeout so as not to miss information?

I have a simple WCF publish/subscribe up and running, based on this example. I am using netTcpBinding with reliableSession enabled. Everything works fine with the functionality (the subscribed clients receive the published data as expected), but at some point the connection times out if it has been idle for a while. I can set up the publisher to reconnect on timeout, but the subscribed clients will be lost. Is there a way to get them back? I would prefer not to just increase the timeouts, as that could cause other problems.
The solution I eventually came up with was to assign a unique identifier to every message that was published, and cache the published message in the service adapter (in the same place where I was storing the callbacks to the subscribed clients. Whenever I published the message, subscribers would receive the message and the corresponding unique id. Subscribers could then use the channel.Faulted event to reconnect and resubscribe to the service with a special method that takes the last received message id as a parameter.
Service code:
/// <summary>
/// Operation used by the subscriber to subscribe to events published.
/// </summary>
public void Resubscribe(int lastReceivedMessageId)
{
// Get callback contract
IPubSubCallback callback = OperationContext.Current.GetCallbackChannel<IPubSubCallback>();
ThreadPool.QueueUserWorkItem(delegate(object state)
{
adapter.Resubscribe(lastReceivedMessageId, callback);
});
}
Adapter code:
/// <summary>
/// Operation used by the subscriber to resubscribe to events published.
/// </summary>
public void Resubscribe(int lastReceivedMessageId, IPubSubCallback callback)
{
try
{
// Send the subscriber any missed messages
foreach (KeyValuePair<int, string> missedMessage in publishedMessages.Where(x => x.Key > lastReceivedMessageId))
{
callback.MessagePublished(missedMessage.Value, missedMessage.Key);
}
// Add the subscriber callback to the list of active subscribers
if (!callbacks.Contains(callback))
{
callbacks.Add(callback);
}
}
catch
{
// ignore subscription, callbacks failed again
}
}
The service can then work out what the client has missed, and resend those messages in the correct order.
This solution seems to be working well for me, but I have a feeling that there must be a better way to do this. Comments / additional answers are very welcome! :)

WCF nested Callback

The backgound: I am trying to forward the server-side ApplyChangeFailed event that is fired by a Sync Services for ADO 1.0 DBServerSyncProvider to the client. All the code examples for Sync Services conflict resolution do not use WCF, and when the client connects to the server database directly, this problem does not exist. My DBServerSyncProvider is wrapped by a head-less WCF service, however, and I cannot show the user a dialog with the offending data for review.
So, the obvious solution seemed to be to convert the HTTP WCF service that Sync Services generated to TCP, make it a duplex connection, and define a callback handler on the client that receives the SyncConflict object and sets the Action property of the event.
When I did that, I got a runtime error (before the callback was attempted):
System.InvalidOperationException: This operation would deadlock because the
reply cannot be received until the current Message completes processing. If
you want to allow out-of-order message processing, specify ConcurrencyMode of
Reentrant or Multiple on CallbackBehaviorAttribute.
So I did what the message suggested and decorated both the service and the callback behavior with the Multiple attribute. Then the runtime error went away, but the call results in a "deadlock" and never returns. What do I do to get around this? Is it not possible to have a WCF service that calls back the client before the original service call returns?
Edit: I think this could be the explanation of the issue, but I am still not sure what the correct solution should be.
After updating the ConcurrencyMode have you tried firing the callback in a seperate thread?
This answer to another question has some example code that starts another thread and passes through the callback, you might be able to modify that design for your purpose?
By starting the sync agent in a separate thread on the client, the callback works just fine:
private int kickOffSyncInSeparateThread()
{
SyncRunner syncRunner = new SyncRunner();
Thread syncThread = new Thread(
new ThreadStart(syncRunner.RunSyncInThread));
try
{
syncThread.Start();
}
catch (ThreadStateException ex)
{
Console.WriteLine(ex);
return 1;
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine(ex);
return 2;
}
return 0;
}
And this is my SyncRunner:
class SyncRunner
{
public void RunSyncInThread()
{
MysyncAgent = new MySyncAgent();
syncAgent.addUserIdParameter("56623239-d855-de11-8e97-0016cfe25fa3");
Microsoft.Synchronization.Data.SyncStatistics syncStats =
syncAgent.Synchronize();
}
}