signalR in blazorise - asp.net-core

I'm using SignalR in asp.net core on server side and blazorise on client side. before I've used SignlaR basically just to chat and now I wanna use it to update a table when a record is inserted in some where else.
I think everything is ok on server side because as I trace it on server, it posts correct values but it does not update the table on client side. I don't know what's wrong.
here is my code on server side which is in a hub:
public async Task SendCartableUpdate(ResultData<PersonnelStationsInfo> resultData)
{
await Clients.All.SendAsync("RefreshCartable",resultData);
}
and this is how I use it on client side:
protected override async Task OnInitializedAsync()
{
//await base.OnInitializedAsync();
user = CurrentUserService.CurrentUser;
await CartableTableChangePage(1);
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("http://localhost:15424/ProductionServiceHub"))
.Build();
hubConnection.On<ResultData<PersonnelStationsInfo>>("RefreshCartable", (_resultData) =>
{
StateHasChanged();
});
await hubConnection.StartAsync();
}
thanks for your helping

Finally I solved it and I'm so excited :)
here is how I changed my code on server side:
public async Task SendCartableUpdate()
{
await Clients.All.SendAsync("RefreshCartable");
}
and this how I changed my code on client side:
protected override async Task OnInitializedAsync()
{
//await base.OnInitializedAsync();
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("http://192.168.2.72:1050/ProductionServiceHub"))
.Build();
hubConnection.On("RefreshCartable", () =>
{
CallLoadData();
StateHasChanged();
});
await hubConnection.StartAsync();
user = CurrentUserService.CurrentUser;
await CartableTableChangePage(1);
}
private void CallLoadData()
{
Task.Run(async () =>
{
await CartableTableChangePage(1);
StateHasChanged();
});
}

Related

Azure Signal R Service not working on Blazor Wasm app

I am trying to set up Azure Signal R Service. Currently, I can get it working if I send a message to ALL clients but if I try and use Groups it doesn't send any messages. I suspect it has to do with the Azure signal R Service. I am running this locally atm
in My Web Api Startup.cs I have the following:
services.AddSignalR()
.AddAzureSignalR(options => options.Endpoints = new[]
{
new ServiceEndpoint(Configuration.GetConnectionString("AzureSignalRConnection"))
});
and
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapHub<SignalRHub>("/SingalRHub`enter code here`");
});
Then In my Hub:
public class SignalRHub: Hub
{
public async Task Join(long deviceId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, deviceId);
}
public async Task ReadTime(int deviceId)
{
await Clients.Group(deviceId).SendAsync($"ReadResponse", DateTime.Now.ToString());
}
}
On my client side: (Blazor wasm app)
private async Task InitiaizeSignalR()
{
//Intialize signal R
_hubConnection = new HubConnectionBuilder()
.WithUrl(_navigationManager.ToAbsoluteUri("/SignalRHub"))
.Build();
_hubConnection.On<string>("ReadResponse", (message) =>
{
Console.WriteLine($"Read Response: {message}");
});
//Connect to hub
await _hubConnection.StartAsync();
//Register to get messages for this DeviceId
await _hubConnection.SendAsync("Join", DeviceId);
}
Now the stange thing is that when I call this on my client, I dono get any response. However If i change it to:
await Clients.All.SendAsync($"ReadResponse", DateTime.Now.ToString());
Then my clinet will get the message?

AddWebhookNotification to call Method in Controller

I have this configured in my StartUp.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.ConfigureEmail(Configuration)
.AddHealthChecksUI(setupSettings: setup =>
{
setup
.AddWebhookNotification("WebHookTest", "/WebhookNotificationError",
"{ message: \"Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]\"}",
"{ message: \"[[LIVENESS]] is back to life\"}",
customMessageFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"{failing.Count()} healthchecks are failing";
},
customDescriptionFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"HealthChecks with names {string.Join("/", failing.Select(f => f.Key))} are failing";
});
})
.AddControllers();
}
public void Configure(IApplicationBuilder app)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
app.ConfigureExceptionHandler();
app
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecksUI(options =>
{
options.ResourcesPath = string.IsNullOrEmpty(pathBase) ? "/ui/resources" : $"{pathBase}/ui/resources";
options.UIPath = "/hc-ui";
options.ApiPath = "/api-ui";
});
endpoints.MapDefaultControllerRoute();
});
}
And in the Controller:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError([FromBody] string id)
{
MimeMessage mimeMessage = new MimeMessage { Priority = MessagePriority.Urgent };
mimeMessage.To.Add(new MailboxAddress(_configuration.GetValue<string>("ConfiguracionCorreoBase:ToEmail")));
mimeMessage.Subject = "WebHook Error";
BodyBuilder builder = new BodyBuilder { HtmlBody = id };
mimeMessage.Body = builder.ToMessageBody();
await _appEmailService.SendAsync(mimeMessage);
return Ok();
}
The watchdog application is configured in the appSettings.json to listen to different APIs.
So far everything works fine, but, if I force an error, I'd like to receive a notification email.
The idea is that, when an error occurs in any of the Healths, you send an email.
Environment:
.NET Core version: 3.1
Healthchecks version: AspNetCore.HealthChecks.UI 3.1.0
Operative system: Windows 10
It's look like you problem in routes. Did you verify that method with Postman?
Also check if your webHook request body is a text, try to change your template payload:
{ "message": "Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]"}",
and in the controller change string to object. And check what you receive in DEBUG.
Try using Api/WebhookNotificationError inst. of /WebhookNotificationError if your controller is ApiController. The controller name seems to be missing
I think you should try this. It works for me.
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError()
{
using (var reader = new StreamReader(
Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false))
{
var payload = await reader.ReadToEndAsync();
//do whatever with your payloade here..
//I am just returning back for a example.
return Ok(payload);
}
}

