Search code examples
c#wcfuwpws-discovery

WCF discovery in UWP app


I've created an universal app that connects to a WCF webservice at intranet, an it's working just fine, since the address of the service's host is known.

The system's architecture allows to be more than one webservice running, in different hosts, for performance and security (redundancy) reasons. So I'm trying to make my app discover every service, with the given contract, that is been running on the same LAN, but I can't manage to do that.

I'm trying the same approach used at a very similar win32 app:

var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
var findCriteria = new FindCriteria(typeof(INewProdColetorWCFService));
findCriteria.Duration = TimeSpan.FromSeconds(5);
var findResponse = await discoveryClient.FindTaskAsync(findCriteria);

Visual Studio "automatically" adds the needed reference (System.ServiceModel.Discovery) for me as seen here

At design time it seems to be ok, but when i try to compile, that error appear:

Cannot find type System.ServiceModel.Configuration.ServiceModelConfigurationElementCollection`1 in module System.ServiceModel.dll.

Have any of you did that in UWP? Can you help me? Thanks in advance, iuri.

ps: I've posted this question in MSDN too


Solution

  • I don't know if I should answer my own question, but I think it may be useful for anyone trying to do the same, so here it goes.

    Since WS-Discovery API is not available in UWP, I had to do it in another way. Using socket was the best alternative I could find. So every WS will listen to a specific port, awaiting for some broadcasted message searching for WS running in the LAN.

    The WS implementation is win32, and this is the code needed:

    private byte[] dataStream = new byte[1024];
    private Socket serverSocket;
    private void InitializeSocketServer(string id)
    {
        // Sets the server ID
        this._id = id;
        // Initialise the socket
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        // Initialise the IPEndPoint for the server and listen on port 30000
        IPEndPoint server = new IPEndPoint(IPAddress.Any, 30000);
        // Associate the socket with this IP address and port
        serverSocket.Bind(server);
        // Initialise the IPEndPoint for the clients
        IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);
        // Initialise the EndPoint for the clients
        EndPoint epSender = (EndPoint)clients;
        // Start listening for incoming data
        serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(ReceiveData), epSender);
    }
    
    private void ReceiveData(IAsyncResult asyncResult)
    {
        // Initialise the IPEndPoint for the clients
        IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);
        // Initialise the EndPoint for the clients
        EndPoint epSender = (EndPoint)clients;
        // Receive all data. Sets epSender to the address of the caller
        serverSocket.EndReceiveFrom(asyncResult, ref epSender);
        // Get the message received
        string message = Encoding.UTF8.GetString(dataStream);
        // Check if it is a search ws message
        if (message.StartsWith("SEARCHWS", StringComparison.CurrentCultureIgnoreCase))
        {
            // Create a response messagem indicating the server ID and it's URL
            byte[] data = Encoding.UTF8.GetBytes($"WSRESPONSE;{this._id};http://{GetIPAddress()}:5055/wsserver");
            // Send the response message to the client who was searching
            serverSocket.BeginSendTo(data, 0, data.Length, SocketFlags.None, epSender, new AsyncCallback(this.SendData), epSender);
        }
        // Listen for more connections again...
        serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(this.ReceiveData), epSender);
    }
    
    private void SendData(IAsyncResult asyncResult)
    {
        serverSocket.EndSend(asyncResult);
    }
    

    The client implementation is UWP. I've created the following class to do the search:

    public class WSDiscoveryClient
    {
        public class WSEndpoint
        {
            public string ID;
            public string URL;
        }
    
        private List<WSEndpoint> _endPoints;
        private int port = 30000;
        private int timeOut = 5; // seconds
    
        /// <summary>
        /// Get available Webservices
        /// </summary>
        public async Task<List<WSEndpoint>> GetAvailableWSEndpoints()
        {
            _endPoints = new List<WSEndpoint>();
    
            using (var socket = new DatagramSocket())
            {
                // Set the callback for servers' responses
                socket.MessageReceived += SocketOnMessageReceived;
                // Start listening for servers' responses
                await socket.BindServiceNameAsync(port.ToString());
    
                // Send a search message
                await SendMessage(socket);
                // Waits the timeout in order to receive all the servers' responses
                await Task.Delay(TimeSpan.FromSeconds(timeOut));
            }
            return _endPoints;
        }
    
        /// <summary>
        /// Sends a broadcast message searching for available Webservices
        /// </summary>
        private async Task SendMessage(DatagramSocket socket)
        {
            using (var stream = await socket.GetOutputStreamAsync(new HostName("255.255.255.255"), port.ToString()))
            {
                using (var writer = new DataWriter(stream))
                {
                    var data = Encoding.UTF8.GetBytes("SEARCHWS");
                    writer.WriteBytes(data);
                    await writer.StoreAsync();
                }
            }
        }
    
        private async void SocketOnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
        {
            // Creates a reader for the incoming message
            var resultStream = args.GetDataStream().AsStreamForRead(1024);
            using (var reader = new StreamReader(resultStream))
            {
                // Get the message received
                string message = await reader.ReadToEndAsync();
                // Cheks if the message is a response from a server
                if (message.StartsWith("WSRESPONSE", StringComparison.CurrentCultureIgnoreCase))
                {
                    // Spected format: WSRESPONSE;<ID>;<HTTP ADDRESS>
                    var splitedMessage = message.Split(';');
                    if (splitedMessage.Length == 3)
                    {
                        var id = splitedMessage[1];
                        var url = splitedMessage[2];
                        _endPoints.Add(new WSEndpoint() { ID = id, URL = url });
                    }
                }
            }
        }
    }
    

    Feel free to comment if you see something wrong, and please tell me if it helps you someway.