avatarTepes Alexandru

Summary

This article explains how to build an asynchronous queue system using Redis and ASP.NET.

Abstract

This article provides a step-by-step guide for building an asynchronous queue system using Redis and ASP.NET. The system will support adding items to a queue and processing them later on. The project will be built as a Web API using the .NET CLI and the StackExchange.Redis library. The article covers registering Redis into ASP.NET, adding jobs to the queue, building API endpoints, and processing jobs from the queue. The author recommends using Redis Insights to view the queue entries via a pretty interface.

Opinions

  • The author believes that building an asynchronous queue system using Redis and ASP.NET is an amazing project for Redis beginners.
  • The author suggests using Swagger, Postman, or Insomnia to call the API endpoints.
  • The author recommends using Redis Insights to view the queue entries via a pretty interface.
  • The author encourages readers to follow them on Medium to read more interesting articles.
  • The author promotes an AI service that provides the same performance and functions as ChatGPT Plus(GPT-4) but is more cost-effective.

Build an Async Queue System using Redis & ASP.NET

The perfect project for Redis beginners

If you’re new to Redis and have just enough experience in ASP.NET, then this is an amazing project to familiarize yourself with the concepts behind Redis.

We will be building an Async Queue System throughout this article. This system will support adding items to a queue and processing them later on.

It’s Async because processing items in the queue is not coupled with putting items in the queue. You can put items in the queue and not have to worry about when they are going to be processed. This is also commonly referenced as “Fire and Forget.”

Setting Up the Project

To make things more interactive, the project will be built as a Web API.

By using the .NET CLI, you can quickly scaffold one:

dotnet new webapi -o QueueAPI

We only require a single external library, which is Redis.

dotnet add package StackExchange.Redis

All jobs inside the queue will have the same properties, which are encapsulated into the following class:

public class JobModel
{
    public string Id { get; set; } = Guid.NewGuid().ToString();
    public string Name { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
}

Each job has a unique ID, name, and status - “queued” or “in progress”.

Registering Redis into ASP.NET

In order to use Redis with any service in the application at a later stage, it is necessary to register it as a Singleton.

You will also need to provide the login credentials to the Redis instance, whether it is hosted on a cloud or locally.

// Program.cs
var connectionOptions = new ConfigurationOptions
{
    EndPoints = { "<your-redis-endpoint>" },
    Password = "<your-redis-password>"
};

builder.Services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(connectionOptions));

In practice, the connection between Redis and the API we are building will be handled by a “Connection Multiplexer,” which we’ll use to obtain an instance of the Redis Database later on.

In case you don’t have a Redis Database, you can get one for free hosted in the cloud at https://app.redislabs.com/.

In the Redis Queue we’re going to build, all new jobs will be added to the tail of the queue, and jobs will be processed from the head of the queue.

Visualization of the Redis Queue

Adding Jobs to the Queue

We have everything ready to start interacting with Redis. The next step is to create a class and inject Redis into it so that we can add things to the queue.

// JobEnqueuerService.cs

public class JobEnqueuerService : IJobEnqueuerService
{
    private readonly IDatabase _redisDatabase;
    public JobEnqueuerService(IConnectionMultiplexer multiplexer)
    {
        _redisDatabase = multiplexer.GetDatabase();
    }
}

The service will have two methods: one to add a job to the queue and the other to fetch all jobs currently in the queue, along with their status.

// JobEnqueuerService.cs

// Add job at the back of the queue
public async Task EnqueueJobAsync(JobModel job)
{
    await _redisDatabase.ListLeftPushAsync("jobQueue", JsonSerializer.Serialize(job));
    await _redisDatabase.HashSetAsync(job.Id, "status", "queued");
}

