-2

I have background worker which implements BackgroundService. I added this service in the beginning of my program.cs with

builder.Services.AddHostedService<MyService>();

How can I get information from this service to my Minimal API? My background worked implements boolean for one status which I'd want to read via API. Previously, I have used keyed services, but it does not seem to exist for hosted service.

3
  • Not sure how keyed services could help here. Can you please elaborate?
    – Guru Stron
    Commented Mar 4 at 23:14
  • This question is similar to: How to pass data from a controller to a background service. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem.
    – JuanR
    Commented Mar 4 at 23:41
  • FYI AddHostedService<T> is just syntactic sugar for AddSingleton<IHostedService,T>(). And a simple AddSingleton<I>(sp => sp.GetService<T>()) can ensure you don't create 2 different instances of the same service. Commented Mar 5 at 1:35

4 Answers 4

2

Since hosted services are registered as singleton, one good approach may be to create a separate singleton class to hold the shared data. So your background service (MyService) can inject this class and update its state. Then you can use anywhere in your API to read this data.

1

To add my few cents here, i would define interface inheriting from IHostedService, adding to this interface just "get status" feature, as below:

public interface ITimedHostedService : IHostedService
{
    string GetState();
}

public class TimedHostedService : ITimedHostedService, IDisposable
{
    private int executionCount = 0;
    private Timer? _timer = null;

    public string GetState() => $"executionCount = {executionCount}";

    public async Task StartAsync(CancellationToken stoppingToken) => 
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
    
    private void DoWork(object? state) => _ = Interlocked.Increment(ref executionCount);

    public async Task StopAsync(CancellationToken stoppingToken) => _timer?.Change(Timeout.Infinite, 0);

    public void Dispose() => _timer?.Dispose();
}

And then registartion becomes also very simple:

builder.Services.AddSingleton<ITimedHostedService, TimedHostedService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ITimedHostedService>());

Then you can use it in your endpoints (controllers, or minimal API) by simply injecting ITimedHostedService and then use GetResult method.

1
  • Basically what I have suggested but breaks the interface segregation. But on other hand doesn't register the class itself.
    – Guru Stron
    Commented Mar 6 at 1:51
1

Refer to the following sample to create a shared singleton service to share the status.

public class MyServiceState
{
    public bool IsRunning { get; set; }
}

Background services:

public class MyService : BackgroundService
{
    private readonly MyServiceState _state;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly CancellationTokenSource _cts = new();

    public MyService(MyServiceState state, IServiceScopeFactory scopeFactory)
    {
        _state = state;
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _state.IsRunning = true;
        Console.WriteLine("background service start running");
        // Create a linked token source to cancel after 20 seconds
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _cts.Token);
        linkedCts.CancelAfter(TimeSpan.FromSeconds(20));

        try
        {
            while (!linkedCts.Token.IsCancellationRequested)
            {
                Console.WriteLine("background service running");
                // Simulate work
                await Task.Delay(2000, linkedCts.Token);
            }
        }
        catch (OperationCanceledException)
        {
            // Expected when the service is stopped
        }
        finally
        {
            _state.IsRunning = false;
            Console.WriteLine("background service stopped");
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _cts.Cancel();
        await base.StopAsync(cancellationToken);
    }
}

Then, register them in the Program.cs file:

// Register the shared state as a singleton
builder.Services.AddSingleton<MyServiceState>();
 
// Register the background service
builder.Services.AddHostedService<MyService>();

Finally, create a minimal API to expose the status:

// Minimal API to expose the status
app.MapGet("/status", (MyServiceState state) => Results.Ok(new { IsRunning = state.IsRunning }));

The result as below:

When the background service is running, we can see the API response "true":

result1

Once the background service stop, the status is false.

result2

Besides, you can also store the status in the database from the Background Service, then in the minimal API access the database to get the latest status. Using this method, you might need to use the IServiceScopeFactory or IServiceProvider to get the required service such as DbContext, and then insert/query data from the database. Here are some resources, you can check them:

Constructor injection behavior

Dependency injection in ASP.NET Core

1

Hosted services are registered as collection of IHostedService. Depending on the case you can use the the "delegating" approach - i.e. register service (as singleton) first and then reabstract it using registrations with implementation factory:

// IApiStatusProvider - interface to use in Minimal APIs endpoints
public class MyService : IApiStatusProvider, IHostedService 
{...}

services.AddSingleton<MyService>();
services.AddSingleton<IApiStatusProvider>(provider => provider.GetRequiredService<MyService>());
services.AddHostedService<MyService>(provider => provider.GetRequiredService<MyService>());

You can skip the IApiStatusProvider interface:

services.AddSingleton<MyService>();
services.AddHostedService<MyService>(provider => provider.GetRequiredService<MyService>());

And resolve the service itself but personally I prefer the approach with extra abstraction so unneeded details will not be exposed to the API (plus benefit of mocking in tests).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.