ASP.NET Core introduced hosted services, which are a great way to execute tasks in the background.
An ASP.NET Core web application can use them. This is perfect for updating background-running software that may affect all users.
As an alternative, a Worker Service template can be used to run them. This can be configured as a Windows service and is intended to run in the background.
The addition of a hosted service to an ASP.NET Core application will be the main topic of this article. We’ll look at how to make one, add it to the instance of IServiceCollection
, and use dependency injection inside of it.
Creating a hosted service
There are two methods available to us for creating a hosted service. We can accomplish this first by inheriting the IHostedService interface. We have to incorporate the StartAsync
and StopAsync
methods into our class as part of that.
using Microsoft.Extensions.Hosting; namespace RoundTheCode.HostedServiceExample { public class MyHostedService : IHostedService { public Task StartAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }
After that, we can use the StartAsync
method to begin our tasks. One thing to keep in mind, though, is that we would need to use Task to launch our tasks individually.Hurry.
A task that is infinite will never finish if we choose to run it directly in the StartAsync
method. Before launching the web application, an ASP.NET Core application must wait for the StartAsync
method to finish.
This is the way to trigger a different task in the StartAsync
without causing the web application to launch slowly.
using Microsoft.Extensions.Hosting; namespace RoundTheCode.HostedServiceExample { public class MyHostedService : IHostedService { public Task StartAsync(CancellationToken cancellationToken) { Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { await Task.Delay(new TimeSpan(0, 0, 5)); // 5 second delay } }); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }
Inheriting the BackgroundService class
You can also inherit the BackgroundService
class as an alternative. An abstract class that also inherits the IHostedService
interface is called BackgroundService
.
It’s advantageous to inherit this service because it eliminates the need for us to implement the StartAsync
and StopAsync
methods. But we have the option to override them if we so choose.
How then do we carry out an assignment in this class? ExecuteAsync
is an abstract method in the BackgroundService class. We must override this abstract method into our background service.
using Microsoft.Extensions.Hosting; namespace RoundTheCode.HostedServiceExample { public class MyBackgroundService : BackgroundService { protected override Task ExecuteAsync(CancellationToken stoppingToken) { return Task.CompletedTask; } } }
Additionally, we don’t have to run a separate task and run the risk of the web application not starting, unlike when we run a task in StartAsync
. By using the async
modifier, we can make the ExecuteAsync
task asynchronous and execute our task inside of it.
using Microsoft.Extensions.Hosting; namespace RoundTheCode.HostedServiceExample { public class MyBackgroundService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.CompletedTask; } } }
Add the hosted service to IServiceCollection
The IServiceCollection
interface has an extension method named AddHostedService
that is used for configuring services.
We don’t specify the interface when adding the hosted service because the AddHostedService
extension method expects a class that inherits IHostedService
.
The IServiceCollection
type argument that is passed into the ConfigureServices
method can be used to add the Startup.cs
file to the web application, which will be the case if using.NET 5 or lower.
namespace RoundTheCode.HostedServiceExample { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add dependency injection. services.AddHostedService<MyBackgroundService>(); ... } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... } } }
In the event that the web application was made with the .NET 6 template and runs on a.NET 6 platform, the following can be added to the Program.cs
file:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddHostedService<MyBackgroundService(); var app = builder.Build(); ... app.Run();
The hosted service will function in the same manner regardless of the template that is being used.
How dependency injection works
The singleton service lifetime is used to add hosted services to the DI container. It can therefore only resolve classes that are injected into it, such as singleton and transient service lifetime classes.
The hosted service would throw a runtime error when the hosted background service started if we were to inject a scoped service lifetime class into it. This is because the hosted service would not know which scope it belongs to.
However, when a background task is executed, we can define our own scope and resolve a scoped service from that scope.
We must inject the IServiceProvider
instance into the hosted service in order to accomplish that. We can make a new scope instance within that interface by using the CreateScope
method. From there, we can resolve a scoped service into our background task using our newly created scope instance.
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace RoundTheCode.HostedServiceExample { public class MyBackgroundService : BackgroundService { private readonly IServiceProvider _serviceProvider; public MyBackgroundService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { using (var scope = _serviceProvider.CreateScope()) { var myScopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); } await Task.Delay(new TimeSpan(0, 1, 0)); } } } }
Worker Service template
Is it necessary for an ASP.NET Core web application to have a background service? It’s beneficial if we have to provide data to every website user. However, the stability of the web application may be impacted if a background service experiences performance problems.
Using a Worker Service template, which is a background service application only, is a better option. Its ability to be utilized as a Windows service is another advantage.