I'm working on c# project.
I'd like to send API request via websocketsharp library in some sort of synchronous way.
I've been trying to do it following way:
Before we send any WS request , we create new SynchronousRequest() object with unique ID and add the newly created object to some sort of waiting list
We send WS request adding unique ID to the payload, on the response - the server will return the same id.
We start waiting for the event to be signaled (signaling happens once we receive response)
On the response handler:
Once WS response arrives, I try to match the context by the unique ID
Once it's matched, we signal event that the response has been received and add the response payload to the the synchronousRequest() object
Problem is step 3, once i use WaitOne() on the event the entire websocket client hangs and no further responses will be received - resulting in complete deadlock.
How can i do some sort of WaitOne() call in seperate thread or perhaps completely better solution exists for my problem, so the entire client does not hang and we match the contexts?
public class SynchronousRequest
{
public long requestId;
public ManualResetEvent resetEvent = new ManualResetEvent(false);
public dynamic response;
public SynchronousRequest()
{
var random = new Random();
requestId = random.Next();
}
}
public class APIWebSocket: BaseAPIWebSocket
{
private List<SynchronousRequest> waitingSyncRequests = new List<SynchronousRequest>();
public APIWebSocket()
{
ws = new WebSocket("wss://www.someserver.com");
registerConnectionEvents(); //Registers onOpen(), onMessage() handlers and similar
}
public void SendSyncTest()
{
var sr = new SynchronousRequest();
waitingSyncRequests.Add(sr);
//some data to send
var msg = new
{
jsonrpc = "2.0",
method = "public/ticker",
id = sr.requestId, //Response should contain the same ID
#params = new
{
instrument_name = "ETH"
}
};
ws.Send(JsonConvert.SerializeObject(msg));
//Below WaitOne() causes the entire websocket connection/thread to block
// No further messages will be received by HandleMessage() once we call WaitOne()
sr.resetEvent.WaitOne(); //Wait until we receive notification that response has been received
//do some processing on response here...
//Synchronous request completed, remove it from list
waitingSyncRequests.Remove(sr);
}
protected override void OnReceivedMessage(System.Object sender, WebSocketSharp.MessageEventArgs e)
{
dynamic message = JsonConvert.DeserializeObject(e.Data);
if (message.id != null )
{
//Find a resetEvent for given message.id
var matchingSyncRequest = waitingSyncRequests.First(r => r.requestId == message.id);
if (matchingSyncRequest != null)
{
matchingSyncRequest.response = message;
matchingSyncRequest.resetEvent.Set(); //Notify that response has been received
}
}
}
}
From what i understand you need to await on some event that will be set at some future time.
You could consider using TaskCompletionSource.
1.Whenever you send a message that you need its result , you could create a TaskCompletionSource (tcs) , add it to a Dictionary and send
the message over the socket.
2.You await tcs.Task or tcs.Task.Result on that tcs.
3.On another Thread/Task you could process your incoming responses ( in your case you have your received message handler. Whenever you receive a response of a target type or
id , you could just fetch the tcs from the dictionary based on the id, and
Task.SetResult(response) it.In that moment the caller (the one you are
awaiting gets unblocked).
public class Message
{
public string ID{get;set;}
}
public class Response
{
public string Id{get;set;}
}
public void MyClass
{
private ConcurrentDictionary<string,TaskCompletionSource<Response>>map=new ConcurrentDictionary<string,TaskCompletionSource<Response>>();
private Websocket socket;
public void SomeEventTrigger()
{
var msg=new Message{ Id="somespecialID" };
var tcs=new TaskCompletionSource<Response>();
ws.Send(JsonConvert.SerializeObject(msg));
if(!this.map.TryAdd(msg.Id,tcs))
{
return;
}
var result=tcs.Result; //this gets blocked until you use `tcs.SetResult`- > that would happen in your OnReceivedMessage
this.map.TryRemove(msg.Id,out TaskCompletionSource<Response>resp);
}
protected override void OnReceivedMessage(System.Object sender, WebSocketSharp.MessageEventArgs e)
{
Response message = JsonConvert.DeserializeObject<Response>(e.Data);
if (message.id != null )
{
if(this.map.TryGetValue(message.id),out TaskCompletionSource<Response> tcs)
{
tcs.SetResult(message); //this unblocks the method that wrote the message to the socket (above)
}
}
}
}
P.S
You need to make sure that you use Task.SetResult on the correct tcs so that the correct call to SomeEventTrigger method stops waiting.
Related
I'm very close to implementing a system in C# which allows me to send a push-notification with the click of a button from an interface written in ASP.net to a C# console application acting as client. This all using WebSockets.
After reading a lot of tutorials and reusing code found online I'm already able to successfully establish a WebSocket connection. I'm yet not able to actually send a Notification.
The part I'm struggling with is the function that get's triggered as soon as a button is clicked:
//Close ticket and send push-notification over websocket
public void Close(int id) {
//Ticket ticket = mgr.GetTicket(id);
//Create a new notification
Notification notif = new Notification();
notif.message = "Rofl test123 Notification lol";
//Initialize WebSocketMiddleware here??
//WebSocketsMiddleware wsm = new WebSocketsMiddleware(what parameter??);
//wsm.Invoke(what HttpContext parameter???)
NotificationManager notifMgr;
//notifMgr.AddSubscriber(wsm);
//notifMgr.SendNotificationAsync(notif);
return;
}
The specific questions/problems I'm encountering are:
How to initialize the class WebSocketsMiddleware? Does it need to be initialized, if yes, what is the parameter with type RequestDelegate? What do I pass to that parameter?
WebSocketsMiddleware has an Invoke function with parameter context of type HttpContext. Do I just need to pass new HttpContext() to this? Is that sufficient?
Someone made a class NotificationManager, this class uses the middleware to actually send a notification. Do I just need to pass the initialized WebSocketsMiddleware variable as parameter for NotificationManager.AddSubscriber()? Will the notifications of each client be nicely separated then?
Can I after that just use SendNotificationAsync() to send the notification?
Bonus question: Say that each client has it's own button. When I click a client's button only that client may receive a push-notification. How to make sure that all the other client's don't receive the same notification as well?
To be able to help me with these questions you'll need the following classes. The question is merely about WebSockets but more about how to initiate and use the classes I gathered from the tutorials.
Notification.cs - Class representing a Notification (notification text, send-date,...):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SC.UI.MVC.Models
{
public class Notification
{
public Guid? notificationId { get; set; }
public int id { get; set; }
public DateTime timestamp { get; set; }
public string message { get; set; }
public string type { get; set; }
public Notification()
{
// add a new guid as a unique identifier for the notification in the db
notificationId = Guid.NewGuid();
}
}
}
WebSocketsMiddleware.cs - Has the low-level part of the WebSockets handled, invoking connection etc:
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace NotificationsApi.Notifications
{
public class WebSocketsMiddleware
{
// private variable to track the next delegate to call in the request chain
private readonly RequestDelegate _next;
public WebSocketsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
CancellationToken ct = context.RequestAborted;
string currentSubscriberId = null;
WebSocket currentSocket = null;
// we want to listen on a specific path for websocket communications
if (context.Request.Path == "/notifications/ws")
{
// make sure the request is a websocket request
if (context.WebSockets.IsWebSocketRequest)
{
currentSocket = await context.WebSockets.AcceptWebSocketAsync();
currentSubscriberId = NotificationManager.Instance.AddSubscriber(currentSocket);
// keep the socket open until we get a cancellation request
while (true)
{
if (ct.IsCancellationRequested)
{
break;
}
}
}
else // return an HTTP bad request status code if anything other a web socket request is made on this URI
{
context.Response.StatusCode = 400;
}
}
// clean up the socket
if (!string.IsNullOrWhiteSpace(currentSubscriberId))
{
NotificationManager.Instance.RemoveSubscriber(currentSubscriberId);
if (currentSocket != null)
{
await currentSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
currentSocket.Dispose();
}
}
// call the next delegate in the pipeline
await _next(context);
return;
}
}
}
NotificationManager.cs - Interface/Class with three functions to add and remove subscribers, and to actually send a notification. Uses the WebSocket middleware to achieve this:
using SC.UI.MVC.Models;
//using NotificationsApi.Persistence;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NotificationsApi.Notifications
{
// interface for NotificationManager for dependency injection
public interface INotificationManager
{
string AddSubscriber(WebSocket subscriber);
void RemoveSubscriber(string subscriberId);
Task SendNotificationAsync(Notification notification);
}
public class NotificationManager : INotificationManager
{
// static instance of the NotificationManager class
private static INotificationManager _instance;
public static INotificationManager Instance { get { return _instance ?? (_instance = new NotificationManager()); } set { _instance = value; } }
// static dictionary to keep track of all notification subscribers
private static ConcurrentDictionary<string, WebSocket> _subscribers = new ConcurrentDictionary<string, WebSocket>();
// adds a subscriber to receive notifications
public string AddSubscriber(WebSocket subscriber)
{
var subscriberId = Guid.NewGuid().ToString();
_subscribers.TryAdd(subscriberId, subscriber);
return subscriberId.ToString();
}
// removes a notifications subscriber
public void RemoveSubscriber(string subscriberId)
{
WebSocket empty;
_subscribers.TryRemove(subscriberId, out empty);
}
// sends a notification to all subscribers
public async Task SendNotificationAsync(Notification notification)
{
// add the notification to the persistence store
//await PersistenceManager.Instance.AddNotificationAsync(notification);
// send the notification to all subscribers
foreach (var s in _subscribers)
{
if (s.Value.State == WebSocketState.Open)
{
var jsonNotification = JsonConvert.SerializeObject(notification);
await SendStringAsync(s.Value, jsonNotification);
}
}
}
// sends a string via web socket communication
private async Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))
{
var buffer = Encoding.UTF8.GetBytes(data);
var segment = new ArraySegment<byte>(buffer);
await socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
}
}
}
Client.cs - Client receiving the push-notification. Not really a problem here I guess:
/* WEBSOCKET PART */
//Variables for websocket
private static object consoleLock = new object();
private const int sendChunkSize = 256;
private const int receiveChunkSize = 256;
private const bool verbose = true;
private static readonly TimeSpan delay = TimeSpan.FromMilliseconds(30000);
//Function to check if a ticket from this client is closed/solved
public void checkTicketSolved() {
Thread.Sleep(1000);
Connect("ws://localhost:5050/notifications/ws").Wait();
Console.WriteLine("Press any key to exit...");
}
public static async Task Connect(string uri)
{
ClientWebSocket webSocket = null;
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Task.WhenAll(Receive(webSocket), Send(webSocket));
}
catch (Exception ex)
{
Console.WriteLine("Exception: {0}", ex);
}
finally
{
if (webSocket != null)
webSocket.Dispose();
Console.WriteLine();
lock (consoleLock)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WebSocket closed.");
Console.ResetColor();
}
}
}
static UTF8Encoding encoder = new UTF8Encoding();
private static async Task Send(ClientWebSocket webSocket)
{
//byte[] buffer = encoder.GetBytes("{\"op\":\"blocks_sub\"}"); //"{\"op\":\"unconfirmed_sub\"}");
byte[] buffer = encoder.GetBytes("{\"op\":\"unconfirmed_sub\"}");
await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
while (webSocket.State == WebSocketState.Open)
{
LogStatus(false, buffer, buffer.Length);
await Task.Delay(delay);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
byte[] buffer = new byte[receiveChunkSize];
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
LogStatus(true, buffer, result.Count);
}
}
}
private static void LogStatus(bool receiving, byte[] buffer, int length)
{
lock (consoleLock)
{
Console.ForegroundColor = receiving ? ConsoleColor.Green : ConsoleColor.Gray;
//Console.WriteLine("{0} ", receiving ? "Received" : "Sent");
if (verbose)
Console.WriteLine(encoder.GetString(buffer));
Console.ResetColor();
}
}
}
You can also find this code on Github.
The relevant parts of the code are located in:
WebServer/UI-MVC/Controllers/TicketController.cs -> Contains function triggered when a button is clicked.
WebServer/UI-MVC/Notifications -> Contains NotificationManager.cs and WebSocketsMiddleware.cs
WebServer/UI-MVC/Models -> Contains Notification.cs
Client/ contains all the code for the client's console application
To give you some context about the application:
This application represents a ticketing system which allows clients/customers which use my software to open support tickets. The WebServer-part is for administrators/employees of me to answer and manage tickets. The Console Application is what my customers/clients need to have installed in order to contact my support service and open a support ticket. When an administrator closes the ticket of a client by clicking a button, it means the ticket and thus the client's problem was resolved and closed. Resulting in the client getting a push-notification about that.
I am not looking for references to other tutorials about WebSockets or suggestions using SignalR instead or whatever, I've already read all of them and I've already used SignalR but am interested in pure WebSockets now. I would be very grateful for someone who could help me working out the first part of code posted in this question (the Close-function) and explains what he has done. Thanks!
I found the solution myself.
First I made a new controller called NotificationsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SC.UI.MVC.Models;
using NotificationsApi.Notifications;
//using NotificationsApi.Persistence;
using System.Net.Http;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace NotificationsApi.Controllers
{
[Route("api/notifications")]
public class NotificationsController : Controller
{
// GET api/notifications
[HttpGet]
public ActionResult Get()
{
try
{
var notifications = new List<Notification>();
//notifications = PersistenceManager.Instance.GetNotifications();
return Ok(notifications);
}
catch (Exception exception)
{
// log exception
// TODO: implement logging
// return a 500
return StatusCode(500);
}
}
// POST api/notifications
[HttpPost]
public async Task<ActionResult> Post(string message)
{
Notification notification = new Notification();
notification.message = message;
Console.WriteLine(message);
try
{
// return a 400 if we didn't get a valid json payload in the body
if (notification == null)
return BadRequest();
await NotificationManager.Instance.SendNotificationAsync(notification);
// we aren't returning the object to reference because POSTing a notification is fire and forget
return Created(string.Empty, null);
}
catch (Exception exception)
{
// log the error
// TODO: implement logging
// return a 500
return StatusCode(500);
}
return Ok();
}
}
}
Then I commented out the unnecessary properties in Notification.cs so only Guid and message remain. Now I'm able to just send a notification by calling the NotificationsController with a POST request carrying the message-parameter as data.
I have a full engine that relies on abstractions based on user interactions. This works great with WPF/Xamarin app, cause I can implements this abstractions with window/form.
I have a little problem for porting this engine into ASP MVC.
A simple example can be show as this.
Abstraction interface (simplified)
public interface IQuestionBox
{
Task<bool> ShowYesNoQuestionBox(string message);
}
For WPF, it's really simple, I implement this interface as return the result of a window by calling ShowDialog().
In a simple business class, I can have this kind of calls (simplified) :
public async Task<string> GetValue(IQuestionBox qbox)
{
if(await qbox.ShowYesNoQuestionBox("Question ?"))
{
return "Ok";
}
return "NOk";
}
I really don't see how can I implement this kind of behavior in ASP, due to stateless of HTTP, knowing that this kind of call can be as various as domain/business need. The way I think it should be done is by returning a PartialView to inject into popup, but I don't see how to do this without breaking all the process ...
Anyone has ever done this ?
as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:
To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.
First we need to create a structure to encapsulate the process:
public class Process
{
// this dictionary store the current process running status, you will use it to define the future answer from the user interaction
private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
// this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
TaskCompletionSource<bool> AwaitableResult { get; } = new TaskCompletionSource<bool>(true);
// here we have the question to interact with the user
IQuestionBox QuestionBox { get; set; }
// this method, receive your bussiness logical the receive your question as a parameter
public IQuestionBox Run(Action<IQuestionBox> action)
{
QuestionBox = new QuestionBox(this);
// here we create a task to execute your bussiness logical processment
Task.Factory.StartNew(() =>
{
action(QuestionBox);
});
// and as I said we wait the result from the processment
Task.WaitAll(AwaitableResult.Task);
// and return the question box to show the messages for the users
return QuestionBox;
}
// this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
public void RegisterForAnsware(string id)
{
if (StatusReport.ContainsKey(id))
return;
StatusReport.Add(id, new Answare()
{
});
}
// this method will deliver an answer for this correct context based on the id
public Answare GetAnsware(string id)
{
if (!StatusReport.ContainsKey(id))
return Answare.Empty;
return StatusReport[id];
}
// this method Releases the processment
public void Release()
{
AwaitableResult.SetResult(true);
}
// this method end the process delivering the response for the user
public void End(object userResponse)
{
if (!StatusReport.ContainsKey(QuestionBox.Id))
return;
StatusReport[QuestionBox.Id].UserResponse(userResponse);
}
// this method define the answer based on the user interaction, that allows the process continuing from where it left off
public static Task<object> DefineAnsware(string id, bool result)
{
if (!StatusReport.ContainsKey(id))
return Task.FromResult((object)"Success on the operation");
// here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
TaskCompletionSource<object> completedTask = new TaskCompletionSource<object>();
StatusReport[id] = new Answare(completedTask)
{
HasAnswared = true,
Value = result
};
return completedTask.Task;
}
}
After that the question implementation
public interface IQuestionBox
{
string Id { get; }
Task<bool> ShowYesNoQuestionBox(string question);
HtmlString ShowMessage();
}
class QuestionBox : IQuestionBox
{
Process CurrentProcess { get; set; }
public string Id { get; } = Guid.NewGuid().ToString();
private string Question { get; set; }
public QuestionBox(Process currentProcess)
{
CurrentProcess = currentProcess;
CurrentProcess.RegisterForAnswer(this.Id);
}
public Task<bool> ShowYesNoQuestionBox(string question)
{
Question = question;
CurrentProcess.Release();
return AwaitForAnswer();
}
public HtmlString ShowMessage()
{
HtmlString htm = new HtmlString(
$"<script>showMessage('{Question}', '{Id}');</script>"
);
return htm;
}
private Task<bool> AwaitForAnswer()
{
TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(2000);
var answare = CurrentProcess.GetAnswer(this.Id);
if (!answare.HasAnswered)
continue;
awaitableResult.SetResult(answare.Value);
break;
}
});
return awaitableResult.Task;
}
}
The differences for yours implementaion are:
1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.
2 - I receive a Process as parameter, because this allows us to call the method
CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.
3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.
4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.
Then a simple process class as you should be in your bussiness logical:
public class SaleService
{
public async Task<string> GetValue(IQuestionBox qbox)
{
if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
{
return "I knew, Edney is the big guy";
}
return "No I disagree";
}
}
And then the class to represent the user answer
public class Answer
{
// just a sugar to represent empty responses
public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
public Answer()
{
}
// one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
public Answer(TaskCompletionSource<object> completedTask)
{
CompletedTask = completedTask;
}
private TaskCompletionSource<object> CompletedTask { get; set; }
public bool Value { get; set; }
public bool HasAnswered { get; set; }
// this method as you can see, will set the result and release the task for the user
public void UserResponse(object response)
{
CompletedTask.SetResult(response);
}
}
Now we use all the entire structure create for this:
[HttpPost]
public IActionResult Index(string parametro)
{
// create your process an run it, passing what you want to do
Process process = new Process();
var question = process.Run(async (questionBox) =>
{
// we start the service
SaleService service = new SaleService();
// wait for the result
var result = await service.GetValue(questionBox);
// and close the process with the result from the process
process.End(result);
});
return View(question);
}
// here we have the method that deliver us the user response interaction
[HttpPost]
public async Task<JsonResult> Answer(bool result, string id)
{
// we define the result for an Id on the process
var response = await Process.DefineAnswer(id, result);
// get the response from process.End used bellow
// and return to the user
return Json(response);
}
and in your view
<!-- Use the question as the model page -->
#model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
<form asp-controller="Home" asp-action="Index">
<!-- create a simple form with a simple button to submit the home -->
<input type="submit" name="btnDoSomething" value="All about Edney" />
</form>
<!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
<!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
<!-- And to finalize, it just show an alert with the process result -->
#section scripts{
<script>
function showMessage(message, id) {
var confirm = window.confirm(message);
$.post("/Home/Answer", { result: confirm, id: id }, function (e) {
alert(e);
})
}
</script>
#Model?.ShowMessage()
}
As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.
I put the project on github to you can download an understand all the solution
I realy hope it can help you
You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:
Client side:
$app = {
uiEventsSocket : null,
initUIEventsConnection : function(url) {
//create a web socket connection
if (typeof (WebSocket) !== 'undefined') {
this.uiEventsSocket = new WebSocket(url);
} else if (typeof (MozWebSocket) !== 'undefined') {
this.uiEventsSocket = new MozWebSocket(url);
} else {
console.error('WebSockets unavailable.');
}
//notify if there is an web socket error
this.uiEventsSocket.onerror = function () {
console.error('WebSocket raised error.');
}
this.uiEventsSocket.onopen = function () {
console.log("Connection to " + url + " established");
}
//handling message from server side
this.uiEventsSocket.onmessage = function (msg) {
this._handleMessage(msg.data);
};
},
_handleMessage : function(data){
//the message should be in json format
//the next line fails if it is not
var command = JSON.parse(data);
//here is handling the request to show prompt
if (command.CommandType == 'yesNo') {
var message = command.Message;
var result = confirm(message);
//not sure that bool value will be successfully converted
this.uiEventsSocket.send(result ? "true" : "false");
}
}
}
And init it from ready or load event:
window.onload = function() { $app.initUIEventsConnection(yourUrl); }
Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).
Server side.
Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.
Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.
The following class will be used to operate commands:
public class UICommand
{
public string CommandType { get; set; }
public string Message { get; set; }
public Type ReturnType { get; set; }
}
And a full code of singleton for handling sockets
public class WebSocketsSingleton
{
private static WebSocketsSingleton _instance = null;
//here stored web sockets groupped by user
//you could use user Id or another marker to exactly determine the user
private Dictionary<string, List<WebSocket>> _connectedSockets;
//for a thread-safety usage
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
public static WebSocketsSingleton Instance {
get {
if (this._instance == null)
{
this._instance = new WebSocketsSingleton();
}
return this._instance;
}
}
private WebSocketsSingleton()
{
this._connectedSockets = new Dictionary<string, List<WebSocket>>();
}
/// <summary>
/// Adds a socket into the required collection
/// </summary>
public void AddSocket(string userName, WebSocket ws)
{
if (!this._connectedSockets.ContainsKey(userName))
{
Locker.EnterWriteLock();
try
{
this._connectedSockets.Add(userName, new List<WebSocket>());
}
finally
{
Locker.ExitWriteLock();
}
}
Locker.EnterWriteLock();
try
{
this._connectedSockets[userName].Add(ws);
}
finally
{
Locker.ExitWriteLock();
}
}
/// <summary>
/// Sends a UI command to required user
/// </summary>
public async Task<string> SendAsync(string userName, UICommand command)
{
if (this._connectedSockets.ContainsKey(userName))
{
var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));
foreach(var item in this._connectedSockets[userName])
{
try
{
await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (ObjectDisposedException)
{
//socket removed from front end side
}
}
var buffer = new ArraySegment<byte>(new byte[1024]);
var token = CancellationToken.None;
foreach(var item in this._connectedSockets[userName])
{
await Task.Run(async () => {
var tempResult = await item.ReceiveAsync(buffer, token);
//result received
token = new CancellationToken(true);
});
}
var resultStr = Encoding.Utf8.GetString(buffer.Array);
if (command.ReturnType == typeof(bool))
{
return resultStr.ToLower() == "true";
}
//other methods to convert result into required type
return resultStr;
}
return null;
}
}
Explanation:
on establishing connection from web socket it will be added with
AddSocket method
on sending request to show a message, the required command will be passed into SendAsync method
the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
after the command sent, application will wait for respond from front end side
the result will be converted to required type and sent back to your IQuestionBox
In the web socket handling your should add some kind of the following code:
app.Use(async (http, next) =>
{
if (http.WebSockets.IsWebSocketRequest)
{
var webSocket = await http.WebSockets.AcceptWebSocketAsync();
var userName = HttpContext.Current.User.Identity.Name;
WebSocketsSingleton.Instance.AddSocket(userName, webSocket);
while(webSocket.State == WebSocketState.Open)
{
//waiting till it is not closed
}
//removing this web socket from the collection
}
});
And your method implementation of ShowYesNoQuestionBox should be kind of following:
public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
var command = new UICommand
{
CommandType = "yesNo",
Message = text,
ReturnType = typeof(bool)
};
return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}
Note that there should be added userName to prevent sending the same message to all of the connected users.
WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.
I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.
It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.
you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.
To clarify ...
so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.
You can either ...
build the process in the database
build it in session on the server
build it on the client as a js object
then do a post for execution ofthe constructed process.
think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.
In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example
public class MController : Controller {
public ActionResult doWork(requirement IQuestionBox)
{
// model is already modelBound/IOC resolved
return PartialView("_doWork", requirement );
}
}
//scripts
$(function(){
$.ajax({
url:"/m/doWork",
type:"get",
success:function(data){
$modal.html(data); // bind to modal
}
});
});
Apologies for not fully understanding the question.
hope this helps!
I am developing an application in C# using HttpClient. My code is flowing through a lot of functions and then finally it does a PostAsyc.
What I want to do is that I want to have a EventHandler which is called when PostAsyc is done. and in that event handler I want to capture and print everything which the client has sent to the server .
Is this possible in .NET HTTPClient?
public void PostData(string data, Action<string> callback)
{
var client = new HttpClient();
var task = client.PostAsync("uri", new StringContent(data));
task.ContinueWith((t) =>
{
t.Result.Content.ReadAsStringAsync().ContinueWith((trep) =>
{
string response = trep.Result;
callback(response);
});
});
}
Instead of using Action<string> callback you can define an event delegate and use that also, this gives you more flexibility of attaching multiple receivers.
public class PostEventArgs : EventArgs { public string Data { get; set; } }
public event EventHandler<PostEventArgs> postDone;
public void PostData(string data)
{
var client = new HttpClient();
var task = client.PostAsync("uri", new StringContent(data));
task.ContinueWith((t) =>
{
t.Result.Content.ReadAsStringAsync().ContinueWith((trep) =>
{
string response = trep.Result;
if (postDone != null)
postDone(this, new PostEventArgs() { Data = response });
});
});
}
Usage:
First Case
serviceObj.PostData("some data", (response)=> { Console.WriteLine(response); });
Second case
serviceObj.postDone += (obj,response)=>{ Console.WriteLine(response); }; // register only once
serviceObj.PostData("some data");
Updated with Task.ContinueWith.
Create a message handler class like this,
public class LoggingMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (request.Method == HttpMethod.Post)
{
// Log whatever you want here
Console.WriteLine(request.ToString());
Console.WriteLine(response.ToString());
}
return response;
}
}
and then create your HttpClient with the message handler as part of the request/response pipeline
var client = new HttpClient(new LoggingMessageHandler() {InnerHandler = new HttpClientHandler()});
client.PostAsync(...) // Whatever
Any request you make from this point on will pass through the LoggingRequestHandler.
By taking this approach you do not need to wrap the HttpClient object and it is also easy to retrofit into existing code.
Because Post requests to APIs need to run asynchronously on windows phone, I am struggling to create a lean easy to use library to interact with an API.
The issue is that people using the library will always need to supply a callback function.
Let's take a look at some pseudo code:
PostRequest Class to help me with POST requests:
class PostRequest
{
private Action<MemoryStream> Callback;
public PostRequest(string urlPath, string data, Action<MemoryStream> callback)
{
Callback = callback;
// Form the URI
UriBuilder fullUri = new UriBuilder(urlPath);
if (!string.IsNullOrEmpty(data))
fullUri.Query = data;
// Initialize a new WebRequest
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fullUri.Uri);
request.Method = "POST";
// Set up the state object for the async request
DataUpdateState dataState = new DataUpdateState();
dataState.AsyncRequest = request;
// Start the asynchronous request
request.BeginGetResponse(new AsyncCallback(HandleResponse),
dataState);
}
private void HandleResponse(IAsyncResult asyncResult)
{
// Get the state information
DataUpdateState dataState = (DataUpdateState)asyncResult.AsyncState;
HttpWebRequest dataRequest = (HttpWebRequest)dataState.AsyncRequest;
// End the async request
dataState.AsyncResponse = (HttpWebResponse)dataRequest.EndGetResponse(asyncResult);
if (dataState.AsyncResponse.StatusCode.ToString() == "OK")
{
// Create a stream from the response
Stream response = dataState.AsyncResponse.GetResponseStream();
TextReader textReader = new StreamReader(response, true);
string jsonString = textReader.ReadToEnd();
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
// Send the stream through to the callback function
Callback(stream);
}
}
}
public class DataUpdateState
{
public HttpWebRequest AsyncRequest { get; set; }
public HttpWebResponse AsyncResponse { get; set; }
}
The API Access Object classes:
class APIAuthenticationCredentials
{
public String Username { get; set; }
public String Password { get; set; }
}
class APIAO
{
private String AuthUrl = "http://api.example.com/";
public static Auth Auth = new Auth();
//...
public static void Authenticate( String data, APIAuthenticationCredentials credentials, Action<MemoryStream> callback )
{
PostRequest request = new PostRequest(AuthURL, data, callback);
}
//...
}
You will notice I have to pass a callback function all the way through this so that once the data is returned by the HandleResponse method in my PostRequest class, the data is forwarded onto some controller that makes the screen do something with the data. At the moment, it's not ultra horrid to use:
private void DisplayData(MemoryStream stream)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Auth));
APIAO.Auth = (Auth)serializer.ReadObject(stream);
}
//...
APIAuthenticationCredentials credentials = new APIAuthenticationCredentials {
Username = "whatever",
Password = "whatever"
}
APIAO.Authenticate( credentials, DisplayData );
//...
The problem is I want to create some kind of repository style pattern... Let's say the API returned different json models, one call returned an array of products... the problem is that I want to create one lovely repository call eg:
IProductRepository productRepository = new ProductRepository();
productRepository.GetAll();
But I've gotta put some GOSH DARN callback function in it too and that means every repository method of any object type returned by the API is going to have this MemoryStream callback... and if I ever want to change that functionality, I've gotta update that stuff everywhere yo. :(
Has anyone seen a better way of doing this crap.
This is starting to become far too complex
--crying
A simpler answer using newer language constructs would be:
public static Task<string> GetData(string url, string data)
{
UriBuilder fullUri = new UriBuilder(url);
if (!string.IsNullOrEmpty(data))
fullUri.Query = data;
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;//TODO update as needed
return client.DownloadStringTaskAsync(fullUri.Uri);
}
In a 4.0 project you can use a TaskCompletionSource to translate a non-Task asynchronous model into a Task:
public static Task<string> GetData2(string url, string data)
{
UriBuilder fullUri = new UriBuilder(url);
if (!string.IsNullOrEmpty(data))
fullUri.Query = data;
WebClient client = new WebClient();
client.Credentials = CredentialCache.DefaultCredentials;//TODO update as needed
var tcs = new TaskCompletionSource<string>();
client.DownloadStringCompleted += (s, args) =>
{
if (args.Error != null)
tcs.TrySetException(args.Error);
else if (args.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(args.Result);
};
client.DownloadStringAsync(fullUri.Uri);
return tcs.Task;
}
The caller now has a Task<string> that represents the results of this asynchronous operation. They can wait on it synchronously and get the result using the Result property, they can add a callback that will execute when the operation finishes using ContinueWith, or they can await the task in an async method which, under the hood, will wire up the remainder of that method as a continuation of that task, but without creating a new method or even a new scope, i.e.
public static async Task Foo()
{
string result = await GetData("http://google.com", "");
Console.WriteLine(result);
}
This will start the asynchronous task, add a callback (or continuation) to that task so that when it runs it will continue executing code where it left off, at which point it will then write the results to the console and mark the Task that this method returns as completed, so that any continuations to this method will then execute (allowing for composition of async methods).
I am new writer to SO, pls bear with me.
I have a WCF service with a duplex service contract. This service contract has an operation contact that suppose to do long data processing. I am constrained to limit the number of concurrent data processing to let's say max 3. My problem is that after the data processing I need to get back to the same service instance context so I call back my initiator endpoint passing the data processing result. I need to mention that due to various reasons I am constrained to TPL dataflows and WCF duplex.
Here is a demo to what I wrote so far
In a console library I simulate WCF calls
class Program
{
static void Main(string[] args)
{
// simulate service calls
Enumerable.Range(0, 5).ToList().ForEach(x =>
{
new System.Threading.Thread(new ThreadStart(async () =>
{
var service = new Service();
await service.Inc(x);
})).Start();
});
}
}
Here is what suppose to be the WCF service
// service contract
public class Service
{
static TransformBlock<Message<int>, Message<int>> transformBlock;
static Service()
{
transformBlock = new TransformBlock<Message<int>, Message<int>>(x => Inc(x), new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 3
});
}
static Message<int> Inc(Message<int> input)
{
System.Threading.Thread.Sleep(100);
return new Message<int> { Token = input.Token, Data = input.Data + 1 };
}
// operation contract
public async Task Inc(int id)
{
var token = Guid.NewGuid().ToString();
transformBlock.Post(new Message<int> { Token = token, Data = id });
while (await transformBlock.OutputAvailableAsync())
{
Message<int> message;
if (transformBlock.TryReceive(m => m.Token == token, out message))
{
// do further processing using initiator service instance members
// something like Callback.IncResult(m.Data);
break;
}
}
}
}
public class Message<T>
{
public string Token { get; set; }
public T Data { get; set; }
}
The operation contract is not really necessary to be async, but I needed the OutputAvailableAsync notification.
Is this a good approach or is there a better solution for my scenario?
Thanks in advance.
First, I think you shouldn't use the token the way you do. Unique identifiers are useful when communicating between processes. But when you're inside a single process, just use reference equality.
To actually answer your question, I think the (kind of) busy loop is not a good idea.
A simpler solution for asynchronous throttling would be to use SemaphoreSlim. Something like:
static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(3);
// operation contract
public async Task Inc(int id)
{
await Semaphore.WaitAsync();
try
{
Thread.Sleep(100);
var result = id + 1;
// do further processing using initiator service instance members
// something like Callback.IncResult(result);
}
finally
{
Semaphore.Release();
}
}
If you really want to (or have to?) use dataflow, you can use TaskCompletionSource for synchronization between the operation and the block. The operation method would wait on the Task of the TaskCompletionSource and the block would set it when it finished computation for that message:
private static readonly ActionBlock<Message<int>> Block =
new ActionBlock<Message<int>>(
x => Inc(x),
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 3
});
static void Inc(Message<int> input)
{
Thread.Sleep(100);
input.TCS.SetResult(input.Data + 1);
}
// operation contract
public async Task Inc(int id)
{
var tcs = new TaskCompletionSource<int>();
Block.Post(new Message<int> { TCS = tcs, Data = id });
int result = await tcs.Task;
// do further processing using initiator service instance members
// something like Callback.IncResult(result);
}