0

I have a self-hosted WCF Service with a peer-to-peer binding. I'm using a DuplexChannel for getting responses. The callback itself is working. I'm saving the callbacks in a static Dictionary. When a client logs in, the callback is added to the Dictionary. But when I create multiple clients, the static variables are empty.

This is my Service interface :

[ServiceContract(SessionMode = SessionMode.Allowed, CallbackContract = typeof(ICallbackService))]
public interface IService
{
    [OperationContract(IsOneWay = true)]
    void Login(string email, string password);

    [OperationContract(IsOneWay = true)]
    void Logout(int userId);
}

My IService implementation :

  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IService
{
    private ServiceHost host = null;
    private DuplexChannelFactory<IService> channelFactory = null;
    private IService service = null;

    private static Dictionary<ICallbackService, ICallbackService> pair = new Dictionary<ICallbackService, ICallbackService>();
    private static Dictionary<string, ICallbackService> clients = new Dictionary<string, ICallbackService>();

    private ICallbackService callback;



    public Service(InstanceContext context)
    {
        startService(context);
    }


    private void startService(InstanceContext context)
    {
        host = new ServiceHost(this);
        host.Open();
        context.Open();
        channelFactory = new DuplexChannelFactory<IBattleshipService>(context, "Endpoint");
        channelFactory.Open();
        service = channelFactory.CreateChannel();

          callback = context.GetServiceInstance() as ICallbackService;
    }

    private void stopService()
    {
        if (host != null)
        {
            if (host.State == CommunicationState.Closed)
            {
                channelFactory.Close();
                host.Close();
            }
        }
    }

    public void Login(string email, string password)
    {

        User user = getAllUsers().Find(u => u.Email.ToLower() == email.ToLower() && u.Password == password);

        if (user != null)
        {
            Console.WriteLine("user : " + email + "    has logged in");
            clients.Add(email, callback);
            callback.Authenticate(true);
        }
        else callback.Authenticate(false);
    }

    public void Logout(int userId)
    {
        string email = getUser(userId).Email;
        clients.Remove(email);
        stopService();
    }
}

My callback service :

public interface ICallbackService
{

    [OperationContract(IsOneWay = true)]
    void Authenticate(bool authenticated);
}

The implementing callback service class in my client application :

 [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)]
public class Client : Service.ICallbackService, 
{
    public bool Authentication { get; internal set; }

    public Client()
    {
    }

    public void Authenticate(bool authenticated)
    {
        Console.WriteLine("authentication : " + authenticated);
        Authentication = authenticated;
    }

}

halfer
  • 19,824
  • 17
  • 99
  • 186
Merve Sahin
  • 1,008
  • 2
  • 14
  • 26
  • 1. Why pair and clients are static? – Artavazd Balayan May 27 '16 at 23:01
  • I want to store all clients and send notifications to certain ones – Merve Sahin May 27 '16 at 23:03
  • Also you must use one of synchronization primitives for pair and clients, for example lock. Or use thread safe container - ConcurrentDictionary – Artavazd Balayan May 27 '16 at 23:04
  • Yes I know, but I wanted to solve this problem first. – Merve Sahin May 27 '16 at 23:05
  • As I can see you're using pair and clients in two non-static methods: Login and Logout. Why pair and clients are static? – Artavazd Balayan May 27 '16 at 23:06
  • Each client instantiates this class and I'm storing them in a static variable otherwise I wouldn't be able to access the callback of each client – Merve Sahin May 27 '16 at 23:08
  • You're using InstanceContextMode.Single - https://msdn.microsoft.com/en-us//library/system.servicemodel.instancecontextmode%28v=vs.110%29.aspx. It means: Only one InstanceContext object is used for all incoming calls and is not recycled subsequent to the calls. If a service object does not exist, one is created. – Artavazd Balayan May 27 '16 at 23:09
  • I'm instantiating the ServiceHost inside the Service class, and because the constructor takes arguments I have to set the mode to Single, otherwise I get an exception – Merve Sahin May 27 '16 at 23:22
  • Thanks for wanting to mark this question as solved. Accepting the answer below is sufficient for that - we prefer titles not to be marked as [solved] etc. Thanks. – halfer Jun 06 '16 at 23:42

1 Answers1

1

