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

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.

Related

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).
​

SignalR context within core3.1 controller - no context.clients

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.

Transferring objects across service provider scopes

I have a CorrelationIdMiddleware that is inspecting incoming request headers and setting a scoped CorrelationId later propagated to all HttpClients.
public class CorrelationId {
public string Value { get;set; }
}
public void ConfigureServices(IServiceCollection services) {
...
services.AddScoped<CorrelationId>();
...
}
I have run into a use case where I need to create an isolated scope around a section of code, but would like the CorrelationId from the scope of the http request to propagate into the isolated scope (The isolated scope has an HttpClient which I would like to have the same header attached).
I would like to spawn off a background Task that is created from DI w/ any required dependencies and for any HttpClients to have headers injected via HttpClientFactory plugins.
public Controller {
public Controller(IServiceProvider serviceProvider, CorrelationId correlationId) { ... }
public IActionResult PostTask() {
var isolatedScope = _serviceProvider.CreateScope();
var action = () => {
using(isolatedScope) {
var backgroundJob = isolatedScope
.ServiceProvider
.GetRequiredService<IBackgroundJob>();
backgroundJob.Execute();
// scopedCorrelationId =/= correlationId
// how can i get correlationId to jump scopes?
}
};
return Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
}
Is there a way to transfer certain objects into the isolated scope? Ideally without having to know the typeof(object) i need to transfer.
It is not possible to transfer objects between the parent scope and the isolated scope.
Information like the CorrelationId arriving with the headers of a request better fits with the HttpContext or with an AsyncLocal variable if it needs to be propagated through an async execution flow.

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.

Asp.Net Core StateLess Serivce Calling StateLess Serice in Azue Service Fabric

I am trying to call a stateless service from Asp.Net Core Stateless API. I am not able to reach the methods in Stateless Service.
This is the Controller action method which will call the stateless service method.
// GET api/values
[HttpGet]
public async Task<string> GetAsync()
{
var repository = ServiceProxy.Create<IRepository>(
new Uri("fabric:/Application1/Stateless1"));
return await repository.GetSomething();
//return new string[] { "value1", "value2" };
}
This is the method in Stateless service.
internal sealed class Stateless1 : StatelessService, IRepository
{
public Stateless1(StatelessServiceContext context)
: base(context)
{ }
public async Task<string> GetSomething()
{
return await Task.FromResult("HELLO FROM SERVICE!");
}
}
And the listener code is
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return this.CreateServiceInstanceListeners();
}
I am able to hit the controller Get method but it is struck at repository.GetSomething() method and not able to reach that method. I don't know what I am missing here.
Any pointers will be very helpful. Thanks in advance
Update:
Manifest file:
You need to change your CreateServiceInstanceListeners...
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return this.CreateServiceRemotingInstanceListeners();
}
It needs the remoting listener as using the ServiceProxy is a remoting call.