How to send Parameter/Query in HubConnection SignalR Core - c#

I'm trying to add parameter into connection to signalr.
I'm using Builder to create my Client connection and start it:
var connection = new HubConnectionBuilder()
.WithUrl("http://10.0.2.162:5002/connection")
.WithConsoleLogger()
.WithMessagePackProtocol()
.WithTransport(TransportType.WebSockets)
.Build();
await connection.StartAsync();
I want to send a simple parameter in this connection:
Something Like:
"Token": "123"
In my server side i think i can take this parameter from HttpContext:
public override Task OnConnectedAsync()
{
var httpContext = Context.Connection.GetHttpContext();
var token = httpContext.Request.Query["Token"];
return base.OnConnectedAsync();
}
Any idea of how to send this parameter?
Thanks.

I have found how to do this after much research:
On my build i just send the token from url connection.
Like this:
var connection = new HubConnectionBuilder()
.WithUrl($"http://10.0.2.162:5002/connection?token={token}")
.WithConsoleLogger()
.WithMessagePackProtocol()
.WithTransport(TransportType.WebSockets)
.Build();

Related

azure functions server (isolated-process): Invoking SignalR group functions from client

I am trying to add a SignalR client to specific SignalR group on an azure functions server (isolated-process). The server side code that I am trying to invoke is the following:
[Function("SendToGroup")]
[SignalROutput(HubName = "serverless", ConnectionStringSetting = "AzureSignalRConnectionString")]
public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
using var bodyReader = new StreamReader(req.Body);
return new SignalRMessageAction("newMessage")
{
Arguments = new[] { bodyReader.ReadToEnd() },
GroupName = "groupToSend"
};
}
I have tried invoking the above from the client side in C# with the following code to no avail:
HubConnection _connection = new HubConnectionBuilder().WithUrl("http://localhost:7071/api").Build();
Dispatcher.Dispatch(async () => await _connection.StartAsync());
// This does not work
await HubConnectionExtensions.InvokeAsync(_connection, "SendToGroup");
In my searches, I have not found a single C# client code sample that shows how to connect to and remove oneself from signalR hub groups. I would really appreciate being pointed in the right direction.

signalR with multiple client connections