Blazor WebAssembly SignalR Authentication

I would love to see an example on how to add authentication to a SignalR hub connection using the WebAssembly flavor of Blazor. My dotnet version is 3.1.300.
I can follow these steps to get an open, unauthenticated SignalR connection working: https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor-webassembly?view=aspnetcore-3.1&tabs=visual-studio
All the tutorials I find seem older or are for a server-hosted type, and don't use the built-in template.
I have added authentication to the rest of the back-end, using the appropriate template and these instructions, including the database:
https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1
But every time I add [Authenticate] to the chat hub, I get an error returned. Is there any way, extending the first tutorial, that we can authenticate the hub that is created there? It would be great to hitch on to the built-in ASP.NET system, but I am fine just passing a token in as an additional parameter and doing it myself, if that is best. In that case I would need to learn how to get the token out of the Blazor WebAssembly, and then look it up somewhere on the server. This seems wrong, but it would basically fill my needs, as an alternative.
There are all sorts of half-solutions out there, or designed for an older version, but nothing to build off the stock tutorial that MS presents.
Update:
Following the hints in this news release https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/, I now can get a token from inside the razor page, and inject it into the header. I guess this is good?? But then how do I get it and make use of it on the server?
Here is a snippet of the razor code:
protected override async Task OnInitializedAsync()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(UriHelper.BaseUri);
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
hubConnection = new HubConnectionBuilder()
.WithUrl(UriHelper.ToAbsoluteUri("/chatHub"), options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
}
}
Update 2:
I tried the tip in here: https://github.com/dotnet/aspnetcore/issues/18697
And changed my code to:
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chatHub?access_token=" + token.Value))
.Build();
But no joy.
I've come across the same issue.
My solution was 2-sided: I had to fix something in the fronend and in the backend.
Blazor
In your connection builder you should add the AccessTokenProvider:
string accessToken = "eyYourToken";
connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/chat", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
options.AccessTokenProvider is of type Func<Task<string>>, thus you can also perform async operations here. Should that be required.
Doing solely this, should allow SignalR to work.
Backend
However! You might still see an error when SignalR attempts to create a WebSocket connection. This is because you are likely using IdentityServer on the backend and this does not support Jwt tokens from query strings. Unfortunately SignalR attempts to authorize websocket requests by a query string parameter called access_token.
Add this code to your startup:
.AddJwtBearer("Bearer", options =>
{
// other configurations omitted for brevity
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs"))) // Ensure that this path is the same as yours!
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
edit 1: Clarified the usage of the Blazor SignalR code
In my case (Blazor WebAssembly, hosted on ASP.NET Core 5.0 using JWT Bearer Token Auth), I had to add the following:
Blazor WASM Client
When building the connection (in my case: in the constructor of some service proxy class), use IAccessTokenProvider and configure the AccessTokenProvider option like so:
public ServiceProxy(HttpClient httpClient, IAccessTokenProvider tokenProvider) {
HubConnection = new HubConnectionBuilder()
.WithUrl(
new Uri(httpClient.BaseAddress, "/hubs/service"),
options => {
options.AccessTokenProvider = async () => {
var result = await tokenProvider.RequestAccessToken();
if (result.TryGetToken(out var token)) {
return token.Value;
}
else {
return string.Empty;
}
};
})
.WithAutomaticReconnect() // optional
.Build();
}
ASP.NET Core Server
Add the following to Startup.ConfigureServices:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => {
// store user's "name" claim in User.Identity.Name
options.TokenValidationParameters.NameClaimType = "name";
// pass JWT bearer token to SignalR connection context
// (from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0)
options.Events = new JwtBearerEvents {
OnMessageReceived = context => {
var accessToken = context.Request.Query["access_token"];
// If the request is for on of our SignalR hubs ...
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.Request.Path.StartsWithSegments("/hubs/service"))) {
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
This is my solution and works
[Inject] HttpClient httpClient { get; set; }
[Inject] IAccessTokenProvider tokenProvider { get; set; }
HubConnection hubConnection { get; set; }
(...)
private async Task ConnectToNotificationHub()
{
string url = httpClient.BaseAddress.ToString() + "notificationhub";
var tokenResult = await tokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
hubConnection = new HubConnectionBuilder().WithUrl(url, options =>
{
options.Headers.Add("Authorization", $"Bearer {token.Value}");
}).Build();
await hubConnection.StartAsync();
hubConnection.Closed += async (s) =>
{
await hubConnection.StartAsync();
};
hubConnection.On<string>("notification", m =>
{
string msg = m;
});
}
}

ASP NET Core SignalR OnDisconnectedAsync not firing with hub [Authorized]

.net core 2.1
Hub code:
[Authorize]
public class OnlineHub : Hub
{
public override async System.Threading.Tasks.Task OnConnectedAsync()
{
int userId = Context.UserIdentifier;
await base.OnConnectedAsync();
}
[AllowAnonymous]
public override async System.Threading.Tasks.Task OnDisconnectedAsync(Exception exception)
{
var b = Context.ConnectionId;
await base.OnDisconnectedAsync(exception);
}
Client code:
$(document).ready(() => {
let token = "token";
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://localhost:44343/online", { accessTokenFactory: () => token })
.configureLogging(signalR.LogLevel.Debug)
.build();
connection.start().catch(err => console.error(err.toString()));
});
Without [Authorize] all works fine, except Context.UserIdentifier in OnConnectedAsync, and it's explainable, but... with [Authorize] attribute on Hub class, OnConnectedAsync start working and OnDisconnected not fires at all, including 30sec timeout (by default).
Any ideas?
If you have a debugger attached and close the client by closing the browser tab, then you'll never observe OnDisconnectedAsync being fired. This is because SignalR check if a debugger is attached and don't trigger certain timeouts in order to making debugging easier. If you close by calling stop on the client then you should see OnDisconnectedAsync called.
Add
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
This should trigger OnDisconnectedAsync in SignalR Hub
for blazor implement this code
#implements IAsyncDisposable
and paste this func. to code
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
This seems to be very old issue, but i will add some experience about troubleshooting, maybe it'll help someone once. After investigating, why signalR javascript client is not causing OnDisconnectedAsync when using Authorize attribute and HTTP connection for messaging, i found that the DELETE method it sends to server is blocked by CORS policy. So you can look to request responses, and if request is blocked as restricted by CORS, highly likely you need to allow DELETE (and OPTIONS as well) method to your CORS policy.

.NET Core Hosted services timeout

I've started using .NET Core Hosted services to handle some pub/sub functionality. I plan to use cancellation tokens to shut the services down. And I wonder whether there is some timeout for them? I want the service to run forever if it has not been explicitly stopped.
Appreciate your help.
protected override Task ExecuteAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(
() =>
{
var redisPubSub = new RedisPubSubServer(ClientsManager, Constants.RedisChannel)
{
OnMessage = (channel, msg) =>
{
Trace.WriteLine($"Received '{msg}' from channel '{channel}'");
var message = JsonConvert.DeserializeObject<Message>(msg);
_hubContext.Clients.User(message.UserId.ToString()).SendAsync("ReceiveMessage", message, cancellationToken);
},
OnUnSubscribe = (message) =>
{
Trace.WriteLine($"OnUnSubscribe returns {message}");
},
OnError = (exception) =>
{
Trace.WriteLine($"OnError returns {exception}");
},
OnStart = () =>
{
Trace.WriteLine($"OnStart has been fired.");
},
OnStop = () =>
{
Trace.WriteLine($"OnStop has been fired");
}
};
redisPubSub.Start();
Trace.WriteLine($"OnStop has been fired {redisPubSub.WaitBeforeNextRestart} {redisPubSub.HeartbeatTimeout} {redisPubSub.HeartbeatInterval}");
}, cancellationToken);
}
If you check out my accepted answer here, and your hostedservice kinda looks like that, you can just do the following within the while-loop contained in the ExecuteAsync method.
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
The method must be marked as async of course.