tags:

views:

105

answers:

2

I've got a WCF service that offers a Login method. A client is required to call this method (due to it being the only IsInitiating=true). This method should return a string that describes the success of the call in any case. If the login failed, the connection should be closed.

The issue is with the timing of the close. I'd like to send the return value, then immediately close the connection.

string Login (string name, string pass)
{
    if (name != pass) {
        OperationContext.Current.Channel.Close ();
        return "fail";
    }
    else {
        return "yay";
    }
}

The MSDN states that calling Close on the channel

causes an ICommunicationObject to gracefully transition from the Opened state to the Closed state. The Close method allows any unfinished work to be completed before returning. For example, finish sending any buffered messages).

This did not work for me (or my understanding is wrong), as the close is executed immediately - WCF does not wait for the Login method to finish executing and return a string but closes the connection earlier.

Therefore I assume that calling Close does not wait for the running method to finish. Now, how can I still return a value, then close?

In my desperation I tried the following, but it is hardly a serious solution.

new Thread (() => {
    Thread.Sleep (100);
    OperationContext.Current.Channel.Close ();
}).Start ();
A: 

Why do you want to close the connection server side? I understand u want to clean up any resources as soon as possible. However why not manage this by using the available throttling (instance modes and such) and timeout options that WCF provides?

Danthar
+4  A: 

When the documentation says "gracefully", it's really talking about when you invoke ICommunicationObject.Close from the client side. On the service side, if you try to close the channel while the client is still waiting for a response, you're going to have a bad time.

The client will get confused, not bothering to acknowledge the close, then the server will time out of its Close attempt, which in your case is happening on a worker thread, and unhandled exceptions on worker threads are not good for your service's health or your own sanity.

What you really want isn't a graceful connection close at all, you want to forcibly disconnect clients that fail to log in successfully. In order to do that, you have to register an event handler for when the operation is completed, after the response is sent:

public string Login(string name, string pass)
{
    if (name != pass)
    {
        OperationContext.Current.OperationCompleted += LoginFailed;
        return "fail";
    }
    return "yay";
}

private void LoginFailed(object sender, EventArgs e)
{
    OperationContext.Current.Channel.Abort();
}

This will do what I'm pretty sure you want - send "fail" back to the client, clean up everything that needs to be cleaned up, and then disconnect the client, preventing any further activity on the session.

Incidentally, I don't know how close this is to the real service code, but it's a little redundant to have a "Login" method on the service when you can just use WCF's built in service credential system.

Aaronaught
Nice explanation! Yes, that's exactly what I want. The OperationCompleted event sounds like a nice idea, I'll try that. About the credential system: I thought it's overkill in my specific case, but I'll remember and have a look later.
mafutrct