I have written an Application where I am using SignalR. I am sending connectionId from Client to Server(controller).
Everything is working fine with single browser (request will sent to server with connectionId="conn_1") and signalR is sending response to only conn_1, but when i open new browser and send a request from that client the previous connection gets disposed. Which means only one connection with particular connectionId remains alive.
Is there any way SignalR can not dispose and send response to both with data they want?
I am new to SignalR and would really appropriate any help or guidance.
Angular SignalRService to start connection with server
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.paymentDraftHubUrl)
.build();
return this.hubConnection
.start()
.then(() => this.hubConnectionStatus = 'Connection started')
.catch(err => (this.hubConnectionStatus = 'Error while starting connection: ' + err));
}
sending connectionId from client component to Api
this.signalRService.startConnection().then((connection) => {
this.connectionId = connection.toString();
//Calling Api
this.getAllTransactionException(
this.connectionId,
this.pageNumber,
this.pageSize
}
MyHub class in C#
public class PaymentDraftServiceHub : Hub, IPaymentDraftHub
{}
Controller for API
using timer to keep calling repository for new data,
[HttpGet]
[Route("GetCsrTranactions")]
public IActionResult GetCsrTranactions([FromQuery] TransactionExceptionDataRequest queryParams)
{
TimeManager.Dispose();
var timerManager = new TimeManager(async () =>
await _paymentDraftHub.Clients.Clients.Client(queryParams.ConnectionId).SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetCsrTranactionsAsync(queryParams)));
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
Client can have multiple connections with multiple connection IDs if client connect from multiple browser windows or tabs.
According to the code you provided, we can find that you just pass connection ID of SignalR client within current active browser tab/window to your controller, and in your controller action, you use this code snippet .Client(queryParams.ConnectionId).SendAsync() to send message to a specific client, so other browser windows or tabs would not receive the message.
If you'd like to send message(s) to a client with multiple connections, you need to map SignalR users to connection Ids and retain information about users-to-connectionIds mapping, then you can get all connectionIds of a client and send messages to that client with with multiple connectionIds, like below.
//code logic here
//to get all connectinIds of a client/user
//from user-to-connectionIds mapping table
await _paymentDraftHub.Clients.Clients(connectionIds_here).SendAsync("method_here",args_here);

.Net Core SignalR: Send to Users except caller from IHubContext (injected into controller)

in order to identify the current user only when working with an injected IHubContext in a controller, I am storing a group with the user id.
However, I'm struggling to send to everyone else since I cannot figure out a way to find out which connection ID to exclude.
My Hub
public override Task OnConnectedAsync()
{
Groups.AddAsync(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnectedAsync();
}
In my controller method, I can invoke methods for that user:
await _signalRHub.Clients.Group(User.Identity.Name).InvokeAsync("Send", User.Identity.Name + ": Message for you");
IHubContext.Clients.AllExcept requires a list of connection IDs. How can I obtain the connection ID for the identified user in order to only notify others?
As suggested by #Pawel, I am now de-duping on the client, which works (well, as long as all your clients are authenticated).
private async Task Identification() => await Clients.Group(Context.User.Identity.Name).InvokeAsync("Identification", Context.User.Identity.Name);
public override async Task OnConnectedAsync()
{
await Groups.AddAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnConnectedAsync();
await Identification();
}
The JS to go along with it (abbreviated):
var connection = new signalR.HubConnection("/theHub");
var myIdentification;
connection.on("Identification", userId => {
myIdentification = userId;
});
Now you can test for callerIdentification == myIdentification in addtional methods like connection.on("something", callerIdentification)
#Tester's comment makes me hopeful there'll be a better way at some point when sending through IHubContext.
In SignalR core, the connectionId is stored in the signalR connection. Assuming you have a signalrR connection defined as follows
signalrConnection = new HubConnectionBuilder()
.withUrl('/api/apphub')
...
.build();
Whenever you make a fetch request, add the signalR connectionId as a header. Eg.
response = await fetch(url, {
...
headers: {
'x-signalr-connection': signalrConnection.connectionId,
},
});
Then in your controller or wherever you have access to a httpContextAccessor, you can use the following to exclude the connection referenced in the header:
public async Task NotifyUnitSubscribersExceptCaller()
{
//Grab callers connectionId
var connectionId = _httpContextAccessor.HttpContext?.Request.Headers["x-signalr-connection"] ?? "";
await _hub.Clients.GroupExcept("myGroup", connectionId).SendCoreAsync("Sample", new object[] { "Hello World!" });
}

WebTokenRequest and OneDriveClient

I'm trying to implement Onedrive client login by using Connect to identity providers with Web Account Manager
With this method finally I get a token using this code
private static async Task<string> RequestTokenAndSaveAccount(WebAccountProvider Provider, String Scope, String ClientID)
{
try
{
WebTokenRequest webTokenRequest = new WebTokenRequest(Provider, "wl.signin onedrive.appfolder onedrive.readwrite", ClientID);
WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);
if (webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success)
{
App.settings.onedriveStoredAccountKey = webTokenRequestResult.ResponseData[0].WebAccount.Id;
return webTokenRequestResult.ResponseData[0].Token;
}
return "";
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return "";
}
}
But I can't use the returned token to create a OnedriveClient because I need a MsaAuthenticationProvider to create the client and it creates its own token ignoring the one coming from the WebTokenRequest, and it doesn't have any method to take the prior token.
There is a way to create a OneDriveClient without going to REST Onedrive API?
Thank you
Edit:
As there are (at this time) two main versions of OneDriveSDK and those are different from each other, there are two ways to achieve this.
OneDrive.SDK 1.x
As #Brad said, an IAuthenticationProvider is needed to create the OneDriveClient.
I got the solution from https://github.com/ginach/Simple-IAuthenticationProvider-sample-for-OneDrive-SDK.
I took the SimpleAuthenticationProvider into my code, and then created the client like this
var client = new OneDriveClient(
new AppConfig(),
/* credentialCache */ null,
new Microsoft.OneDrive.Sdk.HttpProvider(),
new ServiceInfoProvider(new SimpleAuthenticationProvider { CurrentAccountSession = new Microsoft.OneDrive.Sdk.AccountSession { accessToken = AccessToken } }),
ClientType.Consumer);
client.BaseUrl = "https://api.onedrive.com/v1.0";
await client.AuthenticateAsync();
Where the accessToken is taken from the RequestTokenAndSaveAccount method.
OneDrive.SDK 2.x
For this case, the answer given by #dabox is the right solution.
Appending to Brad's answer, you can create a new AuthenticationProivder implements the IAuthenticationProivder interface in the package Microsoft.Graph.Core. And there also is a DelegateAuthenticationProvider in package Microsoft.Graph.Core which provides a Delegate interface for you. An example looks like:
OneDriveClient oneDriveClient = new OneDriveClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetAccessTokenSomeWhereAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return oneDriveClient ;
Modified based on the Microsoft Graph's asp .net example: https://github.com/microsoftgraph/aspnet-connect-sample/blob/master/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Microsoft%20Graph%20SDK%20ASPNET%20Connect/Helpers/SDKHelper.cs#L18
OneDriveClient only requires an IAuthenticationProvider, which is a pretty simplistic interface. You can create your own and implement AuthenticateRequestAsync such that it calls your RequestTokenAndSaveAccount and then adds the bearer token to the request.

SignalR 2.1.0: The connection has not been established

I have a ASP.NET Web Application with a simple HTML page and some JavaScript to communicate via SignalR. That works fine.
Now I'm trying to call a method on the Hub from another project (in the same solution) and by using the .NET Signalr Client Api:
var connection = new HubConnection("http://localhost:32986/");
var hub = connection.CreateHubProxy("MessageHub");
connection.Start();
hub.Invoke("SendMessage", "", "");
The last line causes InvalidOperationException: The connection has not been established. But I am able to connect to the hub from my JavaScript code.
How can I connect to the Hub by using C# code?
UPDATE
The moment after writing this post, I tried to add .Wait() and it worked!
So this will do:
var connection = new HubConnection("http://localhost:32986/");
var hub = connection.CreateHubProxy("MessageHub");
connection.Start().Wait();
hub.Invoke("SendMessage", "", "");
HubConnection.Start returns a Task that needs to complete before you can invoke a method.
The two ways to do this are to use await if you are in an async method, or to use Task.Wait() if you are in a non-async method:
public async Task StartConnection()
{
var connection = new HubConnection("http://localhost:32986/");
var hub = connection.CreateHubProxy("MessageHub");
await connection.Start();
await hub.Invoke("SendMessage", "", "");
// ...
}
// or
public void StartConnection()
{
var connection = new HubConnection("http://localhost:32986/");
var hub = connection.CreateHubProxy("MessageHub");
connection.Start().Wait();
hub.Invoke("SendMessage", "", "").Wait();
// ...
}
The "How to establish a connection" section of the ASP.NET SignalR Hubs API Guide for the .NET client. goes into even more detail.

Categories

Resources