I'm looking at incorporating Azure SignalR functionality into my .net core Blazor web application. To this end i've been following this tutorial - Azure Signalr Serverless. This is working fine - i have a project running the Azure functions app and can start up two browsers and have a chat session. What i'm trying to do is add the ability to receive these message notifications from the Azure signalR hub that's been configured into my Blazor app. I've added the following code in Index.razor.cs that mimics the javascript code in the example client:
public class IndexComponent : ComponentBase
{
private HubConnection _connection;
public string Message;
protected override Task OnInitializedAsync()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:7071/api")
.Build();
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Message = $"Got message {message} from user {user}";
this.StateHasChanged();
});
_connection.StartAsync();
return base.OnInitializedAsync();
}
}
The example javascript code btw is:
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${apiBaseUrl}/api`)
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('newMessage', newMessage);
connection.onclose(() => console.log('disconnected'));
console.log('connecting...');
connection.start()
.then(() => data.ready = true)
.catch(console.error);
So the problem is that my Blazor app never receives any message notifications sent from the javascript chat clients (so the _connection.On handler is never hit). What am i missing in my Blazor code ?
Ok so this is what i needed to do to get it to work in my Blazor app:
_connection.On<object>("newMessage", update =>
{
Console.WriteLine(update);
//Message = update;
});
I needed to subscribe to the 'newMessage' target (since that's the JS is sending on) and also the type that's being posted isn't a string but a JObject type which i would need to deserialize to the correct type.
Related
I am building a simple restaurant management system in WPF. I have my backend in Laravel. I needed to setup a web socket to get real-time notifications on WPF app when a customer places an order from mobile app. I configured the web socket in Laravel using beyondcode/laravel-websockets. For ease, I tested the web socket on client side using laravel-echo with Vue. Everything works well there but I couldn't find any solution to replicate laravel-echo in C#.
Here is the code I am using in Vue.js with laravel-echo:
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
const token = "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'laravel_rdb',
wsHost: '127.0.0.1',
authEndpoint: 'http://localhost/may-app/public/broadcasting/auth',
encrypted: false,
forceTLS: false,
wsPort: 6001,
wssPort: 6001,
disableStats: true,
enabledTransports: ['ws', 'wss'],
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},
})
window.Echo.private('customer-order')
.listen('OrderPlaced', (e) => {
console.log(e)
})
I found SocketIOClient is used to implement web socket functionality in .NET. I tried to use a solution I found here but it didn't work for me. Also, I didn't find any way to set up my authentication URL in this package. I read socket.io documentation for anything related to authentication but I couldn't find any.
How do I implement equivalent functionality in C# .NET as in laravel-echo?
There is probably no client like laravel-echo for .NET. However, you will be able to connect to your sockets using pusher client: pusher/pusher-websocket-dotnet and this is probably the highest level of compatibility you can reach. But you will need to parse your messages and subscribe to the channels by yourself, there will be no sweet wrapping like in laravel-echo =(
I was able to implement a solution using the package mentioned by PunyFlash in the answers. The NuGet package is available here and here is the GitHub repo.
My solution might be useful for someone in the future so, my equivalent code for the laravel-echo code above, in .NET is:
internal class OrderSocket
{
public static async void Connect()
{
try
{
//Setting authentication
var authorizer = new CustomAuthorizer("http://localhost/may-app/public/broadcasting/auth")
{
AuthenticationHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("Authorization", "Bearer " + "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"),
};
//Creating pusher object with authentication
Pusher pusher = new Pusher("laravel_rdb", new PusherOptions
{
Authorizer = authorizer,
Host = "127.0.0.1:6001",
});
//Connecting to web socket
await pusher.ConnectAsync().ConfigureAwait(false);
//Subscribing to channel
Channel channel = await pusher.SubscribeAsync("private-customer-order").ConfigureAwait(false);
if (channel.IsSubscribed)
{
//Binding to an event
channel.Bind("App\\Events\\OrderPlaced", (PusherEvent eventResponse) =>
{
// Deserialize json if server returns json values
Debug.WriteLine(eventResponse.Data);
});
}
}
catch (Exception)
{
Debug.WriteLine("An exception occurred.");
}
}
}
//HttpAuthorizer child class to set default headers
internal class CustomAuthorizer : HttpAuthorizer
{
public CustomAuthorizer(string authEndpoint) : base(authEndpoint) { }
public override void PreAuthorize(HttpClient httpClient)
{
base.PreAuthorize(httpClient);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
}
I have the following services implemented on Azure:
1x Azure SignalR Service (Serverless) ASRS
2x Azure Functions (Serverless) HubFuncDown & HubFuncUp
On the ASRS I have defined TWO UpStream URLs, one to HubFuncDown Azure Function and the other to HubFuncUp. Using the URL pattern defined in the docs:
HubFuncDown Contains the following method which instructs the device app to disable itself:
[FunctionName(nameof(DisableDevice))]
public async Task DisableDevice([SignalRTrigger] InvocationContext invocationContext, string deviceId, ILogger logger) {
await Clients.User(deviceId).SendAsync(DisableDeviceTarget, new NewMessage(invocationContext, deviceId));
}
And HubFuncUp Contains the following method:
[FunctionName(nameof(DeviceDisabled))]
public async Task DeviceDisabled([SignalRTrigger] InvocationContext invocationContext, string deviceId, ILogger logger) {
// .. Updates DBContext, sends alerts etc
}
I'm trying to tell HubFuncUp that this client is now in a disabled state, the code to do that on the (UWP) client is:
var connection = new HubConnectionBuilder()
.WithUrl("https://hubfuncup.azurewebsites.net/api", options => {
options.AccessTokenProvider = () => Auth();
})
.ConfigureLogging(logging => {
logging.AddProvider(new SerilogLoggerProvider());
logging.SetMinimumLevel(LogLevel.Debug);
})
.WithAutomaticReconnect(new RetryPolicy())
.Build();
And to tell HubFuncUp of the state:
await connection.InvokeAsync("UpdateDeviceState", new DisabledDeviceMessage { DeviceId = 123, State = States.Disabled });
But each time I'm receiving a 404 Error from the call to InvokeAsync("UpdateDeviceState"..). It seems no matter what I do I cannot connect the one ASRS to two Azure Functions using multiple Upstream URLs.
Am I correct in thinking I need to utilise a seperate Azure SignalR Service (which doubles my cost) or can I connect the two Functions to the one SignalR Service via routing on the Upstream URLs?
I have a WebSite integrated with SignalR. It functions well, and it has a button which sends popup notification to all clients who are online. It works well when I click on the button.
My API is in another project but in the same Solution. I want to send the above notification by calling from the API side. Basically, a mobile app will send a request to API and then API will send a notification to all online web clients.
Below code runs and not gives the notification nor any error.
Is this fundamentally correct? Appreciate your help
API code (at WebAPI project)
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
GMapChatHub sendmsg = new GMapChatHub();
sendmsg.sendHelpMessage(record_id.ToString());
return "Done";
}
C# code (at Web project)
namespace GMapChat
{
public class GMapChatHub : Hub
{
public void sendHelpMessage(string token)
{
var context = GlobalHost.ConnectionManager.GetHubContext<GMapChatHub>();
context.Clients.All.helpMessageReceived(token, "Test help message");
}
}
}
Home.aspx file (at Web project)
var chat = $.connection.gMapChatHub;
$(document).ready(function () {
chat.client.helpMessageReceived = function (token,msg) {
console.log("helpMessageReceived: " + msg);
$('#helpMessageBody').html(msg)
$('#helpModal').modal('toggle');
};
}
You can not call that hub directly. Firs you need to install the .net client for SignalR from nuget. Then you need to initialize it like this :
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
using (var hubConnection = new HubConnection("your local host address"))
{
IHubProxy proxy= hubConnection.CreateHubProxy("GMapChatHub");
await hubConnection.Start();
proxy.Invoke("sendHelpMessage",record_id.ToString()); // invoke server method
}
// return sth. IEnumerable<string>
}
And opening a new connection per request may not be good idea you may make it per session (if you use) or static or time fashioned.
I am trying to create simple SignalR hub between MVC server side and WinForms client side.
I have created NotificationHub class, specified as this:
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
namespace PF.Timesheet.Service
{
public class NotificationHub : Hub
{
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
public override Task OnConnected()
{
return base.OnConnected();
}
}
}
While code on client side is this:
var hubConnection = new HubConnection("http://localhost:30341/singalr");
var chat = hubConnection.CreateHubProxy("NotificationHub");
string message2 = string.Empty;
chat.On<string, string>("broadcastMessage", (name, message) => { message2 = message; });
chat.On<string, string>("broadcastMessage", (name, message) =>
this.Invoke((Action)(() =>
RichTextBoxConsole.AppendText(String.Format("{0}: {1}" + Environment.NewLine, name, message)));
hubConnection.Start().Wait();
I was trying to get message from broadcastMessage on any possible way, both by assigning it to some string variable 'message2' or by appending text to rich textbox control on UI.
If I make call like this:
hubConnection.Start().Wait();
I will get 401 Unauthorized response from localhost where signalr is selfhosted. (local host is running in parallel as different solution within same project as client WinForms app).
What I am trying to do on server side is to push message to hub from code like this:
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.All.broadcastMessage("NAM", "New entry created.");
If I remove Wait() from: hubConnection.Start(); I won't get Unauthorized 401 error, but I wouldn't get message value as well.
Does anybody sees what I am doing wrongly here?
Things I've tried so far:
Trying to set up EnableDetailedErrors and EnableJSONP properties to true. I have read somewhere that SignalR hub has [Authorized] attribute by default and that this configuration should help.
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
DependencyConfig.Initialize(config);
Loging.Initialize();
app.UseWebApi(config);
app.MapSignalR(new HubConfiguration
{
EnableDetailedErrors = true,
EnableJSONP = true
});
}
}
Trying to set WindowsAuth and AnonymousAuth properties for Server project to Enabled.
Checking if message was actually pushed to hub when using:
context.Clients.All.broadcastMessage("NAM", "New entry created.");
Message was there.
Did anybody passed issue with being Unauthorized and was able to read messages from MVC Server SignalR hub to WinForms client?
I managed to solve this on my own. Issue was that server (that was already setup before and not by me) had basic authentication with username and password.
This wasn't visible at all with any [Authorization] attribute or anything, but it was still there.
I solved it by setting up Credentials property on hubConfiguration:
hubConnection.Credentials = new NetworkCredential("user.name", "password");
Authentication and connection to hub was successful and message was returned from server to client.
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);