// Fetch all jobs in the queue, along with their status
public async Task<List<JobModel>> GetJobsAsync()
{
    var jobs = await _redisDatabase.ListRangeAsync("jobQueue");
    var jobList = new List<JobModel>();
    foreach (var job in jobs)
    {
        var redisJob = JsonSerializer.Deserialize<JobModel>(job);
        redisJob.Status = _redisDatabase.HashGet(redisJob.Id, "status");
        jobList.Add(redisJob);
    }
    return jobList;
}

Building the API Endpoints

To call the two methods we just built, we’ll build two simple API endpoints that can be called using Swagger, Postman, or Insomnia.

// JobEnqueuerController.cs

public async Task<IActionResult> EnqueueTask(JobModel job)
{
    await _jobEnqueuer.EnqueueJobAsync(job);
    return Ok();
}

[HttpGet]
public async Task<IActionResult> GetTasks()
{
    var tasks = await _jobEnqueuer.GetJobsAsync();
    return Ok(tasks);
}

Now that we have the two methods in a separate class, we can go back to the controller and update the two API endpoints.

// JobEnqueuerController.cs

[ApiController]
[Route("[controller]")]
public class JobEnqueuerController : ControllerBase
{
    // Inject the service
    private readonly IJobEnqueuerService _jobEnqueuer;

    public JobEnqueuerController(IJobEnqueuerService jobEnqueuer)
    {
        _jobEnqueuer = jobEnqueuer;
    }

    [HttpPost]
    public async Task<IActionResult> EnqueueTask(JobModel job)
    {
        await _jobEnqueuer.EnqueueJobAsync(job);
        return Ok();
    }
    
    [HttpGet]
    public async Task<IActionResult> GetTasks()
    {
        var tasks = await _jobEnqueuer.GetJobsAsync();
        return Ok(tasks);
    }
}

Make sure to register the service as scoped before running the application:

// Program.cs

builder.Services.AddScoped<IJobEnqueuerService, JobEnqueuerService>();

Processing Jobs from the Queue

Now that we have jobs in the queue, we can process them one by one and remove them!

This will be done by a BackgroundService in ASP.NET, which runs constantly in the background. The service will constantly check if there are new jobs in the queue, and process the oldest one.

This class has two methods: one to fetch the oldest job in the queue and another to remove it.

// JobDequerService.cs

public async Task<JobModel?> DequeueJobAsync()
{
    var job = await _redisDatabase.ListGetByIndexAsync("jobQueue", 0);
    if (!job.HasValue)
    {
        return null;
    }
    var redisJob = JsonSerializer.Deserialize<JobModel>(job);
     
    // change status from "queued" to "in progress"
    await _redisDatabase.HashSetAsync(redisJob.Id, "status", "in progress");
    return redisJob;
}

public async Task CompleteJobAsync(JobModel job)
{
    await _redisDatabase.ListRemoveAsync("jobQueue", JsonSerializer.Serialize(job));
    await _redisDatabase.KeyDeleteAsync(job.Id);
}

By inheriting from BackgroundService, you can define what logic needs to be constantly run in the background in a method named ExecuteAsync:

// JobDequerService.cs

// Inject Redis Database
private readonly IDatabase _redisDatabase;

public JobDequerService(IConnectionMultiplexer multiplexer)
{
    _redisDatabase = multiplexer.GetDatabase();
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        var job = await DequeueJobAsync();
        if (job != null)
        {
            // Simulate time to complete job
            await Task.Delay(5000, stoppingToken);
            await CompleteJobAsync(job);
        }
    }
}

Make sure that the service is registered as a Hosted Service:

// Program.cs

builder.Services.AddHostedService<TaskDequerService>();

And that’s it! You can now play with it via Swagger or by building a user interface in your favorite front-end framework. You can also use the service Redis Insights to view the queue entries via a pretty interface.

Thank you so much for reading this article about Redis! 💻❤️

If you’d like to be up to date and read more articles like this one, be sure to follow me! In the meantime, you can read more interesting articles on my Medium profile 😀

See you in the next one! 👋

Redis
Aspnetcore
Dotnet
Data Structures
Backend Development
Recommended from ReadMedium