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.
Related
I want to call one 3rd party Rest API from the asp.net core application with the pooling mechanism.
Here in the below sample code, I want to call _client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or my pooling call limit gets exceeded.
public async Task<Result> TrackDocumentProcess(RequestModel model)
{
var response = await _client.DocumentProcess(model);
if(response.IsSuccess)
{
if(response.Value.Status != "Success")
//Call the _client.DocumentProcess(model) API after 10 seconds.
else
return Result.Success();
}
}
public async Task<Result<ResponseModel>> DocumentProcess(RequestModel model)
{
var authToken = GetToken();
using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(new HttpMethod("POST"), $"https://apiurl.com/api/TrackDocumentProcessStatus/{model.DocumentId}");
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {authToken.Result.Value.Token}");
var response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(content))
{
var userRequestResponse = JsonSerializer.Deserialize<ResponseModel>(content);
return Result.Success(userRequestResponse);
}
return Result.Failure<ResponseModel>("Error...");
}
Can you please suggest any best practices to solve this problem?
Thanks.
Here in the below sample code, I want to call
_client.DocumentProcess(model) API with some pooling mechanism after every 10 seconds for five times until I get the Result as "Success" or
my pooling call limit gets exceeded.
Well based on your scenario, we can consider two standard implementaion. First one we can implement using PeriodicTimer which provides a periodic timer that would call as per the given time interval.
Another one, would be using BackgroundService worker process which would continue calling API unless the certain condition are meet.
Using PeriodicTimer:
public async Task<IActionResult> DocumentProcess()
{
var status = await TrackDocumentProcessStatus();
if (status == "Success")
{
return Ok(status);
}
else
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
continue;
}
}
return Ok();
}
Explanation:
As you can see, when DocumentProcess method would invocke, it will call the TrackDocumentProcessStatus method thus, the API and if API return pending it update the status and will call again within next 10 seconds and process would continue until it gets success status.
Method Would Invoked Every 10 Seconds:
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 3;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Output:
Note:
If you would like to know more details on it you could check our official document here
BackgroundService worker process:
It would be a individual background worker service which would continue running and check your status behind. You can achieve your requirement as following:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
var status = await TrackDocumentProcessStatus();
status = await TrackDocumentProcessStatus();
if (status == "Success")
{
break;
}
Console.WriteLine(status);
await Task.Delay(1000, stoppingToken);
}
}
public async Task<string> TrackDocumentProcessStatus()
{
var rquestObject = new StatusRequestModel();
rquestObject.RequestId = 1;
var data_ = JsonConvert.SerializeObject(rquestObject);
HttpClient _httpClient = new HttpClient();
var buffer_ = System.Text.Encoding.UTF8.GetBytes(data_);
var byteContent_ = new ByteArrayContent(buffer_);
byteContent_.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string _urls = "http://localhost:5094/api/Rest/CheckDocumentStatus";
var responses_ = await _httpClient.PostAsync(_urls, byteContent_);
if (responses_.StatusCode != HttpStatusCode.OK)
{
return "Pending";
}
string response = await responses_.Content.ReadAsStringAsync();
return response;
}
Note:
Both implementaion would call your API after a certain interval for checking status from another API. The difference of these implementation is background worker service would continue in background, no other action wwould required just like widows service, If you would like to know more details on background worker service you could check our official document here
i want to make WebSockets chat in asp.net core. I decided to make it with middleware. However there is one thing i've been thinking for a day - i don't know how to make correct broadcast.
The problem is that it doesn't show messages of other users, instead it just writes your message for user count times.
Here how it should be (if there are 2 users in 2 tabs of browser):
1: Hello 2
2: Hello 1
Instead of this i see only 2 same messages of 1 user and in other tab of browser i see nothing.
Here is middleware class:
public class WebSocketChatMiddleware
{
RequestDelegate _next;
ConcurrentDictionary<string, WebSocket> connections { get; set; }
public WebSocketChatMiddleware(RequestDelegate next)
{
_next = next;
connections = new ConcurrentDictionary<string, WebSocket>();
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await _next.Invoke(context);
return;
}
CancellationToken ct = context.RequestAborted;
WebSocket ws = await context.WebSockets.AcceptWebSocketAsync();
string wsID = Guid.NewGuid().ToString();
connections.TryAdd(wsID, ws);
while (true)
{
if (ct.IsCancellationRequested)
{
return;
}
string data = await ReadStringAsync(ws, ct);
if (string.IsNullOrEmpty(data))
{
if (ws.State != WebSocketState.Open)
{
break;
}
continue;
}
foreach (var item in connections) // not working broadcast
{
if (ws.State == WebSocketState.Open)
{
await SendStringAsync(ws, data, ct);
}
}
}
WebSocket dummy;
connections.TryRemove(wsID,out dummy);
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure,"UserDisconnected",ct);
ws.Dispose();
await _next(context);
}
async Task<string> ReadStringAsync(WebSocket ws, CancellationToken ct = default)
{
var buffer = new ArraySegment<byte>(new byte[1024 * 8]);
using (MemoryStream ms = new MemoryStream())
{
WebSocketReceiveResult receiveResult;
do
{
ct.ThrowIfCancellationRequested();
receiveResult = await ws.ReceiveAsync(buffer, ct);
ms.Write(buffer.Array, buffer.Offset, receiveResult.Count);
} while (!receiveResult.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin); // Changing stream position to cover whole message
if (receiveResult.MessageType != WebSocketMessageType.Text)
return null;
using (StreamReader reader = new StreamReader(ms, System.Text.Encoding.UTF8))
{
return await reader.ReadToEndAsync(); // decoding message
}
}
}
Task SendStringAsync(WebSocket ws, string data, CancellationToken ct = default)
{
var buffer = System.Text.Encoding.UTF8.GetBytes(data);
var segment = new ArraySegment<byte>(buffer);
return ws.SendAsync(segment, WebSocketMessageType.Text, true, ct);
}
Configure method in Startup.cs :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseWebSockets();
app.UseMiddleware<WebSocketChatMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
I will be veeeeery grateful for your help c:
Change the code like below:
foreach (var item in connections) // not working broadcast
{
//if (ws.State == WebSocketState.Open)
//{
// await SendStringAsync(ws, data, ct);
//}
if (item.Value.State != WebSocketState.Open)
{
continue;
}
await SendStringAsync(item.Value, data, ct);
}
Result:
I don't understand why the response comes only if I use CompleteAsync().
using var call = _apiClient.GetToken(headers: _threadContext.Metadata, deadline: DateTime.UtcNow.AddSeconds(5));
var keyReq = new GetTokenRequest()
{
Key = publicKey
};
var readTask = Task.Run(async () =>
{
await foreach(var message in call.ResponseStream.ReadAllAsync())
{
if (message.Challenge != null)
{
var challenge = message.Challenge.ToByteArray();
var signature = await VerifySignature(challenge);
var signReq = new GetTokenRequest
{
Signature = ByteString.CopyFrom(signature)
};
await call.RequestStream.WriteAsync(signReq);
await call.RequestStream.CompleteAsync();
}
else if (message.Token != null)
{
token = message.Token;
}
}
});
await call.RequestStream.WriteAsync(keyReq);
await readTask;
If I change the end with this, I receive messages but in the response the next WriteAsync fails because the stream is closed.
await call.RequestStream.WriteAsync(keyReq);
await call.RequestStream.CompleteAsync();
await readTask;
And if I doesn't complete the request, response message never comes.
Any idea ?
Note: the server is in go.
This code doesn't work with Grpc.Net.Client.Web only. With classic SocketHttpHandler it's ok. Problem is solved. thanks.
For uploading images from a client to the server I use chunking.
Here is the client code:
private async Task UploadPersonImage(int personId, string fileName, CancellationToken cancellationToken)
{
var stream = Client.UploadPersonImage();
PersonImageMessage personImageMessage = new PersonImageMessage();
personImageMessage.PersonId = personId;
personImageMessage.ImageType = ImageType.Jpg;
byte[] image = File.ReadAllBytes(fileName);
int imageOffset = 0;
byte[] imageChunk = new byte[imageChunkSize];
while (imageOffset < image.Length && !cancellationToken.IsCancellationRequested)
{
int length = Math.Min(imageChunkSize, image.Length - imageOffset);
Buffer.BlockCopy(image, imageOffset, imageChunk, 0, length);
imageOffset += length;
ByteString byteString = ByteString.CopyFrom(imageChunk);
personImageMessage.ImageChunk = byteString;
await stream.RequestStream.WriteAsync(personImageMessage).ConfigureAwait(false);
}
await stream.RequestStream.CompleteAsync().ConfigureAwait(false);
if (!cancellationToken.IsCancellationRequested)
{
var uploadPersonImageResult = await stream.ResponseAsync.ConfigureAwait(false);
// Process answer...
}
}
And this is the server code:
public override async Task<TransferStatusMessage> UploadPersonImage(
IAsyncStreamReader<PersonImageMessage> requestStream, ServerCallContext context)
{
TransferStatusMessage transferStatusMessage = new TransferStatusMessage();
transferStatusMessage.Status = TransferStatus.Success;
try
{
await Task.Run(
async () =>
{
CancellationToken cancellationToken = context.CancellationToken;
await using (Stream fs = File.OpenWrite(ImageFileName))
{
await foreach (PersonImageMessage personImageMessage in
requestStream.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
fs.Write(personImageMessage.ImageChunk.ToByteArray());
}
}
}).ConfigureAwait(false);
}
// Is thrown on cancellation -> ignore...
catch (OperationCanceledException)
{
transferStatusMessage.Status = TransferStatus.Cancelled;
}
catch (RpcException rpcEx)
{
if (rpcEx.StatusCode == StatusCode.Cancelled)
{
transferStatusMessage.Status = TransferStatus.Cancelled;
}
else
{
_logger.LogError($"Exception while processing image file '{ImageFileName}'. Exception: '{requestStream}'");
transferStatusMessage.Status = TransferStatus.Failure;
}
}
// Delete incomplete file
if (transferStatusMessage.Status != TransferStatus.Success)
{
File.Delete(ImageFileName);
}
return transferStatusMessage;
}
Everything works fine. I want to cancel the upload in between sending the chunks. Now, CompleteAsync() is called and the server thinks the data transfer ended successfully. I'm looking for a way to trigger the cancellation in the server (i.e. the CancellationToken in the ServerCallContext) via the client.
As a workaround I could add a flag to PersonImageMessage, something like 'upload_cancelled', to tell the server that the transfer is aborted. But there must be a built-in mechanism.
Does somebody know the trick?
You can pass cancellationToken to your Client stream RPC method. Then the server can receive it as context.cancellationToken.
var stream = Client.UploadPersonImage(cancellationToken: cancellationToken);
If the client cancels, stream.RequestStream.CompleteAsync() should not be called.
I have a functionality of search users. I have provided a textview and on that textview changed method I'm firing a method to get data from web server. But I'm facing problem when user types letter, because all the api hits done in async task. Service should be hit after 100 milli-sec of wait, means if user types a letter "a" then doesn't type for 100 milli-sec then We have to hit the service. But if user types "a" then "b" then "c", so one service should be hit for "abc", not for all.
I followed the official link, but it doesn't help me
https://msdn.microsoft.com/en-us/library/jj155759.aspx
So basically here is my code
textview.TextChange+= (sender,e) =>{
CancellationTokenSource cts = new CancellationTokenSource();
await Task.Delay(500);
// here some where I have to pass cancel token
var lst = await APIClient.Instance.GetUserSearch("/user/get?searchTerm=" + newText, "application/json",cts);
if (lst != null && lst.Count > 0){
lstSearch.AddRange(lst);
}
}
Here is my method to GetUser
public async Task<JResponse> GetUserSearch<JResponse>(string uri, string contentType,CancellationToken cts)
{
try
{
Console.Error.WriteLine("{0}", RestServiceBaseAddress + uri);
string url = string.Format("{0}{1}", RestServiceBaseAddress, uri);
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = contentType;
if (Utility.CurrentUser != null && !string.IsNullOrWhiteSpace(Utility.CurrentUser.AuthToken))
{
request.Headers.Add("api_key", Utility.CurrentUser.AuthToken);
}
request.Method = "POST";
var payload = body.ToString();
request.ContentLength = payload.Length;
byte[] byteArray = Encoding.UTF8.GetBytes(body.ToString());
request.ContentLength = byteArray.Length;
using (var stream = await request.GetRequestStreamAsync())
{
stream.Write(byteArray, 0, byteArray.Length);
stream.Close();
}
using (var webResponse = await request.GetResponseAsync())
{
var response = (HttpWebResponse)webResponse;
using (var reader1 = new StreamReader(response.GetResponseStream()))
{
Console.WriteLine("Finished : {0}", uri);
var responseStr = reader1.ReadToEnd();
var responseObj = JsonConvert.DeserializeObject<JResponse>(
responseStr,
new JsonSerializerSettings()
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
return responseObj;
}
}
}
catch (System.Exception ex)
{
Utility.ExceptionHandler("APIClient", "ProcessRequestAsync", ex);
}
return default(JResponse);
}
In your example, you are creating a CancellationTokenSource - you need to hold a reference to it, so that the next time the handler is invoked, the previous search can be cancelled. Here is an example console app that you should be able to run, but the important piece is in the handler.
private CancellationTokenSource _cts;
private async void TextChangedHandler(string text) // async void only for event handlers
{
try
{
_cts?.Cancel(); // cancel previous search
}
catch (ObjectDisposedException) // in case previous search completed
{
}
using (_cts = new CancellationTokenSource())
{
try
{
await Task.Delay(TimeSpan.FromSeconds(1), _cts.Token); // buffer
var users = await _userService.SearchUsersAsync(text, _cts.Token);
Console.WriteLine($"Got users with IDs: {string.Join(", ", users)}");
}
catch (TaskCanceledException) // if the operation is cancelled, do nothing
{
}
}
}
Be sure to pass the CancellationToken into all of the async methods, including those that perform the web request, this way you signal the cancellation right down to the lowest level.
Try to use timer. First time then you change text - you create it. Then you change text after that - you restart timer. If you don't change text for 700 milliseconds - timer will fire PerformeSearch method. Use Timeout.Infinite for timer period parameter to prevent it from restarting.
textview.TextChange += (sender,e) =>
{
if (_fieldChangeTimer == null)
_fieldChangeTimer = new Timer(delegate
{
PerformeSearch();
}, null, 700, Timeout.Infinite);
else
{
_fieldChangeTimer.Change(700, Timeout.Infinite);
}
};
Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource(); Example method
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}