Click here to Skip to main content
15,867,921 members
Articles / Web Development / ASP.NET / ASP.NET Core

Background Worker in ASP.NET Core

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
12 Nov 2018CPOL3 min read 9.3K   4   3
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.

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
When asked, I always see myself as a .Net Developer because of my affinity for the Microsoft platform, though I do pride myself by constantly learning new languages, paradigms, methodologies, and topics. I try to learn as much as I can from a wide breadth of topics from automation to mobile platforms, from gaming technologies to application security.

If there is one thing I wish to impart, that that is this "Always respect your craft, your tests and your QA"

Comments and Discussions

 
QuestionWhat's wrong with HostedServices? Pin
Artur Gor13-Nov-18 18:48
Artur Gor13-Nov-18 18:48 
Just implement IHostedService and register it in DI. Or I'm missing something?
AnswerRe: What's wrong with HostedServices? Pin
Bogdan Marian21-Nov-18 10:02
professionalBogdan Marian21-Nov-18 10:02 
AnswerRe: What's wrong with HostedServices? Pin
Vlad Neculai Vizitiu28-Jan-19 10:11
Vlad Neculai Vizitiu28-Jan-19 10:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.