Why doesn't IServerStreamWriter send notification responses? - c#

I work on a cross-platform application. For connection between them, I use gRPC technology. When a client connects to server, it is added to an observers list located in server implementation. When a client connects, I want to send a message to the rest of connected clients telling them that a new client connected. The problem is that when I want to send a response to clients that a new client connected, using the observers from my list, I get the following exception:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler.")'
This is my proto file where I declared my server:
syntax = "proto3";
package com.example.grpc.chat;
message ChatMessage {
string from = 1;
string message = 2;
}
message ChatMessageFromServer {
ChatMessage message = 2;
}
service ChatService {
rpc Login(ChatMessage ) returns (stream ChatMessageFromServer);
}
The server code :
public class ChatServiceImpl : ChatService.ChatServiceBase
{
private static HashSet<IServerStreamWriter<ChatMessageFromServer>> responseStreams = new HashSet<IServerStreamWriter<ChatMessageFromServer>>();
/*
* if the stream object (from "for" statement inside this method) isn't the responseStream object given in the list with parameters,
* the rest of clients aren't notified when a new login request is pushed.
*/
public override async Task Login(global::Com.Example.Grpc.Chat.ChatMessage request,
IServerStreamWriter<global::Com.Example.Grpc.Chat.ChatMessageFromServer> responseStream,
ServerCallContext context)
{
Console.WriteLine("Login method from server");
responseStreams.Add(responseStream);
// Create a server message that wraps the client message
var message = new ChatMessageFromServer
{
Message = new ChatMessage
{
From = "login",
Message = "hello"
}
};
// If stream variable isn't equal to responseStream from list of parameters, the client corresponding to that stream isn't notified and it's thrown the above exception
foreach (var stream in responseStreams)
{
await stream.WriteAsync(message);
}
}
}
The client code where the client send a login request:
public partial class ChatForm : Form
{
private const string Host = "localhost";
private const int Port = 9090;
private ChatService.ChatServiceClient _chatService;
public ChatForm()
{
//InitializeComponent();
InitializeGrpc();
}
private void InitializeGrpc()
{
// Create a channel
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
// Create a client with the channel
_chatService = new ChatService.ChatServiceClient(channel);
}
private async void ChatForm_Load(object sender, EventArgs e)
{
var message = new ChatMessage
{
From = "Unknown",
Message = "Login text"
};
// Open a connection to the server
try
{
using (var call = _chatService.Login(message))
{
// Read messages from the response stream
while (await call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
chatTextBox.Text += displayMessage;
}
}
}
catch (RpcException )
{
throw;
}
}
}

Your notifyObservers method is asynchronous but has a void return type, which means you can't await it. You're effectively starting the method, and returning as soon as you hit the first await operator that uses an incomplete awaitable (the first WriteAsync call, probably).
You then return the task with a ReservationResponse, and the operation completes.
When that first awaitable call completes, notifyObservers will continue, but at that point the operation has already completed, so when you try to write to the response stream, the system will throw the error you're seeing.
I strongly suspect you should return a Task from notifyObservers and await that from your main entry method:
// Names changed to be conventional C#
public override async Task<ReservationResponse> SaveReservation(
global::Res.Protocol.ReservationRequest request, ServerCallContext context)
{
// some code for saving my reservation in repository database
ReservationResponse response = new Res.Protocol.ReservationResponse
{
Type = ReservationResponse.Types.Type.Savereservation,
Journey = GetProtoJourney(journey)
};
await NotifyObserversAsync(response);
// Note: no Task.FromResult, as you're in an async method. The response
// will already be wrapped in a task.
return new ReservationResponse
{
Type = ReservationResponse.Types.Type.Savereservation
};
}
public async Task NotifyObserversAsync(Res.Protocol.ReservationResponse response)
{
foreach (var ob in responseStreams)
{
await ob.WriteAsync(response);
}
}

Related

Discord C# + API OpenAI

