WCF discovery in UWP app

限于喜欢 提交于 2019-12-11 13:55:30

问题


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


回答1:


I came across this thread whilst doing some research myself. After reading up on https://en.wikipedia.org/wiki/WS-Discovery and using Wireshark to decipher some of the specifics, I've got a basic proof of concept that supports Microsoft's WS-Discovery specification, meaning no changes are necessary on the server end.

I've jumped off the project for now, but hopefully someone might get some use from it:

public class WSDiscoveryResponse
{
    private readonly string
        _content,
        _remotePort;

    private readonly HostName
        _remoteAddress;

    public WSDiscoveryResponse(string content, HostName remoteAddress, string remotePort)
    {
        this._content = content;
        this._remoteAddress = remoteAddress;
        this._remotePort = remotePort;
    }
}

public class WSDiscoveryClient
{
    private const string
        SRC_PORT = "0",//represents 'use any port available'
        DEST_IP_WSDISCOVERY = "239.255.255.250", //broadcast (ish)
        DEST_PORT_WSDISCOVERY = "3702";

    private TimeSpan _timeout = TimeSpan.FromSeconds(5);

    private List<WSDiscoveryResponse> _wsresponses = null;

    /// <summary>
    /// Get available Webservices
    /// </summary>
    public async Task<List<WSDiscoveryResponse>> GetAvailableWSEndpoints()
    {
        _wsresponses = new List<WSDiscoveryResponse>();
        using (var socket = new DatagramSocket())
        {
            try
            {
                socket.MessageReceived += SocketOnMessageReceived;
                //listen for responses to future message
                await socket.BindServiceNameAsync(SRC_PORT);
                //broadcast interrogation
                await SendDiscoveryMessage(socket);
                //wait for broadcast responses
                await Task.Delay(_timeout).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                SocketErrorStatus webErrorStatus = SocketError.GetStatus(ex.GetBaseException().HResult);
            }
        }
        return _wsresponses;
    }

    private string BuildDiscoveryMessage()
    {
        const string outgoingMessageFormat = @"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:a=""http://www.w3.org/2005/08/addressing""><s:Header><a:Action s:mustUnderstand=""1"">http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe</a:Action><a:MessageID>urn:uuid:{0}</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=""1"">urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01</a:To></s:Header><s:Body><Probe xmlns=""http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01""><d:Types xmlns:d=""http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01"" xmlns:dp0=""http://tempuri.org/"">dp0:IDiscoveryService</d:Types><Duration xmlns=""http://schemas.microsoft.com/ws/2008/06/discovery"">PT5S</Duration></Probe></s:Body></s:Envelope>";
        string outgoingMessage = string.Format(outgoingMessageFormat, Guid.NewGuid().ToString());
        return outgoingMessage;
    }

    private async Task SendDiscoveryMessage(DatagramSocket socket)
    {
        using (var stream = await socket.GetOutputStreamAsync(new HostName(DEST_IP_WSDISCOVERY), DEST_PORT_WSDISCOVERY))
        {
            string message = BuildDiscoveryMessage();
            var data = Encoding.UTF8.GetBytes(message);
            using (var writer = new DataWriter(stream))
            {
                writer.WriteBytes(data);
                await writer.StoreAsync();
            }
        }
    }

    private void SocketOnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
    {
        var dr = args.GetDataReader();
        string message = dr.ReadString(dr.UnconsumedBufferLength);

        _wsresponses.Add(new WSDiscoveryResponse(message, args.RemoteAddress, args.RemotePort));
    }
}



回答2:


UWP doesn’t support the WS-Discovery API for now. Details please see https://msdn.microsoft.com/en-us/library/windows/apps/mt185502.aspx. There is no System.ServiceModel.Discovery API support for UWP apps in the document. But you can use it in a win32 application. If you need this feature you can submit your idea to UserVoice site: https://wpdev.uservoice.com/forums/110705-universal-windows-platform




回答3:


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.



来源:https://stackoverflow.com/questions/37285149/wcf-discovery-in-uwp-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!