SignalR context within core3.1 controller - no context.clients - asp.net-core

I am trying to call a SignalR Hub Action from a controller.
On my controller I have this:
private readonly IHubContext<TurnHub> _hubContext;
public HomeController(ILogger<HomeController> logger, IHubContext<TurnHub> hubContext)
{
_logger = logger;
_hubContext = hubContext;
_gameService = new GameService(ModelState);
}
public async Task<IActionResult> Test()
{
return View();
}
public async Task<IActionResult> TestMessage()
{
await _hubContext.Clients.All.SendAsync("TurnChanged", 1);
return View();
}
When I break on hub context, I can see nodes for "Clients" and "Groups" but there are no clients or groups under that level. Running the controller action sees no errors, but the message isn't pushed to the client.
On the hub I have this:
public class TurnHub : Hub
{
public async Task EndTurn(int nextUser)
{
await Clients.All.SendAsync("TurnChanged", nextUser);
}
}
And the view has this:
<script>
var connection = new signalR.HubConnectionBuilder().withUrl("/TurnHub").build();
connection.on("TurnChanged", function (nextUser) {
debugger;
alert(nextUser);
});
</script>
I was expecting any browser window that was displaying that view to alert when one of the clients hits that controller action (Called from a button on that view).
What am I doing wrong?
I have the signalr core package installed, the js file from "add client library" #microsoft/signalr. There are no console errors on the browser to say anything is wrong!
Any help greatly appreciated.

In your javascript client you need to start the hub connection. Like this
connection.start();
The start will return a promise so you could do some stuff after the hub has been connected. Also failures in connection can be tracked by catching errors on that promise.

Related

ASP.NET Core/5/6 - When exactly does a "Scoped Service" get disposed?

In ASP.NET Core/5/6 you can register a "Scoped Service" with the ServiceCollectionServiceExtensions.AddScoped() method.
In the Microsoft .NET Article Dependency injection in .NET, it states:
In apps that process requests, scoped services are disposed at the end of the request. https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
Is their definition of "end of the request" before or after the response (including headers) has been sent?
Answer: after
After running a test, it looks like scoped services are disposed after the response has finished sending. This is unfortunate, because if the response is a large file, it may take a few seconds to finish sending to the client, and the service will be unnecessarily kept alive (undisposed) that whole time. It would be better if scoped services were disposed before the response begins sending.
Anyway, here's the test I made.
First I created a test IDisposable class that writes to the console whenever it is instantiated and disposed:
public sealed class DisposableTest : IDisposable
{
public DisposableTest()
{
Console.WriteLine("//////////////////// Created");
}
private bool Disposed = false;
public void Dispose()
{
if (!Disposed)
{
Disposed = true;
Console.WriteLine("//////////////////// Disposed");
}
}
}
Then I added the scoped service in Program.cs (for ASP.NET 6):
services.AddScoped<DisposableTest>();
Next I added some middleware to write to the console whenever the Response.OnStarting() and Response.OnCompleted() events were run:
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
Context.Response.OnStarting(delegate ()
{
Console.WriteLine("//////////////////// Response Started");
return Task.CompletedTask;
});
Context.Response.OnCompleted(delegate ()
{
Console.WriteLine("//////////////////// Response Completed");
return Task.CompletedTask;
});
await Next();
});
Lastly I added the service to the constructor of HomeController so it gets accessed:
public class HomeController : Controller
{
public HomeController(DisposableTest TheTest) { }
public IActionResult Index() { return View(); }
}
After visiting the path /home/index, the IIS Express console showed the following:
It looks like the service is disposed after the response finishes sending.

Blazor WASM ViewModel

