Search code examples
c#winformswcfprocesswcf-client

Handling WCF events in another process


I have a non-serializable object that I would like to access from a separate process. I've looked around and it seems the only viable option is to use WCF but I'm not sure how to do this as I'm new to WCF. If I create a WCF service, how do I 'hook' the WinForm into the various events in the WCF service? For example, the user communicates with the WCF service directly and I would likemy WinForm client to be notified. How would I be able to know when the user has done something with the WCF service and have the WinForm client pick up on that?


Solution

  • A way to achieve what you are looking for is to implement a callback contract on your service. Then your win-forms app would be able to "subscribe" to events fired on the service (such as modifications to your object).

    To do this you implement a service contract with a callback contract:

    [ServiceContract]
    public interface IMyService_Callback
    {
        [OperationContract(IsOneWay = true)]
        void NotifyClients(string message);
    }
    
    [ServiceContract(CallbackContract = typeof(IMyService_Callback))]
    public interface IMyService
    {
        [OperationContract]
        bool Subscribe();
    }
    

    You then implement your service:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class MyService : IMyService
    {
        private List<IMyService_Callback> callbacks;
    
        public MyService()
        {
            this.callbacks = new List<IMyService_Callback>();
        }
    
        private void CallClients(string message)
        {
            callbacks.ForEach(callback => callback.NotifyClients(message));
        }
    
        public bool Subscribe()
        {
            var callback = OperationContext.Current.GetCallbackChannel<IMyService_Callback>();
    
            if (!this.callbacks.Contains(callback))
            {
                this.callbacks.Add(callback);
            }
    
            // send a message back to the client
            CallClients("Added a new callback");
    
            return true;
        }
    }
    

    In your winforms client you only need to implement the callback method:

    [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
    public partial class ServiceClient : Form, IMyService_Callback
    {
        // Sync context for enabling callbacks
        SynchronizationContext uiSyncContext;
    
        public ServiceClient()
        {
            InitializeComponent(); //etc.
    
            uiSyncContext = SynchronizationContext.Current;
    
            // Create proxy and subscribe to receive callbacks
            var factory = new DuplexChannelFactory<IMyService>(typeof(ServiceClient), "NetTcpBinding_IMyService");
            var proxy = factory.CreateChannel(new InstanceContext(this));
            proxy.Subscribe();
        }
    
        // Implement callback method
        public void NotifyClients(string message)
        {
            // Tell form thread to update the message text field
            SendOrPostCallback callback = state => this.Log(message);
    
            uiSyncContext.Post(callback, "Callback");
        }
    
        // Just updates a form text field
        public void Log(string message)
        {
            this.txtLog.Text += Environment.NewLine + message;
        }
    }    
    

    Config for service:

    <system.serviceModel>
      <services>
        <service name="TestService.MyService" behaviorConfiguration="Normal">
          <endpoint 
            address="net.tcp://localhost:8000/MyService" 
            contract="TestService.IMyService" 
            binding="netTcpBinding" />
        </service>
      </services>
      <behaviors>
        <serviceBehaviors>
          <behavior name="Normal" >
            <serviceDebug includeExceptionDetailInFaults="true"/>
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>
    

    Config for client

    <system.serviceModel>
      <client>
        <endpoint address="net.tcp://localhost:8000/MyService" 
                  binding="netTcpBinding"
                  contract="TestService.IMyService"
                  name="NetTcpBinding_IMyService">
        </endpoint>
      </client>
    </system.serviceModel>