using Discord;
using Discord.WebSocket;
using RestSharp;
using Newtonsoft.Json;
using System.Net;
namespace DiscordBot.Service;
public class OpenAiService
{
private const string OpenAiApiKey = "OPENAIKEY";
private const string ChatGptApiUrl = "https://api.openai.com/v1/completions";
private const string DalleApiUrl = "https://api.openai.com/v1/images/generations";
/// <param name="message"></param>
internal static async Task<Tuple<bool, string>> ChatGpt(SocketMessage message)
{
try
{
if (string.IsNullOrWhiteSpace(message.Content.Substring(5)))
{
await message.Channel.SendMessageAsync("The message content is empty. Please provide a prompt for the ChatGPT API.");
return new Tuple<bool, string>(false, "The message content is empty.");
}
// Create a new RestClient instance
var client = new RestClient(ChatGptApiUrl);
// Create a new RestRequest instance
var request = new RestSharp.RestRequest("", Method.Post);
// Set the request headers
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", $"Bearer {OpenAiApiKey}");
// Create the request data
var data = new
{
// The prompt is everything after the !chat command
model = "text-davinci-003",
prompt = message.Content.Substring(5),
max_tokens = 256
};
var jsonData = JsonConvert.SerializeObject(data);
// Add the request data to the request body
request.AddJsonBody(jsonData);
// Send the request and get the response
var response = await client.ExecuteAsync(request);
// Holds the response from the API.
string responseText;
var success = true;
// Check the status code of the response
if (response.Content != null && response.StatusCode == HttpStatusCode.OK)
{
// Get the response text from the API
responseText = JsonConvert.DeserializeObject<dynamic>(response.Content)?["choices"][0]["text"] ??
"Could not deserialize response from ChatGPT Api!";
}
else
{
// Get the ErrorMessage from the API
responseText = response.ErrorMessage ?? string.Empty;
success = false;
}
// Send the response to the Discord chat
await message.Channel.SendMessageAsync(responseText);
return new Tuple<bool, string>(success, responseText);
}
catch (Exception ex)
{
// Log the exception or handle it in another way
Console.WriteLine(ex.Message);
return new Tuple<bool, string>(false, "An error occurred while sending the request to the ChatGPT API");
}
}
/// <summary>
/// The method uses the RestClient class to send a request to the Dall-E API, passing the user's message as the
/// prompt and sends an image to the Chat
/// </summary>
/// <param name="message"></param>
/// <returns>Boolean indicating whether the request was successful</returns>
internal static async Task<Tuple<bool, string>> DallE(SocketMessage message)
{
try
{
if (string.IsNullOrWhiteSpace(message.Content.Substring(5)))
{
await message.Channel.SendMessageAsync("The message content is empty. Please provide a prompt for the ChatGPT API.");
return new Tuple<bool, string>(false, "The message content is empty.");
}
// Create a new RestClient instance
var client = new RestClient(DalleApiUrl);
// Create a new RestRequest instance
var request = new RestSharp.RestRequest("", Method.Post);
// Set the request headers
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", $"Bearer {OpenAiApiKey}");
// Create the request data
var data = new
{
// The prompt is everything after the !image command
model = "image-alpha-001",
prompt = message.Content.Substring(6),
n = 1,
size = "1024x1024"
};
var jsonData = JsonConvert.SerializeObject(data);
// Add the request data to the request body
request.AddJsonBody(jsonData);
// Send the request and get the response
var response = await client.ExecuteAsync(request);
// Holds the response from the API.
string responseText;
var success = false;
// Check the status code of the response
if (response.Content != null && response.StatusCode == HttpStatusCode.OK)
{
// Get the image URL from the API response
var imageUrl = JsonConvert.DeserializeObject<dynamic>(response.Content)?["data"][0]["url"];
responseText = $"Here is the generated image: {imageUrl}";
success = true;
}
else
{
// Get the ErrorMessage from the API
responseText = response.ErrorMessage ?? string.Empty;
}
// Send the response to the Discord chat
await message.Channel.SendMessageAsync(responseText);
return new Tuple<bool, string>(success, responseText);
}
catch (Exception ex)
{
// Log the exception or handle it in another way
Console.WriteLine(ex.Message);
return new Tuple<bool, string>(false, "An error occurred while sending the request to the Dall-e API");
}
}
}
public class Program
{
private const string DiscordToken = "BOTTOKEN";
private static void Main()
{
MainAsync().GetAwaiter().GetResult();
}
public static async Task MainAsync()
{
//Creates a config with specified gateway intents
var config = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
};
// Create a new Discord client
var client = new DiscordSocketClient(config);
// Log messages to the console
client.Log += Log;
// Handle messages received
client.MessageReceived += HandleCommand;
// Login to Discord
await client.LoginAsync(TokenType.Bot, DiscordToken);
// Start the client
await client.StartAsync();
// Block this program until it is closed
await Task.Delay(-1);
}
/// <summary>
/// This method is called whenever a message is received
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private static async Task HandleCommand(SocketMessage message)
{
var success = true;
var responseText = string.Empty;
await Log(new LogMessage(LogSeverity.Info, nameof(HandleCommand),
"New Message incoming..."));
// Check if the message starts with one of these commands
switch (message.Content)
{
case { } chat when chat.StartsWith("!chat"):
await Log(new LogMessage(LogSeverity.Info, nameof(HandleCommand),
"Recived !chat command: " + message.Content));
(success, responseText) = await OpenAiService.ChatGpt(message);
break;
case { } image when image.StartsWith("!image"):
await Log(new LogMessage(LogSeverity.Info, nameof(HandleCommand),
"Recived !image command: " + message.Content));
(success, responseText) = await OpenAiService.DallE(message);
break;
default:
await Log(new LogMessage(LogSeverity.Info, nameof(HandleCommand),
"No command found, normal message"));
break;
}
if (!success)
await Log(new LogMessage(LogSeverity.Warning, nameof(HandleCommand),
"Error with one of the request to the Apis!"));
if (!string.IsNullOrEmpty(responseText))
await Log(new LogMessage(LogSeverity.Info, nameof(HandleCommand),
"Respone: " + responseText));
}
/// <summary>
/// This method logs messages to the console
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
private static Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}
Here is my code that does not contain errors according to the compiler but causes errors in the console:
15:58:09 Discord Discord.Net v3.9.0 (API v10)
15:58:09 Gateway Connecting
15:58:11 Gateway You're using the GuildScheduledEvents gateway intent without listening to any events related to that intent, consider removing the intent from your config.
15:58:11 Gateway You're using the GuildInvites gateway intent without listening to any events related to that intent, consider removing the intent from your config.
15:58:11 Gateway Connected
15:58:11 Gateway Ready
15:58:36 HandleComma New Message incoming...
15:58:36 HandleComma No command found, normal message
15:58:55 HandleComma New Message incoming...
15:58:55 HandleComma Recived !chat command: !chat explain me quantum physic in simple terms
Argument cannot be blank. (Parameter 'Content')
15:58:55 HandleComma Error with one of the request to the Apis!
15:58:55 HandleComma Respone: An error occurred while sending the request to the ChatGPT API
So I asked ChatGPT, who returned my code without modification.
Then I looked for help on this:
You're using the GuildScheduledEvents gateway intent without listening to any events related to that intent, consider removing the intent from your config. You're using the GuildInvites gateway intent without listening to any events related to that intent, consider removing the intent from your config.
I couldn't find anything except this: Events in Discord.Net
I would need help and am at your disposal for any further information

