Broadcast in WebSocket chat - c#

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:

Related

.NET MAUI WebSocket doesn't work on android 5 and 8

On my android 11 device my websocket works fine, but when i try to run it on older phone with android 5 or 8, it doesnt work and it only says Unable to connect to the remote server
Is there some way to make it works ?
I am using System.Net.WebSockets;
async Task StartWebSocketAsync()
{
await ws.ConnectAsync(new Uri("wss://url?token=" + Token), t);
await Task.Factory.StartNew(async () =>
{
while (true)
{
await ReadMessage();
}
}, t, TaskCreationOptions.LongRunning, TaskScheduler.Default);
SendMessageAsync(msg);
}
}
async Task ReadMessage()
{
WebSocketReceiveResult result;
var message = new ArraySegment<byte>(new byte[4096]);
do
{
result = await ws.ReceiveAsync(message, t);
if (result.MessageType != WebSocketMessageType.Text)
break;
var messageBytes = message.Skip(message.Offset).Take(result.Count).ToArray();
string receivedMessage = Encoding.UTF8.GetString(messageBytes);
JObject jsonObject = JObject.Parse(receivedMessage);
Status = (string)jsonObject["data"]["state"][0][1];
}
while (!result.EndOfMessage);
}
async void SendMessageAsync(string message)
{
var byteMessage = Encoding.UTF8.GetBytes(message);
var segmnet = new ArraySegment<byte>(byteMessage);
await ws.SendAsync(segmnet, WebSocketMessageType.Text, true, t);
}

gRPC C#: how to cancel upload correctly

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.

Twitter stream with web socket MatchingTweetReceived not triggered in twitterinvl

I am creating a twiiter stremaing MVC controller. The controller method is implemented as a web socket.
Ref: Connecting with websockets to windows azure using MVC controllerenter link description here
I want to display the twitter stream in the UI. I am using twitterinvl. My code is included below. The stream event MatchingTweetReceived are not getting triggered from the controller method.
Any help on this is highly appreciated.
Controller action method.
public ActionResult About()
{
if (ControllerContext.HttpContext.IsWebSocketRequest)
{
ControllerContext.HttpContext.AcceptWebSocketRequest(DoTalking);
}
return new HttpStatusCodeResult(HttpStatusCode.SwitchingProtocols);
}
Rest of the code
public async Task DoTalking(AspNetWebSocketContext context)
{
try
{
WebSocket socket = context.WebSocket;
while (true)
{
var buffer = new ArraySegment<byte>(new byte[1024]);
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);
if (socket.State == WebSocketState.Open)
{
string userMessage = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
string outmessage = "Empty";
//userMessage = "You sent: " + userMessage + " at " + DateTime.Now.ToLongTimeString();
Auth.SetUserCredentials("<<credential>>", "<<credential>>", "<<credential>>", "<<credential>>");
var stream = Stream.CreateFilteredStream();
stream.AddTrack("enterhash");
stream.MatchingTweetReceived += (sender, theTweet) =>
{
outmessage = theTweet.Tweet.CreatedBy.ToString() + theTweet.Tweet.CreatedAt + theTweet.Tweet;
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMessage));
//Console.WriteLine($"Tweet: {theTweet.Tweet.CreatedAt} {theTweet.Tweet}");
socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
};
stream.StartStreamMatchingAllConditions();
Trace.WriteLine(outmessage);
//await socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
else
{
break;
}
}
}
catch (Exception ex)
{
}
}

Upload new file to onedrive using microsoft graph c# asp.net