I did a lot of Razor pages the past year, and a couple of weeks ago I started to transform all to a ViewModel for my Blazor Server App.
Now I thought it's time to make a new Blazor WebAssembly App.
But I struggle to build a POC with a ViewModel, based on the WeatherForecast example.
But whatever I do, I have errors. And so far I did not find a a good basic example.
Unhandled exception rendering component: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
System.InvalidOperationException: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
Example: https://github.com/rmoergeli/fm2
namespace fm2.Client.ViewModels
{
public interface IFetchDataViewModel
{
WeatherForecast[] WeatherForecasts { get; set; }
Task RetrieveForecastsAsync();
Task OnInitializedAsync();
}
public class FetchDataViewModel : IFetchDataViewModel
{
private WeatherForecast[] _weatherForecasts;
private IFetchDataModel _fetchDataModel;
public WeatherForecast[] WeatherForecasts
{
get => _weatherForecasts;
set => _weatherForecasts = value;
}
public FetchDataViewModel(IFetchDataModel fetchDataModel)
{
Console.WriteLine("FetchDataViewModel Constructor Executing");
_fetchDataModel = fetchDataModel;
}
public async Task RetrieveForecastsAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
Console.WriteLine("FetchDataViewModel Forecasts Retrieved");
}
public async Task OnInitializedAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
}
}
}
namespace fm2.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IFetchDataViewModel, FetchDataViewModel>();
await builder.Build().RunAsync();
}
}
}
Additional note:
Here how I did it previously for Blazor Server App: https://github.com/rmoergeli/fm2_server
Here I try the same for the Blazor WebAssembly App:
https://github.com/rmoergeli/fm2_wasm (Constructor is not initialized).
This POC is different comapred to the first link at the top. Here I tried to just do the same like I did for the Blazor Server App.
I pulled the latest code from Github. It looks like the wrong api was getting called.
When I changed from this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
to this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
in WeatherViewModel.cs
I could get the weather data to be displayed.

Asp.Net Core Cannot access a disposed context instance

I'm trying to implement SignalR in order to consume data from a angular frontend application.
I've checked all the results on google that I can find, but I still can't solve my issue.
The error I'm getting is:
Cannot access a disposed context instance. A common cause of this
error is disposing a context instance that was resolved from
dependency injection and then later trying to use the same context
instance elsewhere in your application. This may occur if you are
calling 'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances. Object name: 'AdminContext'
Controller
[Route("api/[controller]")]
[ApiController]
public class ChartController : ControllerBase
{
private IHubContext<ChartHub> _hub;
private readonly ILiveMonitoringService _service;
public ChartController(IHubContext<ChartHub> hub, ILiveMonitoringService service)
{
_hub = hub;
_service = service;
}
public IActionResult Get()
{
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", await _service.GetAllAsync()));
return Ok(new { Message = "Request Completed" });
}
}
Service
public Task<List<LiveMonitoring>> GetAllAsync()
{
return _repository.GetAll().Take(100).ToListAsync();
}
Repository
public IQueryable<TEntity> GetAll()
{
try
{
return _adminContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception("Couldn't retrieve entities");
}
}
What could be the problem?
I'm pretty sure that TimerManager is your issue. You did not show its declaration but looks like its constructor accepts a callback to be called at some later point of time. And that's the issue. Your scoped service _service is captured in the callback and used at some later point of time when the request has already ended. So after the request ended, the DbContext is disposed and your _service will consume a disposed context.
The fix is to simply get the data first before passing it into your callback so that the _service will not be captured into that callback, like this:
public async Task<IActionResult> Get()
{
var liveMonitorings = await _service.GetAllAsync();
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings));
return Ok(new { Message = "Request Completed" });
}
We need to change the returned type of Get to Task<IActionResult> to support async call.
If you actually want to call _service.GetAllAsync() at some time later (not at the time of requesting Get) inside the callback, you need to inject an IServiceScopeFactory to create a scope for your service inside that callback, like this:
public IActionResult Get([FromServices] IServiceScopeFactory serviceScopeFactory)
{
var timerManager = new TimerManager(async () =>
{
using(var scope = serviceScopeFactory.CreateScope()){
var service = scope.ServiceProvider.GetRequiredService<ILiveMonitoringService>(); ​
​var liveMonitorings = await service.GetAllAsync();
​return await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings);
​ }
​});
​return Ok(new { Message = "Request Completed" });
}
This way you don't need to inject your _service into the controller's constructor (because it's not used at all).
​

How to get the current ClaimsPrincipal in SignalR core outside of a Hub

I have a SignalR Core hub which has a dependency on a service. That service itself has it's own dependencies and one of them requires access to the current ClaimsPrincipal.
I know, that I can access the ClaimsPrincipal inside the hub using the Context.User property and pass it as a parameter to the service, which can also pass it as a parameter and so on. But I really don't like to pollute the service API by passing this kind of ambient info as a parameter.
I've tried to use the IHttpContextAccessor as described in: https://learn.microsoft.com/en-us/aspnet/core/migration/claimsprincipal-current?view=aspnetcore-2.2
This seems to be working with a simple SignalR setup, but it isn't working with the Azure SignalR service, which will be our production setup.
Is there a reliable way how to get the ClaimsPrincipal outside of the hub that will work for both a simple local setup and Azure SignalR service?
In the current SignalR version (1.1.0) there is no support for this. I've created a feature request: https://github.com/dotnet/aspnetcore/issues/18657 but it was rejected. Eventually, I've ended up doing it like this:
services.AddSingleton(typeof(HubDispatcher<>), typeof(HttpContextSettingHubDispatcher<>));
public class HttpContextSettingHubDispatcher<THub> : DefaultHubDispatcher<THub> where THub : Hub
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextSettingHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext,
IOptions<HubOptions<THub>> hubOptions, IOptions<HubOptions> globalHubOptions,
ILogger<DefaultHubDispatcher<THub>> logger, IHttpContextAccessor httpContextAccessor) :
base(serviceScopeFactory, hubContext, hubOptions, globalHubOptions, logger)
{
_httpContextAccessor = httpContextAccessor;
}
public override async Task OnConnectedAsync(HubConnectionContext connection)
{
await InvokeWithContext(connection, () => base.OnConnectedAsync(connection));
}
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
{
await InvokeWithContext(connection, () => base.OnDisconnectedAsync(connection, exception));
}
public override async Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
{
switch (hubMessage)
{
case InvocationMessage _:
case StreamInvocationMessage _:
await InvokeWithContext(connection, () => base.DispatchMessageAsync(connection, hubMessage));
break;
default:
await base.DispatchMessageAsync(connection, hubMessage);
break;
}
}
private async Task InvokeWithContext(HubConnectionContext connection, Func<Task> action)
{
var cleanup = false;
if (_httpContextAccessor.HttpContext == null)
{
_httpContextAccessor.HttpContext = connection.GetHttpContext();
cleanup = true;
}
await action();
if (cleanup)
{
_httpContextAccessor.HttpContext = null;
}
}
}

