-->

wcf callback reference

2019-08-23 22:07发布

问题:

I have a desktop app with a duplex WCF service, but I have some troubles using the callback.

The service is started as following in main of program.cs:

ServiceHost svcHost = new ServiceHost(typeof(PeriodicService));
svcHost.Open();
Console.WriteLine("Available Endpoints :\n");
svcHost.Description.Endpoints.ToList().ForEach(endpoint =>   Console.WriteLine(endpoint.Address.ToString() + " -- " + endpoint.Name));

For the service I created a subscribe function where the callbackchannel is saved in a global variable, then the callback uses that global variable to talk back to the client (there will be only one client connecting).

IPeriodicCallback callbackClient;

public IPeriodicCallback Proxy
{
     get
     {
        return this.callbackClient;
     }
}

public void joinPeriodicService()
{
    Console.WriteLine("Client subscribe");
    this.callbackClient = OperationContext.Current.GetCallbackChannel<IPeriodicCallback>();
}

The thing I want to do now is call the callbackclient from an other class. In the other class I created the service as:

private PeriodicService periodicService = new PeriodicService();

And I try to write data to it using:

if(this.periodicService.Proxy != null)
{ 
     this.periodicService.Proxy.On1MinuteDataAvailable(tmpPeriod);
}

However the proxy stays null, I also tried to move the proxy part to the class but this also results in it staying null.

When the client connects I nicely get the message "Client Subscribe" but it seems there are two instances running of the periodicservice.

But my problem is I don't see an other way to access the periodicservice then creating it in my class, or is it also already created by the svcHost?

Can ayone point me in the right direction?

回答1:

This repository shows the a duplex WCF imeplementation I made to answer a similar question a while ago, its a full working example with as little extra stuff as possible.

https://github.com/Aelphaeis/MyWcfDuplexPipeExample

Lets say we have a Service Contract like this :

[ServiceContract(CallbackContract = typeof(IMyServiceCallback),SessionMode = SessionMode.Required)]
public interface IMyService
{
    [OperationContract(IsOneWay=true)]
    void DoWork();
}

Note that I specified a CallbackContract. If you want to make a duplex, you would want to perhaps make your Service Behavior implementation of the above contract like this :

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
    public void DoWork()
    {
        Console.WriteLine("Hello World");
        Callback.WorkComplete();
    }

    IMyServiceCallback Callback
    {
        get
        {
            return OperationContext.Current.GetCallbackChannel<IMyServiceCallback>();
        }
    }
}

The important thing here is the Callback. This is how your service would allow you to access specified to you by the Client.

You also need to define the callback interface, In my case its quite simple :

[ServiceContract]
public interface IMyServiceCallback 
{
    [OperationContract(IsOneWay = true)]
    void WorkComplete();
}

Now I want to create a client to use this Duplex Service. The first thing I need to do is implement the IMyServiceCallback. I need to do this on the client. In this case the implementation is this:

class Callback : IMyServiceCallback
{
    public void WorkComplete()
    {
        Console.WriteLine("Work Complete");
    }
}

Now when I want to open my duplex connection with the services I would create a proxy class like this something like this:

public class MyServiceClient: IMyService, IDisposable
{
    DuplexChannelFactory<IMyService> myServiceFactory { get; set; }

    public MyServiceClient(IMyServiceCallback Callback)
    {
        InstanceContext site = new InstanceContext(Callback);
        NetNamedPipeBinding binding = new NetNamedPipeBinding();
        EndpointAddress endpointAddress = new EndpointAddress(Constants.myPipeService + @"/" + Constants.myPipeServiceName);

        myServiceFactory = new DuplexChannelFactory<IMyService>(site, binding, endpointAddress);
    }

    public void DoWork()
    {
        myServiceFactory.CreateChannel().DoWork();
    }

    public void Dispose()
    {
        myServiceFactory.Close();
    }
}

Notice that I specified an InstanceContext. That Instance Context will be an instance of the object I created that implements IMyServiceCallback.

That's all you need to do! Simple as that!

Update : Callback objects are just like any other object. You can store them into a collection and iterate through them and based on some condition.

One way is to create a property in the IMyServiceCallback that can uniquely identify it. When a client connects to the service it can call a method which specifies a callback object which can then be cached or saved for later use. You can then iterate the callbacks and based on some condition you can call a method for a specific client.

This is certainly more complicated; however, it is certainly manageable. I will add an example in a bit.

Update 2 This is a working example of exactly what you want; however, its a lot more complicated. I'll try to explain as simply as I can : https://github.com/Aelphaeis/MyWcfDuplexPipeExample/tree/MultiClient

Here is a list of the changes:

  • I've modified the client proxy (and service) so that when initialized it calls the init Method
  • I've also modified the Service implementation so that now it is a single instance dealing with all requests (for convenience).
  • I added a new OperationContract in the Service interface called Msg
  • I've added a new Method in the IMyServiceCallback called RecieveMessage.
  • I've added a way to identify the client.

In the proxy class I have the following :

public MyServiceClient(IMyServiceCallback Callback)
{
    InstanceContext site = new InstanceContext(Callback);
    NetNamedPipeBinding binding = new NetNamedPipeBinding();
    EndpointAddress endpointAddress = new EndpointAddress(Constants.myPipeService + @"/" + Constants.myPipeServiceName);
    myServiceFactory = new DuplexChannelFactory<IMyService>(site, binding, endpointAddress);
    Init();
}
public void Init()
{
    myServiceFactory.CreateChannel().Init();
}

In my service I have the following :

public class MyService : IMyService
{
    public List<IMyServiceCallback> Callbacks { get; private set; }

    public MyService(){
        Callbacks = new List<IMyServiceCallback>();
    }

    public void Init()
    {
        Callbacks.Add(Callback);
    }
// and so on

My IMyServiceCallback has been redefined to :

[ServiceContract]
public interface IMyServiceCallback 
{
    [OperationContract]
    int GetClientId();

    [OperationContract(IsOneWay = true)]
    void WorkComplete();

    [OperationContract(IsOneWay = true)]
    void RecieveMessage(String msg);
}

By specifying a number, you can contact the client that corresponds with that number. If two clients have the same Id, both clients will be contacted.