I'm using Microsoft.AspNetCore.SignalR (1.1.0)
I have some services in startup.cs:
services.AddSingleton<IHostedService, MySingletonService>();
services.AddScoped<MyScopedService >();
Now I want to use this service in my hub, so I inject it:
private readonly MyScopedService _cs;
private readonly MySingletonService _ss;
public MyHub(MyScopedService cs, MySingletonService ss)
{
_cs = cs;
_ss= ss;
}
This works only for the scoped service, as soon as the service is singleton the hub never gets called and cannot connect in the browser. Why is this not possible? I just want the existing instance of the singleton service, call a method and then let it go again.
This has nothing to do with SignalR. Since I created my service as an IHostedService I would also need to inject it as an IHostedService which is impossible because I have multiple IHostedServices and only want this specific one. The solution is to inject as a normal singleton and then start it.
services.AddSingleton<MySingletonService>();
//Start the background services
services.AddHostedService<BackgroundServiceStarter<MySingletonService>>();
I found the solution here:
https://stackoverflow.com/a/51314147/1560347
Related
I'm having a huge problem with the configuration/dependency injection of an application.
I have a singleton class added through DI with AddSingleton, that has in its constructor a IRequestClient, that is scoped because
busConfigurator.AddRequestClient()
which among other things, has the same effect as AddScoped.
When I start the app, it says
"Cannot consume scoped service 'MassTransit.IRequestClient`1[...]' from singleton '...'.)"
Which absolutely makes sense.
The weirdest thing is that I have another app set up the exact same way, but it just works and I would really like for that class to remain singleton.
My colleague and I spent an entire day trying to find the differences between the two applications, but they are virtually the same in their configurations, so we are having trouble in understanding why one works while the other doesn't.
I'm not entirely sure on what details could be important to better define the problem, so feel free to ask.
We've looked all around the internet trying to find a solution, but it was always "Change singleton to transient", but that's not an option, first because it HAS to be a singleton, otherwise it wouldn't make sense in our app, as that thing is what caches lots of date from our db so we can't just go around keeping on collecting heaps of data, second because the first app works with singleton, not with transient and we'd like to keep it that way
// This method is called in Main()
private static void ConfigureMassTransit(IServiceCollection services)
{
services.AddMassTransit(busConfigurators =>
{
busConfigurators.AddRequestClient<ICacheRepository>();
busConfigurators.AddConsumers(typeof(Program).GetTypeInfo().Assembly);
busConfigurators.UsingRabbitMq((context, cfg) =>
{
cfg.Host(new Uri($"rabbitmq://{Config.Settings.RabbitMq_Host}"), hostConfigurator =>
{
hostConfigurator.Username(Config.Settings.RabbitMq_User);
hostConfigurator.Password(Config.Settings.RabbitMq_Password);
});
cfg.ReceiveEndpoint("myApp", e =>
{
e.ConfigureConsumers(context);
});
});
});
// CacheRepository
public class CacheRepository : ICacheRepository
{
private readonly IClient Client;
public CacheRepository(ICacheRepository client, ILogger<CacheRepository> logger)
{
this.client = client;
this.logger = logger;
}
}
When a dependency is scoped, the implication is that a new instance is needed for each scope (which is usually an incoming HTTP request or message.) It implies that the instance should not be re-used for multiple requests.
If you have a singleton that depends on that scoped dependency, that singleton will be created using an instance of that dependency (the request client.) Because that singleton "lives forever," so does the instance of the request client it contains.
The result is that the request client is not supposed to be re-used across different scopes, but now it is. One instance is used forever.
A likely solution is to modify the class that depends on that client so that it doesn't need to be a singleton. You mentioned that it has to be a singleton because it caches data.
How does it cache data? Does it do so by storing data in a private field? If so, perhaps you could make that field static. Now the class instance isn't re-used, but those fields are shared between instances. (Verify that interaction with those fields is thread safe if they may be accessed concurrently.)
Or if there's some other cache mechanism, you could move that into its own dependency and make that a singleton.
Then your class can be scoped. It will depend on the singleton cache, always using the same instance. It will also depend on the scoped request client, using a new instance for each scope.
You could inject IServiceProvider instead, and create a scope when the singleton needs to perform a request. That way, you're sticking to the expected use of the request client.
await using var scope = provider.CreateAsyncScope();
var client = scope.ServiceProvider.GetRequiredService<IRequestClient<T>>();
await client.GetResponse(...);
Grpc.Net.ClientFactory package provides gRPC integration with the IHttpClientFactory and it comes with the convinient extension method used to register gRPC clients: IServiceCollection.AddGrpcClient<TClient>().
The problem is that this method registers TClient with the transient lifetime, which is, in turn, means that it's imposible to inject it into the singleton service. And looks like there is no possibility to configure it somehow (at least the AddGrpcClient source code has nothing on that).
So the question is: how to correclty inject the gRPC client to the singleton service and benefit from the IHttpClientFactory integration at the same time? Should I inject some client factory instead?
How about using named Grpc client to register the client, using like this.
// Register on the Startup.cs
services
.AddGrpcClient<Catalog.CatalogClient>("Catalog", o =>
{
o.Address = new Uri("https://localhost:5001");
});
// Create your service
public class OrderingService : IOrderingService
{
private readonly Catalog.CatalogClient _client;
public OrderingService(GrpcClientFactory grpcClientFactory)
{
_client = grpcClientFactory.CreateClient<Catalog.CatalogClient>("Catalog");
}
}
// Register the service as singleton at Startup.cs
service.AddSingleton<IOrderingService, OrderingService>();
// now _client instance is persist as long as you doesn't do anything weird with it
The grpc client might be a new instance each time (transient), but the underlying HttpClientMessageHandler "is managed".
I see no problems injecting a transient instance into a singleton service.
I'm using Steeltoe to implement Event Messaging, with RabbitMQ, between microservices, but I'm having an issue when I register my Listener service and it not recognizing other DI services.
In my Startup.cs file I have my services being registered as follows:
public void ConfigureServices(IServiceCollection servcies)
{
...
// Add my custom service as a scoped service
services.AddScoped<IMyService>(provider => new MyService());
var rabbitSection = configuration.GetSection(RabbitOptions.PREFIX);
services.Configure<RabbitOptions>(rabbitSection);
services.AddRabbitServices();
services.AddRabbitAdmin();
services.AddRabbitTemplate();
// Add Rabbit Listener Service
services.AddSingleton<MyRabbitListenerService>();
services.AddRabbitListeners<MyRabbitListenerService>();
...
}
... then in my MyRabbitListenerService.cs class:
public class MyRabbitListenerService
{
private readonly IMyService _myService;
public MyRabbitListenerService(IMyService myService)
{
_myService = myService;
}
[RabbitListener("MyQueue")]
public async Task MessageListener(byte[] message)
{
// Do stuff ...
}
}
When I run this, I'm getting an error indicating the IMyService couldn't be injected into the Listener service as it wasn't registered. I can't work out why this isn't working. Is it because I'm trying to inject a scoped service into a singleton service?
UPDATE
I did some testing and changing IMyService from a scoped service to a singleton made it work. Now I need to figure out how to get around this because in my situation it doesn't make sense to register IMyService as a singleton.
The reason for this error is that you cannot consume a scoped service from a singleton. Scoped has per request semantics, which for messaging doesn't make sense. Perhaps you mean AddTransient? That works. If this doesn't work for you, Can you give more detail on why MyService cannot be just transient?
I have a scoped service:
public class GetLatestStatus:IGetLatestStatus{
private HttpClient _httpClient;
private readonly int _status;
public GetLatestStatus(HttpClient httpClient){
_httpClient = httpClient;
_date= GetStatusFromService();
}
public string GetStatus(){
return _status;
}
private string GetStatusFromService(){
Logger.Info($"Calling service...");
var request = new HttpGetRequest{Url = "http://some.service/get/status"};
var result = _httpClient.Get(request).Result;
return result.Status;
}
}
Here is how it is defined in the startup:
public virtual void ConfigureServices(IServiceCollection services){
services.AddScoped<IGetLatestStatus, GetLatestStatus>()
.AddHttpClient<IGetLatestStatus, GetLatestStatus>();
services.AddTransient<ISomeClass1, SomeClass1>();
services.AddTransient<ISomeClass2, SomeClass2>();
services.AddTransient<ISomeClass3, SomeClass3>();
}
It is being used by three transient classes.
The intent of this class is that _status is defined only once, when the request comes in. Then it is stored throughout the lifecycle of the request.
Instead, it seems that GetStatusFromService() is being called three times, one per transient class, when the request first comes in.
How do I make this class work the way I intended? I thought that defining something as a Scoped Service means that there's only one copy for the lifecycle of the request. Thank you all for the help!
TL:DR
It happens because you register GetLatestStatus like this after scoped registration .AddHttpClient<IGetLatestStatus, GetLatestStatus>();
So may create another class to store the status and register it as scoped. Then use the Http Configured service to reach the service from it
According to MSDN;
To configure the above structure, add HttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient() extension method for IServiceCollection. This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. It defines a transient configuration for the HttpMessageHandlerBuilder. This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.
Please check the link for more information https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
I am trying to add a Background Timer in ASP.NET Core 3.0, which periodically executes a task.
Google led me to this, where I implemented the 'Timed background tasks'.
However, I'm stuck in resolving the HostedService in the controller.
I need a specific instance of TimedHealthCheckService so I can call another public function called 'GetAvailableODataUrl()'.
In the startup.cs I use the following code:
services.AddHostedService<TimedHealthCheckService>();
The TimedHealthCheckService obviously implements IHostedService:
public class TimedHealthCheckService : IHostedService, IDisposable
In my controller, I have the following constructor:
public HealthCheckController(ILogger<HealthCheckController> logger, IHostedService hostedService)
{
this.logger = logger;
this.timedHealthCheckService = hostedService as TimedHealthCheckService;
}
However, when I start my WebAPI, the timedHealthCheckService is always null.
It seems another IHostedService gets injected into the constructor. By checking hostedService, it is actually an object of type GenericWebHostService.
If I change the controller's constructor to:
public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)
I am getting the following error:
Unable to resolve service for type 'HealthCheck.Web.TimedHealthCheckService' while attempting to activate 'HealthCheck.Web.Controllers.HealthCheckController'.
I also tried services.AddSingleton<IHostedService, TimedHealthCheckService>(); with the same result.
Try these two lines in startup.cs:
services.AddSingleton<TimedHealthCheckService>();
services.AddHostedService<TimedHealthCheckService>(provider => provider.GetService<TimedHealthCheckService>());
The first line above tells the service provider to create a singleton and give it to anyone who wants a TimedHealthCheckService, like your controller's constructor. However, the service provider is unaware that the singleton is actually an IHostedService and that you want it to call StartAsync().
The second line tells the service provider that you want to add a hosted service, so it'll call StartAsync() when the application starts running. AddHostedService accepts a Func<IServiceProvider,THostedService> callback. The callback we provide fetches the singleton TimedHealthCheckService from the service provider and returns it back to the service provider as an IHostedService. The service provider then calls its StartAsync() function.
And in your controller:
public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)