Signalr with redis backplane, sending message to user - .net-core

I am researching to use signalr to send messages from an api to a specific user (keyword). Locally, I have everything working as designed (even using redis backplane).
When I move up to an actual environment with multiple servers (azure app service), it seems like messages to specific users don't flow through the backplane. If I send a message to Clients.All it works. But, if I send a message to Clients.User(username), the message is never received. This works locally because it's a single server, but doesn't seem to work in multiple servers.
await this.impersonationContext.Clients.User(mainUserName).SendAsync("msguser", new object[] { mainUserName });
await this.impersonationContext.Clients.All.SendAsync("msg", new object[] { "wtf" });
If I call the above code, only the msg event is fired, but the msguser is never recieved.
Could I be missing something on the setup? That's my assumption, I cannot be the only one doing this.
Below is my setup in the startup.cs. This is using .net core 5 with the latest nugets, etc
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
})
.AddStackExchangeRedis(this.Configuration["Redis:Cache"], options => {
options.Configuration.ChannelPrefix = "ImpersonationService";
});
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
Any help would be appreciated.
Update #1
Looks like the user info is being pushed through the backplane (username hidden). So maybe its the connection from javascript client?
Update #2
Getting closer...looks like the subscription isn't setup for the specific user like I see locally. Could this be websockets?
Update #3
Found it....sorta.
I was missing the authorize attribute on the hub, which was allowing the websocket to connect, even though websockets isn't authenticating for some reason (different issue). Once I added the authorize, it will drop down to long polling with the user info and works as designed

Related

gRpc error "Incomplete Message" When Not Using GrpcWeb, using F5 Load Balancer