How to send acknowledgment (Consumer) in RabbitMQ externally?

I have an application where it sending message to RMQ broker as below:
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
using (var connection = connectionFactory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("demo",
durable:true,
exclusive:false,
autoDelete:false,
arguments:null);
Console.WriteLine("Click enters to send random case Id");
do
{
Console.ReadLine();
var message = new {CaseId = new Random().Next()};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
channel.BasicPublish("", "demo", null, body);
Console.WriteLine("Successfully send message.");
} while (true);
}
}
It successfully sent the message.
There is another app called the consumer app.
The code is below:
private void InitiateRabbitMq()
{
var connectionFactory = new ConnectionFactory()
{
HostName = "localhost"
};
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
MessageHandler messageReceiver = new MessageHandler(channel);
channel.BasicConsume("demo", false, messageReceiver);
}
The message handler is:
public class MessageHandler : DefaultBasicConsumer
{
private readonly IModel _channel;
public MessageHandler(IModel channel)
{
_channel = channel;
}
public override async void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,
IBasicProperties properties, ReadOnlyMemory<byte> body)
{
var message = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(body.ToArray()));
_processor.process(message);
}
}
Here is the process method parth which is another class:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
UploadFilesFinishedCallback);
Once begin transfer completed. It invoked UploadFilesFinishedCallback . I want to acknowledge in this method. How can I do it?
This is how you Acknowledge message:
channel.BasicAck(deliveryTag, false);
So it means your Function "UploadFilesFinishedCallback" must have the deliveryTag
==> and this means that also your "process" function must have the deliveryTag (which currently gets only the message content)
solution:
add new parameter "deliveryTag" to function "process", and to function "UploadFilesFinishedCallback"
you can use it in the callback like this:
client.BeginTransfer(transfer,
HandleUploadProgressChanged,
HandleUploadComplete,
HandleUploadInterrupted,
HandleUploadCancelled,
3600,
() => { UploadFilesFinishedCallback(deliveryTag) });
(depends on the signature of the callback function)