Here is full example of worked code. I hope you'll understand the code. It's pretty easy. Here is the link to the project. Run example enter image description here:

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace WCFTest
{
    class Program
    {
        public interface ICallbackService
        {
            [OperationContract(IsOneWay = true)]
            void Authenticate(bool authenticated);
        }
        [ServiceContract(SessionMode = SessionMode.Allowed, CallbackContract = typeof(ICallbackService))]
        public interface IService
        {
            [OperationContract(IsOneWay = true)]
            void Login(string email, string password);

            [OperationContract(IsOneWay = true)]
            void Logout(string email);
        }
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
        public class Service : IService
        {
            private Dictionary<string, ICallbackService> _clientDict = new Dictionary<string, ICallbackService>();

            public void Login(string email, string password)
            {
                WriteInfoAboutInvoker("Login");

                // Get client callback
                var callback = OperationContext.Current.GetCallbackChannel<ICallbackService>();
                lock (_clientDict)
                {
                    _clientDict.Add(email, callback);
                    Console.WriteLine("Added '{0}'", email);
                }
            }
            public void Logout(string email)
            {
                WriteInfoAboutInvoker("Logout");
                lock (_clientDict)
                {
                    if (_clientDict.ContainsKey(email))
                    {
                        _clientDict.Remove(email);
                        Console.WriteLine("Removed '{0}'", email);
                    }
                }
            }
            private void WriteInfoAboutInvoker(string method)
            {
                var ctx = OperationContext.Current;
                var msgProp = ctx.IncomingMessageProperties;
                var endpoint = msgProp[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

                Console.WriteLine("Invoked {0} from {1}:{2}. SessionId: {3}", method, endpoint.Address, endpoint.Port, ctx.SessionId);
            }
        }
        public class ServiceClient : IDisposable, ICallbackService, IService
        {
            private readonly string _cs;
            private IService _service;
            private DuplexChannelFactory<IService> _channel;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="cs">Format is 'hostname:port'</param>
            public ServiceClient(string cs)
            {
                if (string.IsNullOrEmpty(cs))
                    throw new ArgumentException("cs");
                _cs = cs;
            }
            public void Connect()
            {
                try { InternalConnect(); }
                catch (Exception ex)
                {
                    throw new Exception(string.Format("Can't connect to server '{0}'. Error: {1}", _cs, ex.Message));
                }
            }
            public void Dispose()
            {
                try
                {
                    _channel.Closed -= Channel_Closed;
                    _channel.Faulted -= Channel_Faulted;
                    _channel.Close();
                }
                catch { }
            }
            private void InternalConnect()
            {
                InstanceContext ctx = new InstanceContext(this);
                NetTcpBinding tcpBinding = CreateBindings();
                _channel = new DuplexChannelFactory<IService>(ctx, tcpBinding, new EndpointAddress("net.tcp://" + _cs));
                _channel.Closed += Channel_Closed;
                _channel.Faulted += Channel_Faulted;
                _service = _channel.CreateChannel();
            }
            void Channel_Closed(object sender, EventArgs e)
            {
                Console.WriteLine("Channel closed");
            }
            private NetTcpBinding CreateBindings()
            {
                NetTcpBinding tcpBinding = new NetTcpBinding();
                tcpBinding.Security.Mode = SecurityMode.None;
                tcpBinding.Security.Mode = SecurityMode.Transport;
                tcpBinding.CloseTimeout = TimeSpan.FromSeconds(1);
                tcpBinding.OpenTimeout = TimeSpan.FromSeconds(2);
                tcpBinding.ReceiveTimeout = TimeSpan.FromSeconds(15);
                tcpBinding.SendTimeout = TimeSpan.FromSeconds(15);
                return tcpBinding;
            }
            void Channel_Faulted(object sender, EventArgs e)
            {
                Console.WriteLine("Channel faulted!!!");
            }
            [Obsolete("This method used only for callback", true)]
            public void Authenticate(bool authenticated)
            {
                Console.WriteLine("authenticated: {0}", authenticated);
            }
            public void Login(string email, string password)
            {
                _service.Login(email, password);
            }
            public void Logout(string email)
            {
                _service.Logout(email);
            }
        }
        static void Main(string[] args)
        {
            // args[0] contains mode: server or client
            if (args[0] == "server"){
                RunServer("localhost:42424");
            }
            else if (args[0] == "client"){
                RunClient("localhost:42424");
            }
            else{
                Console.WriteLine("Unknown mode. Only server or client");
            }
        }

        private static void RunClient(string cs)
        {
            // Create client for our servuce
            using (var client = new ServiceClient(cs))
            {
                // Connect to it
                client.Connect();
                var fakeEmail = Guid.NewGuid().ToString();
                // Call service methods (operation contracts)
                client.Login(fakeEmail, fakeEmail);
                Console.WriteLine("Invoked Login");

                Console.WriteLine("Press 'Enter' to call Logout");
                Console.ReadLine();

                client.Logout(fakeEmail);
                Console.WriteLine("Invoked Logout");
            }
        }
        static void RunServer(string cs)
        {
            var service = new Service();

            var sh = new ServiceHost(service, new Uri("net.tcp://" + cs));
            Console.WriteLine("ServiceHost created");
            try
            {
                sh.Open();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Couldn't open ServiceHost: {0}", ex);
                return;
            }
            Console.WriteLine("ServiceHost opened");
            sh.Closed += ServiceHost_Closed;
            sh.Faulted += ServiceHost_Faulted;

            Console.WriteLine("Press 'Enter' to quit");
            Console.ReadLine();
        }
        private static void ServiceHost_Faulted(object sender, EventArgs e)
        {
            Console.WriteLine("ServiceHost faulted!!!");
        }
        private static void ServiceHost_Closed(object sender, EventArgs e)
        {
            Console.WriteLine("ServiceHost closed");
        }
    }
}
Merve Sahin
  • 1,008
  • 2
  • 14
  • 26
Artavazd Balayan
  • 2,353
  • 1
  • 16
  • 25
  • I tried this, but the service created by the channel does not invoke the methods in my `Service` class – Merve Sahin May 29 '16 at 17:20
  • I solved the problem, I was using different endpoints I changed them and now it invokes the methods but I get an `ArgumentException : TransactionFlowProperty is already existing`. This happens when the callback is being invoked – Merve Sahin May 29 '16 at 22:43
  • I was using the NetPeerTcpBinding, which was not working because of a bug, I changed it into a TcpBinding and added the SecurityMode to the binding and now it works. – Merve Sahin Jun 05 '16 at 12:31