We have a grpc service deployed to multiple servers and an F5 pointer/load balancer to those servers. The service(s) are setup as windows services, bound to a specific port. This is working...fine, with the exception of consuming the service when using the F5 address, and the call originating from a server, unit test, or testing suite (Kreya); grpcweb/wasm calls are having no issues. Implementation below:
builder.Services.AddCodeFirstGrpcClient<INTERFACE>(client =>
{
client.Address = new Uri(<<address>>);
client.ChannelOptionsActions.Add(option =>
{
var handler = new Grpc.Net.Client.Web.GrpcWebHandler(Grpc.Net.Client.Web.GrpcWebMode.GrpcWeb, new HttpClientHandler());
option.HttpClient = new HttpClient(handler);
option.DisposeHttpClient = true;
// can't have an HttpClient and a HttpHandler so make sure this is null
option.HttpHandler = null;
option.MaxReceiveMessageSize = int.MaxValue;
});
});
So, server/test(s) calls to the individual servers (https://servername:port/) each return data; when using the F5 url: "Incomplete Message." Implementation, below:
services.AddCodeFirstGrpcClient<INTERFACE>(implementation =>
{
implementation.Address = new Uri(<<address>>);
});
Even in the unit tests/suite, when switching to using grpcweb or grpcwebtext, there is no difference, I still get the error message, "Incomplete Message."
Using Kreya, I can see that the connection is established, tls handshake completed, message sent, but, no message response/sent, it just fails.
I know I can implement load balancing from the client calls themselves, but, that defeats the purpose of utilizing F5 for our organization, and we all just needing to know a single url, and all the other benefits of such an implementation.
Can anyone provide any insight into why this error is occuring, only when the calls originate from a server/unit test? I wrote/implemented the service, and have been the primary deployer of the service, and worked with the server folk regarding the F5 implementation. We're utilizing protobuf-net grpc, if that helps. This dangole issue sounds like something hella dumb that I'm over looking.
Thanks much.

Why is this SignalR messages being received multiple times?

I am using SignalR in a Blazor server-side app. I added the Microsoft.AspNetCore.SignalR.Client Nuget package (v5.0.11) to the project, and used the following code to create the hub connection...
HubConnection hubConnection = new HubConnectionBuilder()
.WithUrl("url")
.Build();
await hubConnection.StartAsync();
I then send out a message with the following code (Debug.WriteLine added to confirm what's going on)...
Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} SignalR msg sent");
await hubConnection.SendAsync("Send", "Hello");
The component that is to handle such messages creates the hub connection and hooks up to the On handler as follows...
HubConnection hubConnection = new HubConnectionBuilder()
.WithUrl("url")
.Build();
hubConnection.On<string>("Receive", msg =>
Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} SignalR msg received - {msg}"));
await hubConnection.StartAsync();
When the message is sent out, it is definitely only being sent once (which I can confirm from the output panel, where I only see the "sent" output once), but it is received twice.
Searching around, it seems that a common reason for this is if jQuery was loaded twice. However, I have checked this, and it's not the case. It's only being loaded the once. Furthermore, one of the other developers on the team tried it, and he got the message received 15 times! Even if we had accidentally included jQuery twice, we certainly didn't include it 15 times. Also, he's using exactly the same code as me (checked out from source control), so we should get the same results if this were the issue.
Anyone any idea why this could be happening? Thanks
This can occur when you initialise a HubConnection within a component and don’t configure IDispose or IAsyncDispose on your component to dispose the HubConnection.
In a Blazor Server-Side application there is already a SignalR host pushing updates to the client.
If you want to push updates from a page you can use a Singleton Service to handle the communication.
If you set up your own SignalR hub and use the SignalR client in your components, your application is something like the diagram below:
As you can see, the client is actually running on the server. The SignalR hub you've added adds processing and memory overhead and does not add any value.
I created a simple sample app that uses a service that clients listen to for updates:
https://github.com/conficient/BlazorServerWithSignalR

Creating a service worker that can run in background, using signalr for asp.net site

I'm playing with different ways to create push notifications in asp.net core, since my boss asked me to research it.
So i just set up a barebones project that uses signalr to send messages from a hub to the client, and conversely for the client to the hub.
Currently, I have a button on a certain page where this javascript is used:
function receive() {
$.ajax({
url: '/msg/SendToClient',
type: 'get',
});
}
var connection = new signalR.HubConnectionBuilder()
.withUrl("/NotificationHub")
.build();
connection.on("ReceiveMessage", function (user, message) {
alert(message);
});
connection.start();
Where the receive() is called everytime the button is pushed, that means that a corresponding controller action is called, which sends a message from server -> client.
But what I want is that I would like the javascript file to be laying on the client, and just run once in a while to check for new available push notifications.
A few scenarios are available, and I would like to know about all of them:
Push notifications when browser is closed. I'm guessing this is not possible with a "regular" service worker?
Push notification when browser is open, but site is not open. For this, I guess I will need to somehow have a javascript file/service worker on the clients computer just always running. This I really have no idea how to achieve this.
A push notification that can work when the site is open in the browser. All I think I need for this is to have the javascript included always when the user is on the site, and then somehow make a request every few minutes.
Any inputs on these three things will be greatly appreciated. Is any of them easily achieveable/advisable? Is there any "normal" mode of operation for these three approaches to push notifications?
For any of these three scenarios, is there a case where signalr is parhaps not the best resource?

Xamarin Forms Azure App Service ADAL Logout not working as expected

We are currently writing a Xamarin Forms Azure Mobile application, using client flow, AAD authentication, refresh tokens etc.
Most of this is working as expected. However, logging out of the application does not work properly. It completes the logout process for both Android and iOS - but upon redirection to the login screen, hitting sign in will never prompt the user with the Microsoft login as expected, it will sign them straight back into the app.
To add a little bit of background, this app has been implemented as per Adrian Hall's book,
current link: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/
with the above described options and configurations.
I have also read through the 30 days of Zumo (also by Adrian Hall) blog on this, and every single post I can find on here relating to this.
My current logout code is as follows:
public async Task LogoutAsync()
{
var loginProvider = DependencyService.Get<ILoginProvider>();
client.CurrentUser = loginProvider.RetrieveTokenFromSecureStore();
var authUri = new Uri($"{client.MobileAppUri}/.auth/logout");
using (var httpClient = new HttpClient())
{
if (IsTokenExpired(client.CurrentUser.MobileServiceAuthenticationToken))
{
var refreshed = await client.RefreshUserAsync();
}
httpClient.DefaultRequestHeaders.Add("X-ZUMO-AUTH", client.CurrentUser.MobileServiceAuthenticationToken);
await httpClient.GetAsync(authUri);
}
// Remove the token from the cache
loginProvider.RemoveTokenFromSecureStore();
//Remove the cookies from the device - so that the webview does not hold on to the originals
DependencyService.Get<ICookieService>().ClearCookies();
// Remove the token from the MobileServiceClient
await client.LogoutAsync();
}
As far as I can tell, this includes everything I have found so far - i.e. calling the /.auth/logout endpoint, removing the token locally, clearing the cookies from the device (as we log in inside a webview) and lastly calling the LogoutAsync() method from the MobileServiceClient.
Am I missing anything? Or is there a way we can force log out from this environment? As I know you can't "invalidate" an OAuth token, you have to wait until it expires - but to my mind, the /.auth/logout endpoint is supposed to handle this within the Azure environment? Though I'm just not sure to what extent.
Any help is appreciated.
We are currently writing a Xamarin Forms Azure Mobile application, using client flow, AAD authentication, refresh tokens etc. Most of this is working as expected. However, logging out of the application does not work properly.
I assumed that if you use the server flow for logging with AAD, the logout processing may works as expected. As you described that you used client flow, since you have clear the client cache for token, I assumed that the issue may caused by the LoginAsync related (ADAL part) logic code, you need to check your code, or you could provide the logging related code for us to narrow this issue.

WCF Service with SignalR

I have a web application which has few charts on dashboard. The data for charts is fetched on document.ready function at client side invoking a WCF service method.
What i want is now to use SignalR in my application. I am really new to SignalR. How can i call WCF methods from SignalR Hub or what you can say is that instead of pulling data from server i want the WCF service to push data to client every one minute.
Is there a way of communication between signalR and WCF service.
Also another approach can be to force client to ask for data from WCF Service every minute.
Any help will be really appreciated.
I have done following as of yet.
Client Side Function on my Dashboard page
<script src="Scripts/jquery.signalR-2.0.3.min.js"></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="/signalr/hubs"></script>
<a id="refresh">Refresh</a>
$(function() {
var dashboardHubProxy = $.connection.dashboardHub;
$.connection.hub.start().done(function() {
// dashboardHubProxy.server.refreshClient(parameters);
$("#refresh").click(function() {
dashboardHubProxy.server.refreshClient(parameters);
});
});
dashboardHubProxy.client.refreshChart = function (chartData) {
debugger;
DrawChart(chartData, 'Hourly Call Count For Last ' + Duration + ' Days', '#chartHourly', 'StackedAreaChart');
};
});
and my Dashboard Hub class is as follows
public class DashboardHub : Hub
{
private readonly ReportService ReportService = new ReportService();
public void RefreshClient(string parameters)
{
var chartData = ReportService.GenerateHourlyCallsTrendGraphicalReport(parameters);
Clients.All.refreshChart(chartData);
}
}
My SignalR startup class is as follows
[assembly: OwinStartup(typeof(CallsPortalWeb.Startup), "Configuration")]
namespace CallsPortalWeb
{
public static class Startup
{
public static void Configuration(IAppBuilder app)
{
ConfigureSignalR(app);
}
public static void ConfigureSignalR(IAppBuilder app)
{
app.MapSignalR();
}
}
}
When i click on refresh button and a debugger on RefreshClient method on hub the debugger doesn't get to the method which means i am unable to call server side method of SignalR.
Is there anything needs to be done in web.config?
I agree with AD.Net's comment. To elaborate slightly more though, the SignalR hubs can be hosted directly in your web project kinda the same way controllers are used. There is also a package out there so you can host the SignalR library on its own so it can act as a service all on its own. Either way you will need to hit the SignalR hub first as that is how it communicates then you would call your WCF service methods from within the hubs.
Brief explanation
Your HUB will have methods used by both your USER Client and your WCF Client. You may use something like UserConnected() for the user to call in and setup your logging of the connection. Then the WCF service may call your HUB with an UpdateUserStats(Guid connnectionId, UserStats stats) which would in turn call the USER client directly and provide the stats passed in like so Clients.Client(connectionId).updateStats(stats) which in turn would have a method on the USERS client named updateStats() that would handle the received information.
Initial page landing
What AD.Net provided is basic code that will be called when the user lands on the page. At this point you would want to log the ConnectionId related to that user so you can directly contact them back.
First contact with your hub touching WCF
From your Hub, you could call your WCF service as you normally would inside any normal C# code to fetch your data or perform action and return it to your user.
Method of updating the user periodically
SignalR removes the need for your client code to have to continually poll the server for updates. It is meant to allow you to push data out to the client with out them asking for it directly. This is where the persistence of the connections come into play.
You will probably want to create a wrapper to easily send messages to the hub from your application, since you are using WCF I would assume you have your business logic behind this layer so you will want the WCF service reaching out to your Hub whenever action X happens. You can do that by utilizing the Client side C# code as in this case your client is actually the user and the WCF service. With a chat application the other user is basically doing what you want your WCF service to do, which is send a message to the other client.
Usage example
You are running an online store. The dashboard displays how many orders there have been for the day. So you would wire up a call to the hub to send a message out to update the products ordered when a user places a new order. You can do this by sending it to the admin group you have configured and any admins on the dashboard would get the message. Though if these stats are very user specific, you will more then likely instead reach into the database, find the ConnectionId that the user has connected with and send the update message directly to that connectionid.
WCF Client Code Example
Just incase you want some code, this is directly from MS site on connecting with a .net client. You would use this in your WCF service, or wherever in your code you plan on connecting and then sending an update to your user.
var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start();
Here is a link directly to the .Net Client section: http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-net-client
I am sure you have seen this link but it really holds all the good information you need to get started. http://www.asp.net/signalr
Here is a more direct link that goes into usages with code for you. http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server
ADDED: Here is a blog specific to Dashboards with SignalR and their polling.
http://solomon-t.blogspot.com/2012/12/signalr-and-interval-polling-for.html
ADDED: Here is a page on managing users signalR connections.
http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections
Update for your code update
The .Net Client library (in NuGet) gives your .net code access to the hub. Since you are a client you will need to connect to the hub just like the User who is also a client. Your hub would act as the server for this. So with the .Net Client I am assuming you would setup a windows service that would internally poll, or something event based that would call the .Net Client code portion of it which would reach out to your hub. Your hub would take the information provided, more than likely a ConnectionId or GroupId and broad cast the User (which is perhaps on a website so it would be the JS client) a method that would update the front end for the user client. Basically what I mention under "Brief Explanation".
Now, to directly respond to the code you posted. That is Javascript, I would expect a connect like you have done. Updating the chart on initial connection is fine as well. If this is all the code signalR wise though you are missing a client side method to handle the refresh. Technically, instead of calling Clients.Caller.RefreshChart() you could just return that data and use it, which is what your javascript is doing right now. You are returning void but it is expecting a your date.
Now, I would actually say correct your javascript instead of correcting the hub code. Why? Because having a method in JS on your client that is called "refreshChart()" can be reused for when you are having your server reach out and update the client.
So I would recommend, dropping anything that is related to updating the dashboard in your JS done statement. If you want to do a notification or something to the user that is fine but dont update the grid.
Now create a JS client function called "refreshChart", note the lower case R, you can call it with a big R in c# but the js library will lowercase it so when you make the function have it will receive your dashboard information.
Now, on the server polling, or executing on some action, your WCF would call a method on the hub that would be say "UpdateDashboar(connectionId,dashInfo)" and that method would then inside of it call the "refreshChart" just like you are doing in your RefreshClient method, accept instead of doing Clients.Caller you would use Clients.Client(connectionId).refreshChart(chartInfo).
Directly the reason your code is not working is because you need to turn that Void into the type you expect to be returned. If the rest is coded right you will have it update once. You will need to implement the other logic I mentioned if you want it constantly updating. Which is again why I asked about how you are persisting your connections. I added a link to help you with that if you are not sure what I am talking about.
You should use the SignalR Hub to push data to the client. Your hub can consume a WCF service (the same way your client can) to get the data.
from client:
hub.VisitingDashBoard();
on the hub in the VisitingDashBoard method:
var data = wcfClient.GetDashboardData()//may be pass the user id from the context
Clients.Caller.UpdateDashboard(data)
Of course your client will have a handler for UpdateDashboard call

Resources