Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Background Worker in ASP.NET Core

0.00/5 (No votes)
12 Nov 2018 1  
How to add functionality to an ASP.NET Core application outside of a request

Introduction

In this post, we are going to discuss how we can add functionality to an ASP.NET Core application outside of a request.

The code for this post can be found here.

The Story

As some, if not all of you know, web servers usually only work in the context of a request. So when we deploy an ASP.NET Core (or any other web server) and it doesn’t receive a response to a request to the server, then, it will stay insert on the server waiting for a request, be it from a browser or an API endpoint.

But there might be occasions when, depending on the application that is being built, we need to do so some work outside of the context of a request. A list of such possible scenarios goes as follows:

  • Serving notifications to users
  • Scraping currency exchange rates
  • Doing data maintenance and archival
  • Communicating with a non-deterministic external system
  • Processing an approval workflow

Though there are not a whole lot of scenarios in which a web server would do more than just serve responses to requests, otherwise this would be common knowledge, it is useful to know how to embed such behavior in our applications without creating worker applications.

The Setup

The Project

First, let’s create an ASP.NET Core application, in my example, I created a 2.1 MVC Application.

We’re going to use this project to create a background worker as an example.

The Injectable Worker

Though this step is not mandatory for our work, we will create a worker class that will be instantiated via injection so we can test out the worker class and keep it decoupled from the main application.

namespace AspNetBackgroundWorker
{
    using Microsoft.Extensions.Logging;

    public class BackgroundWorker
    {
        private readonly ILogger _logger;

        private int _counter;

        public BackgroundWorker(ILogger logger)
        {
            _counter = 0;
            _logger = logger;
        }

        public void Execute()
        {
            _logger.LogDebug(_counter.ToString());
            _counter++;
        }
    }
}

Notice that for this example, this class doesn’t do much except log out a counter, though the reason we’re using an ILogger is so that we can see it in action with it being created and having dependencies injected.

Registering the Worker in the Inversion of Control Container

Inside the ConfigureServices method from the Startup.cs file, we will introduce the following line:

services.AddSingleton();

It doesn’t need to be a singleton, but it will serve well for our purpose.

The Implementation

Now that we have a testable and injectable worker class created and registered, we will move on to making it run in the background.

For this, we will be going into the Program.cs file and change it to the following:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace AspNetBackgroundWorker
{
    using System;
    using System.Threading;

    using Microsoft.Extensions.DependencyInjection;

    public class Program
    {
        public static void Main(string[] args)
        {
            // We split up the building of the webHost with running it 
            // so that we can do some additional work before the server actually starts
            var webHost = CreateWebHostBuilder(args).Build(); 

            // We create a dedicated background thread that will be running alongside the web server.
            Thread counterBackgroundWorkerThread = new Thread(CounterHandlerAsync) 
            {
                IsBackground = true
            };

            // We start the background thread, providing it with the webHost.Service 
            // so that we can benefit from dependency injection.
            counterBackgroundWorkerThread.Start(webHost.Services); 

            webHost.Run(); // At this point, we're running the server as normal.
        }

        private static void CounterHandlerAsync(object obj)
        {
            // Here we check that the provided parameter is, in fact, an IServiceProvider
            IServiceProvider provider = obj as IServiceProvider 
                                        ?? throw new ArgumentException
            ($"Passed in thread parameter was not of type {nameof(IServiceProvider)}", nameof(obj));

            // Using an infinite loop for this demonstration but it all depends 
            // on the work you want to do.
            while (true)
            {
                // Here we create a new scope for the IServiceProvider 
                // so that we can get already built objects from the Inversion Of Control Container.
                using (IServiceScope scope = provider.CreateScope())
                {
                    // Here we retrieve the singleton instance of the BackgroundWorker.
                    BackgroundWorker backgroundWorker = scope.ServiceProvider.GetRequiredService();

                    // And we execute it, which will log out a number to the console
                    backgroundWorker.Execute();
                }

                // This is only placed here so that the console doesn't get spammed 
                // with too many log lines
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup();
    }
}

I have provided some inline comments so that it’s easier to follow along.

To test out this code, we need to run the application in console/project mode so that we can follow along on the console window.

Conclusion

Although this example doesn’t do much in the sense of a real-life scenario, it does show us how to make a background thread and run it alongside the web server.

Also, it is not mandatory to run the thread from the Program.cs file, but since this will be a background worker that will do its things forever, I thought it would have been a nice spot. Some other places this could be used at would be:

  • From a middleware
  • From a controller
  • Creating a class that can receive methods and delegates to run ad-hoc and arbitrary methods.

And since we are making use of IServiceProvider, we can use all the registered services at our disposal, not only the ones we registered but also the ones the web server registered, for example Logger, Options, DbContext.

I personally used it in a scenario where a signalR hub would send out periodical notifications to specific users, and that needed to run outside the context of a web request.

I hope you enjoyed this post and found it useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here