Skip to content

Using Hangfire and MediatR as a Message Dispatcher

Two popular libraries in Hangfire and MediatR can be used together to create a pretty powerful out-of-process messaging dispatcher. I’ve covered this a bit many years ago but I figured I’d give it a refresh since it’s a bit easier in the world of ASP.NET Core.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Hangfire and MediatR Bridge/Wrapper

The first thing you need to do is create a bridge/wrapper around MediatR. At first this may seem completely pointless, but it has a purpose. Hangfire has a lot extensibility in terms of how jobs are executed that are often times defined by using attributes. Because we don’t own the MediatR library, we need to have our own class that we can defined these attributes on.

Here’s a simple start to our Bridge/Wrapper

using System.ComponentModel;
using System.Threading.Tasks;
using MediatR;
namespace Hangfire.MediatR
{
public class MediatorHangfireBridge
{
private readonly IMediator _mediator;
public MediatorHangfireBridge(IMediator mediator)
{
_mediator = mediator;
}
public async Task Send(IRequest command)
{
await _mediator.Send(command);
}
[DisplayName("{0}")]
public async Task Send(string jobName, IRequest command)
{
await _mediator.Send(command);
}
}
}

In the example above, I have an overload for Send() that accepts the jobName as the first parameter. The DisplayName attribute will be used by Hangfire to show in the UI Dashboard the name of the job. This is a simple example of why we need this wrapper.

MediatR Extensions

The next piece of the puzzle is creating extension methods to be able to use Hangfire to create background jobs. In the example below I’ve created Enqueue() extension methods that use the Hangfire BackgroundJobClient to enqueue work using our Bridge/Wrapper

using MediatR;
namespace Hangfire.MediatR
{
public static class MediatorExtensions
{
public static void Enqueue(this IMediator mediator, string jobName, IRequest request)
{
var client = new BackgroundJobClient();
client.Enqueue<MediatorHangfireBridge>(bridge => bridge.Send(jobName, request));
}
public static void Enqueue(this IMediator mediator,IRequest request)
{
var client = new BackgroundJobClient();
client.Enqueue<MediatorHangfireBridge>(bridge => bridge.Send(request));
}
}
}

Hangfire Serialization

In the BackgroundJobClient.Enqueue() above, Hangfire will use Json.NET to serialize the IRequest that we are passing to Send() that ultimately gets put into storage. When Hangfire then pulls that job out of storage to perform the work, it needs to deserialize it. Because it’s just an IRequest it has no way to turning that into a concrete type.

To handle this, we need to configure Hangfire to add type handling when it serializes/deserializes.

using Newtonsoft.Json;
namespace Hangfire.MediatR
{
public static class HangfireConfigurationExtensions
{
public static void UseMediatR(this IGlobalConfiguration configuration)
{
var jsonSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
configuration.UseSerializerSettings(jsonSettings);
}
}
}

ConfigureServices

The last thing we need to do is actually configure Hangfire in the ConfigureServices of either your ASP.NET Core Startup or your HostBuilder.

Here’s an example of my Worker process. This is just a console app that is purely a Hangfire server that just processes jobs from Hangfire.

using Hangfire;
using Hangfire.MediatR;
using Microsoft.Extensions.Hosting;
using Sales;
using Shipping;
namespace Worker
{
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSales();
services.AddShipping();
services.AddHangfire(configuration =>
{
configuration.UseSqlServerStorage("Server=localhost\\SQLEXPRESS;Database=Hangfire;Trusted_Connection=True;");
configuration.UseMediatR();
});
services.AddHangfireServer();
});
}
}
view raw Program.cs hosted with ❤ by GitHub

Enqueuing a Request

In order to enqueue a MediatR request to Hangfire is a matter of calling the Enqueue() extension method we created off of IMediator.

using System;
using System.Threading;
using System.Threading.Tasks;
using Hangfire.MediatR;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace Sales
{
public class PlaceOrder : IRequest
{
public Guid OrderId { get; set; }
}
public class PlaceOrderController : Controller
{
private readonly IMediator _mediator;
public PlaceOrderController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost("/sales/orders/{orderId:Guid}")]
public IActionResult Action([FromRoute] Guid orderId)
{
_mediator.Enqueue("Place Order", new PlaceOrder
{
OrderId = orderId
});
return NoContent();
}
}
public class PlaceOrderHandler : IRequestHandler<PlaceOrder>
{
public Task<Unit> Handle(PlaceOrder request, CancellationToken cancellationToken)
{
// Do your actual work here
throw new NotImplementedException();
}
}
}
view raw PlaceOrder.cs hosted with ❤ by GitHub

In the example above, the HTTP Request to our Controller action will return a 204 NoContent, even though we are throwing in our PlaceOrderHandler because it’s execution is actually done out of the context of the HTTP Request. This could either be in a different thread, an entire different process, or on a totally on a different server).

More

There’s a lot more you can do with this and take it much farther to handle things like event publishing to have each event handler be it’s own job within Hangfire.

You can get all the source code of this running example on my GitHub.

If you have any thoughts or questions, please leave a comment on my blog, Twitter, or on the YouTube video.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


Links

Leave a Reply

Your email address will not be published. Required fields are marked *