How to resolve HostedService in Controller - c#

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)

Related

Scoped services - is it the same copy per request? It feels like multiple copies are being used in my code

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

Injecting singleton scoped dependency in SignalR hub

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

Entity Framework Core - disposed when trying to run 2nd Query

I have an issue with a database context being disposed. I have set up the databases like the below in the Configure services method. The code has been simplified to hopefully make it easier to read.
public void ConfigureServices(IServicesCollection services)
{
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database1")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database2")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database3")));
Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database4")));
Services.AddScoped<IQueryService1, ConcreteService1>();
Services.AddScoped<IQueryService1, ConcreteService2>();
Services.AddScoped<IQueryService1, ConcreteService3>();
Services.AddScoped<IQueryService1, ConcreteService4>();
}
Now in one of the controllers I inject the relevant services that are required.
[Produces("application/json")]
[Route("api/[controller]/[action]
public class DigitalFinesController : Controller
{
private readonly IQueryService1 _Service1;
public DigitalFinesController(IConfiguration configuration, IQueryServices1 QueryService1)
{
_Service1 = QueryService1;
}
[Authorize]
[HttpPost]
[ActionName("SubmitFine")]
[ProducesResponseType(200)]
[ProducesResponseType(401)]
public async Task<IActionResult> SubmitFine([FromBody] Models.DigitalFine fine)
{
//This is a simple version of my issue
var vehicles = _Service1.Vehicles.FirstOrDefault(p=> p.vrm == "OX7 DFG");
if(vehicle == null)
{
return BadRequest("Vehicle is missing");
}
var fleet = _Service1.Fleets.FirstOrDefault(p=> p.Code = "MyCode");
}
}
And once I get to the second query I get the following exception
System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
And I am stumped to understand why this is happening. Can anyone please give me a pointer to fix this?
Many thanks
Simon
I think it may have to do with how you're registering it. Try registering it with AddSingleton instead of AddScoped
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes-and-registration-options
Scoped
Scoped lifetime services are created once per request.
Warning
When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don't inject via constructor injection because it forces the service to behave like a singleton. For more information, see ASP.NET Core Middleware.
Singleton
Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance. If the app requires singleton behaviour, allowing the service container to manage the service's lifetime is recommended. Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.
Warning
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
.AddScoped will dispose after lifetime of the request, try changing to singleton or transient:
Please see documentation:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
At this section:
In the sample app, the IMyDependency service is registered with the
concrete type MyDependency. The registration scopes the service
lifetime to the lifetime of a single request. Service lifetimes are
described later in this topic.
Warning
When using a scoped service in a middleware, inject the service into
the Invoke or InvokeAsync method. Don't inject via constructor
injection because it forces the service to behave like a singleton.
For more information, see ASP.NET Core Middleware.

Is changing contextLifetime to Singleton a correct solution to fix the following error?

I am using Asp.net Core 2. Consider the following classes:
public class BlogDbContext: DbContext
{
.....
}
public interface IBlogData { ... }
public class BlogData : IBlogData
{
private BlogDbContext _context;
public BlogData(BlogDbContext context) { ... }
.......
}
When I used the default value contextLifetime: ServiceLifetime.Scoped as follows,
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddDbContext<BlogDbContext>(...);
.....
services.AddSingleton<IBlogData, BlogData>();
}
Compilation, first migration and first database update were performed without any error. But I got the following error when visiting the page for the first time.
InvalidOperationException: Cannot consume scoped service 'MyProject.Data.BlogDbContext' from singleton 'MyProject.Services.IBlogData'.
Question
Is it correct if I fix the error by changing contextLifetime as follows ?
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddDbContext<BlogDbContext>(...,contextLifetime: ServiceLifetime.Singleton);
.....
services.AddSingleton<IBlogData, BlogData>();
}
Note: this problem is specific to Asp.Net Core 2.0.
It's because you are trying to use a scoped service from a singleton service.
This is new to asp.net core 2.0. Only singleton services can be consumed by a
signleton service.
You need to add BlogData as Scoped.
No, you should generally always used scoped for DbContext in asp.net core that way it gets created once per request and is automatically disposed for you at the end of the request.
You are not really showing the code where the error is happening, but my guess is it is happening because you are running some code in startup to run the migrations. If you confirm that or show the code where the error is actually happening I could offer more help
Old question without a great answer.
The reason you get this error is because a scoped service has to be recreated every time a new page request is made (Atleast within ASP.net). How it does that is everytime a service is "requested" by way of injection (For example within a constructor), it caches the first time it's requested, then for subsequent requests, it simply returns that same instance. At the end of the page load, it trashes this instance.
Now a singleton is instead cached that first time it's requested, but it's never disposed. Every request ever made for the service will return the exact same instance.
The problem is if you have a parent service that is singleton that then references a child service that is scoped. That first request, the parent and child are created. The second request, well the parent is a singleton so it isn't recreated. Now we have a problem, because the child service is scoped, how can it be created for each request if the thing that is requesting it (And thus kicking off the DI), is a singleton? It can't. So an exception is thrown.
Interestingly, it is more about saving yourself from hanging yourself more than anything. For example, if you replace the scoped instance with a transient one, you will still have the same "problem", but it won't throw an exception.
More info here if you need further examples : https://dotnetcoretutorials.com/2018/03/20/cannot-consume-scoped-service-from-singleton-a-lesson-in-asp-net-core-di-scopes/

How to invoke WCF service with own constructor using my InstanceProvider

I am kind of new in implementing and using WCF services and extremely new (and apparently clueless) in DI.
I have WCF Services which are having constructors. The parameters of the constructors could only come in runtime from the Client application (Web server).
Something like this:
In Application server:
public class MyService : IMyService {
private IUserContext userContext;
public MyService(IUserContext uContext) {
this.userContext = uContext;
}
public DoWork() {
... // uses uContext
}
}
In Web server can only see IMyService and not the implementation of the MyService. The code would be something like this (oversimplified console app):
class Program {
static void Main(string[] args) {
var factory = new ChannelFactory<IMyService>("MyServiceEndpoint"); // MyServiceEndpoint correctly defined in config file
var client = factory.CreateChannel();
client.DoWork();
((IClientChannel)client).Close();
factory.Close();
}
}
First WCF "forced" me to use parameter-less constructor in the implementation of MyService in order to test it I added that by initializing the UserContext object. Of course I don't have the necessary info to create the object in compile time so this won't help me.
I proceeded with using this solution creating my own ServiceHostFactory, ServiceHost and IInstanceProvider where IDependency is an interface IUserContext which is implemeted by my UserContext class.
This works as expected, I registered in my svc file the custom factory, I don't need parameter-less constructor anymore. However since I don't know how to pass my UserContext to the InstanceProvider I only get a default UserContext object.
Now my noviceness comes in. I don't know how to invoke MyService by passing in the UserContext which lives in the web server. Do I also need own ChannelFactory?
Can someone direct me in the right way by updating the web server dummy code?
Thanks!
Remark: I don't want UserContext to be a parameter of the DoWork() method, because that would mean changing the parameter list of all my services and all calls...
The notion of constructors does not exist on the wire (no matter what transport you are using). For that reason you will never be able to make the client invoke a particular constructor. This is simply not part of the design of WCF (also not part of SOAP).
Don't use constructor parameters that are provided by the client. Or, make the service class have a parameterless ctor and make all service methods accepts the former constructor parameters as normal parameters.
You can also transmit common parameters as SOAP headers. That saves you changing the signature of all service methods.

Categories