Trying to upload a file to onedrive that does not already exist. I have managed to get it to update an existing file. But can't seem to figure out how to create a brand new file. I have done this useing the Microsoft.Graph library.
Here is the code that works to update an existing file:
public async Task<ActionResult> OneDriveUpload()
{
string token = await GetAccessToken();
if (string.IsNullOrEmpty(token))
{
// If there's no token in the session, redirect to Home
return Redirect("/");
}
GraphServiceClient client = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return Task.FromResult(0);
}));
try
{
string path = #"C:/Users/user/Desktop/testUpload.xlsx";
byte[] data = System.IO.File.ReadAllBytes(path);
Stream stream = new MemoryStream(data);
// Line that updates the existing file
await client.Me.Drive.Items["55BBAC51A4E4017D!104"].Content.Request().PutAsync<DriveItem>(stream);
return View("Index");
}
catch (ServiceException ex)
{
return RedirectToAction("Error", "Home", new { message = "ERROR retrieving messages", debug = ex.Message });
}
}
I'd suggest using the ChunkedUploadProvider utility that is included in the SDK. Aside from being a little easier to work with, it will allow you to upload files of any side rather than being limited to files under 4MB.
You can find a sample of how to use ChunkedUploadProvider in the OneDriveUploadLargeFile unit test.
To answer your direct question, uploading works the same for both replacing and creating files. You do however need to specify the file name rather than just an existing Item number:
await graphClient.Me
.Drive
.Root
.ItemWithPath("fileName")
.Content
.Request()
.PutAsync<DriveItem>(stream);
This code will help you all to upload small and large files using Microsoft graph Api Sdk in ASP .NEt Core
Upload or replace the contents of a DriveItem
*Controller code : -*
[BindProperty]
public IFormFile UploadedFile { get; set; }
public IDriveItemChildrenCollectionPage Files { get; private set; }
public FilesController(ILogger<FilesModel> logger, GraphFilesClient graphFilesClient, GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
_graphFilesClient = graphFilesClient;
_logger = logger;
_graphServiceClient = graphServiceClient;
_tokenAcquisition = tokenAcquisition;
}
[EnableCors]
[HttpPost]
[Route("upload-file")]
[RequestFormLimits(MultipartBodyLengthLimit = 100000000)]
[RequestSizeLimit(100000000)]
public async Task<IActionResult> uploadFiles(string itemId, string folderName, [FromHeader] string accessToken)
{
_logger.LogInformation("into controller");
if (UploadedFile == null || UploadedFile.Length == 0)
{
return BadRequest();
}
_logger.LogInformation($"Uploading {UploadedFile.FileName}.");
var filePath = Path.Combine(System.IO.Directory.GetCurrentDirectory(), UploadedFile.FileName);
_logger.LogInformation($"Uploaded file {filePath}");
using (var stream = new MemoryStream())
{
UploadedFile.CopyTo(stream);
var bytes = stream.ToArray();
_logger.LogInformation($"Stream {stream}.");
stream.Flush();
await _graphFilesClient.UploadFile(
UploadedFile.FileName, new MemoryStream(bytes), itemId, folderName, accessToken);
}
return Ok("Upload Successful!");
}
*Service code :-*
[EnableCors]
public async Task UploadFile(string fileName, Stream stream,string itemId,string folderName,string accessToken)
{
GraphClients graphClients = new GraphClients(accessToken);
GraphServiceClient _graphServiceClient = graphClients.getGraphClient();
_logger.LogInformation("Into Service");
var filePath = Path.Combine(System.IO.Directory.GetCurrentDirectory(),fileName);
_logger.LogInformation($"filepath : {filePath}");
Console.WriteLine("Uploading file: " + fileName);
var size = stream.Length / 1000;
_logger.LogInformation($"Stream size: {size} KB");
if (size/1000 > 4)
{
// Allows slices of a large file to be uploaded
// Optional but supports progress and resume capabilities if needed
await UploadLargeFile(filePath, stream,accessToken);
}
else
{
try
{
_logger.LogInformation("Try block");
String test = folderName + "/" + fileName;
// Uploads entire file all at once. No support for reporting progress.
// for getting your sharepoint site open graph explorer > sharepoint sites > get my organization's default sharepoint site.
var driveItem = await _graphServiceClient
.Sites["Your share point site"]
.Drive
.Root.ItemWithPath(test)
.Content
.Request()
.PutAsync<DriveItem>(stream);
_logger.LogInformation($"Upload complete: {driveItem.Name}");
}
catch (ServiceException ex)
{
_logger.LogError($"Error uploading: {ex.ToString()}");
throw;
}
}
}
private async Task UploadLargeFile(string itemPath, Stream stream,string accessToken)
{
GraphClients graphClients = new GraphClients(accessToken);
GraphServiceClient _graphServiceClient = graphClients.getGraphClient();
// Allows "slices" of a file to be uploaded.
// This technique provides a way to capture the progress of the upload
// and makes it possible to resume an upload using fileUploadTask.ResumeAsync(progress);
// Based on https://docs.microsoft.com/en-us/graph/sdks/large-file-upload
// Use uploadable properties to specify the conflict behavior (replace in this case).
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "#microsoft.graph.conflictBehavior", "replace" }
}
};
// Create the upload session
var uploadSession = await _graphServiceClient.Me.Drive.Root
.ItemWithPath(itemPath)
.CreateUploadSession(uploadProps)
.Request()
.PostAsync();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<DriveItem>(uploadSession, stream, maxSliceSize);
// Create a callback that is invoked after
// each slice is uploaded
IProgress<long> progress = new Progress<long>(prog =>
{
_logger.LogInformation($"Uploaded {prog} bytes of {stream.Length} bytes");
});
try
{
// Upload the file
var uploadResult = await fileUploadTask.UploadAsync(progress);
if (uploadResult.UploadSucceeded)
{
_logger.LogInformation($"Upload complete, item ID: {uploadResult.ItemResponse.Id}");
}
else
{
_logger.LogInformation("Upload failed");
}
}
catch (ServiceException ex)
{
_logger.LogError($"Error uploading: {ex.ToString()}");
throw;
}
}

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.

Categories

Resources