StreamSocketListener Recive Multiple Messages

This is my code, can you help me to change the code so that I can recive multiple messages? At the moment I can just recive one question then the client must reconnect.
I hope you can help me.
public string PortNumber = "1337";
public MainPage()
{
this.InitializeComponent();
StartServer();
}
private async void StartServer()
{
try
{
var streamSocketListener = new Windows.Networking.Sockets.StreamSocketListener();
// The ConnectionReceived event is raised when connections are received.
streamSocketListener.ConnectionReceived += this.StreamSocketListener_ConnectionReceived;
// Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
await streamSocketListener.BindServiceNameAsync(this.PortNumber);
this.serverListBox.Items.Add("server is listening...");
}
catch (Exception ex)
{
Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
this.serverListBox.Items.Add(webErrorStatus.ToString() != "Unknown" ? webErrorStatus.ToString() : ex.Message);
}
}
private async void StreamSocketListener_ConnectionReceived(Windows.Networking.Sockets.StreamSocketListener sender, Windows.Networking.Sockets.StreamSocketListenerConnectionReceivedEventArgs args)
{
string request;
using (var streamReader = new StreamReader(args.Socket.InputStream.AsStreamForRead()))
{
request = await streamReader.ReadLineAsync();
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.TB1.Text=(string.Format("server received the request: \"{0}\"", request)));
// Echo the request back as the response.
using (Stream outputStream = args.Socket.OutputStream.AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(request);
await streamWriter.FlushAsync();
}
}
}
You should always listen the upcoming message in the StreamSocketListener.ConnectionReceived Event, this event will only trigger when a connection was received on the StreamSocketListener object. If you want to always receive the sent data, you should always read it in the StreamSocketListener.ConnectionReceived Event.
Here is the sample code in the official StreamSocket:
In the Scenario1_Start.xaml.cs, the OnConnection is same as your StreamSocketListener_ConnectionReceived method. You can also learn more from the official sample.
private async void OnConnection(
StreamSocketListener sender,
StreamSocketListenerConnectionReceivedEventArgs args)
{
DataReader reader = new DataReader(args.Socket.InputStream);
try
{
while (true)
{
// Read first 4 bytes (length of the subsequent string).
uint sizeFieldCount = await reader.LoadAsync(sizeof(uint));
if (sizeFieldCount != sizeof(uint))
{
// The underlying socket was closed before we were able to read the whole data.
return;
}
// Read the string.
uint stringLength = reader.ReadUInt32();
uint actualStringLength = await reader.LoadAsync(stringLength);
if (stringLength != actualStringLength)
{
// The underlying socket was closed before we were able to read the whole data.
return;
}
// Display the string on the screen. The event is invoked on a non-UI thread, so we need to marshal
// the text back to the UI thread.
NotifyUserFromAsyncThread(
String.Format("Received data: \"{0}\"", reader.ReadString(actualStringLength)),
NotifyType.StatusMessage);
}
}
catch (Exception exception)
{
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
NotifyUserFromAsyncThread(
"Read stream failed with error: " + exception.Message,
NotifyType.ErrorMessage);
}
}