AspNetCore.SignalR SendAsync not firing inside OnConnectedAsync

I am having an issue where I would like to send an event to the frontend whenever somebody is connected to the hub, but the notification is not being received on the front end. I think I may be confused between calling methods directly from the hub vs. utilizing the IHubContext. I was not able to find much information related to these versions, so your help will be greatly appreciated!
Package versions:
Server side (.Net Core 2.2): Microsoft.AspNetCore.SignalR (1.1.0)
Client side (React): #aspnet/signalr:1.1.0
So this is my example Hub:
public class MyHub: Hub<IMyHub>
{
public override async Task OnConnectedAsync()
{
// This newMessage call is what is not being received on the front end
await Clients.All.SendAsync("newMessage", "test");
// This console.WriteLine does print when I bring up the component in the front end.
Console.WriteLine("Test");
await base.OnConnectedAsync();
}
public Task SendNewMessage(string message)
{
return Clients.All.SendAsync("newMessage", message);
}
}
Now the working call I have so far is in a service, but that is sending "newMessage" like so:
public class MessageService: IMessageService
{
private readonly IHubContext<MyHub> _myHubContext;
public MessageService(IHubContext<MyHub> myHubContext)
{
_myHubContext = myHubContext;
}
public async Task SendMessage(string message)
{
// I noticed tis calls SendAsync from the hub context,
// instead of the SendMessage method on the hub, so maybe
// the onConnectedAsync needs to be called from the context somehow also?
await _myHubContext.Clients.All.SendAsync("newMessage", message);
}
}
So the above service method call works and will contact the front end, this is an example of my front end connection in a react component:
const signalR = require('#aspnet/signalr');
class MessageComponent extends React.Component {
connection: any = null;
componentDidMount() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:9900/myHub')
.build();
this.connection.on('newMessage', (message: string) => {
// This works when called from the service IHubContext
// but not OnConncectedAsync in MyHub
console.log(message);
});
this.connection.start();
}
componentWillUnmount() {
this.connection.stop();
}
render() {
...
}
}
This is because you are using a Strongly Typed Hub (https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2#strongly-typed-hubs).
I assume you defined SendAsync on your IMyHub interface and so the server is sending a message with method = SendAsync, arguments = "newMessage", "test". If you removed your IMyHub type then this will work as expected.