ASP.NET Core with WebSockets - WebSocket handshake never occurs

I am very new to C# programming, having previously only worked with Java. This project I am building should be very straightforward - we have a web page with a selection of foreign currency pairs. The element chosen is sent to the server, which responds with a hardcoded value of their exchange rate. The requirement is that both actions are implemented through the use of WebSockets. Here is the JS code on my page:
var protocol;
var wsUri;
var socket;
window.onload = function(e) {
e.preventDefault();
protocol = location.protocol === "https:" ? "wss:" : "ws:";
wsUri = protocol + "//" + window.location.host;
socket = new WebSocket(wsUri);
socket.onopen = e => {
console.log("socket opened", e);
};
document.getElementById("currencypair").onchange = function()
{
var selector = document.getElementById("currencypair");
var text = selector.options[selector.selectedIndex].text;
socket.send(text);
};
socket.onmessage = function (evt) {
var receivedMessage = evt.data;
document.getElementById("output").html(receivedMessage);
};
};
Here is a snippet of the Startup.cs class Configure method:
app.UseWebSockets();
app.UseMiddleware<WebSocketMiddleware>();
And here is the middleware class to process requests.
public class WebSocketMiddleware
{
private readonly RequestDelegate _next;
public WebSocketMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await _next.Invoke(context);
return;
}
var ct = context.RequestAborted;
using (var socket = await context.WebSockets.AcceptWebSocketAsync())
{
while (true)
{
var stringReceived = await ReceiveStringAsync(socket, ct);
if (CurrencyPairCollection.CurrencyPairs.TryGetValue(stringReceived, out var value))
{
await SendStringAsync(socket, value.ToString(), ct);
}
else
{
throw new Exception("Unexpected value");
}
await Task.Delay(1000, ct);
}
}
}
private static async Task<string> ReceiveStringAsync(WebSocket socket, CancellationToken ct = default(CancellationToken))
{
var buffer = new ArraySegment<byte>();
using (var ms = new MemoryStream())
{
WebSocketReceiveResult result;
do
{
ct.ThrowIfCancellationRequested();
result = await socket.ReceiveAsync(buffer, ct);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
if (result.MessageType != WebSocketMessageType.Text || result.Count.Equals(0))
{
throw new Exception("Unexpected message");
}
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
}
private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))
{
var segment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data));
return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
}
}
Please mind I was working with the following example which contains mistakes listed by people in the comment section. I did my best to resolve them, however due to my limited experience, that may be where the fault lies.
https://www.softfluent.com/blog/dev/Using-Web-Sockets-with-ASP-NET-Core
Basically, upon running the app the browser console immediately reports this:
WebSocket connection to 'ws://localhost:51017/' failed: Error during WebSocket handshake: Unexpected response code: 200
I have been able to answer my own question. So in Startup.cs of which I provided only a snippet, a call to app.UseMvc() is made right before the lines I have already shared. This is generated by the default template. The trick was to move this call to below the following:
app.UseWebSockets();
app.UseMiddleware<WebSocketMiddleware>();
as otherwise the request pipeline is disrupted.
This will allow our socket to open, however without changing the following line in async Task ReceiveStringAsync(...)
var buffer = new ArraySegment<byte>();
to
var buffer = new ArraySegment<byte>(new byte[8192]);
it will still close prematurely. Next, just needed to correct JS syntax error. Changed
document.getElementById("output").html(receivedMessage);
to
document.getElementById("output").value = receivedMessage;
That's it, it works.

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
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));
}
}
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
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.

Categories

Resources