0 Comments Posted in:

In my recent Pluralsight course "Versioning and Evolving Microservices with ASP.NET Core", I built my demos on top on an existing microservices application that had been created as the basis for a Microservices in ASP.NET Core learning path on Pluralsight (which is nearing completion).

For messaging between microservices, this application used Rebus which is a very simple service bus implementation in .NET that allows you to plug in a few different services as a back-end. For example, in Azure you could use Azure Service Bus, Azure Storage Queues or Azure SQL Database, and many other "transports" are available.

In this post, I'll go through the basics of setting up Rebus in ASP.NET Core, where we are going to have one microservice send a message, and another handle those messages.

I'm going to use Azure Storage Queues as the transport as they are (1) very simple and cheap, and (2) have an emulator you can use for local development. And of course by using an abstraction, we are free to change later to a different underlying transport.

Sending messages

In the project that will send messages using Rebus, we'll add references to following NuGet packages in our csproj file:

<PackageReference Include="Rebus" Version="6.4.1" />
<PackageReference Include="Rebus.AzureQueues" Version="1.0.0" />
<PackageReference Include="Rebus.Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Rebus.ServiceProvider" Version="5.0.6" />

Next, in Startup.ConfigureServices we'll use the AddRebus method to set up Rebus. I'm configuring it to integrate with the ASP.NET Core logging. I'm using UseAzureStorageQueuesAsOneWayClient for the transport as this project will only need to send messages, rather than receive them.

I need to pass in a CloudStorageAccount which in development can use the connection string of the Azure Storage Emulator (UseDevelopmentStorage=true).

And I also need to tell it what queues to route different message types to. In my simple example I have one message called VoteMessage, and I'll send that to a queue name I read out of configuration.

var storageAccount = CloudStorageAccount.Parse(Configuration.GetConnectionString("AzureQueues"));

// Configure and register Rebus
services.AddRebus((configure, provider) => configure
    .Logging(l => l.MicrosoftExtensionsLogging(provider.GetRequiredService<ILoggerFactory>()))
    .Transport(t => t.UseAzureStorageQueuesAsOneWayClient(storageAccount))
    .Routing(r => r.TypeBased().Map<VoteMessage>(Configuration["AzureQueues:QueueName"]))
    );

Also in my Startup.Configure method I added the following call to UseRebus:

app.ApplicationServices.UseRebus();

Finally, in my controller or Razor page that wants to send the message, I simply take a dependency on Rebus.Bus.IBus, and call the Send method. Rebus knows what queue to send it to based on the type of message I'm sending.

await bus.Send(new VoteMessage()
{
    PollId = Poll.Id,
    Option = option,
    VoterId = voterId
});

Handling messages

In the ASP.NET Core application that handles the messages, the steps are similar. In my very simple example, I'm putting the handlers into an existing ASP.NET Core web API application, although in a production app, I'd want to separate this concern and have a dedicated worker process.

The steps are almost identical. We're referencing the same four NuGet packages. And our Startup.ConfigureServices method looks like this.

services.AddRebus((configure, provider) => configure
    .Logging(l => l.MicrosoftExtensionsLogging(provider.GetRequiredService<ILoggerFactory>()))
    .Transport(t => t.UseAzureStorageQueues(storageAccount, Configuration["AzureQueues:QueueName"])));

Note that we don't need to set up any routing because this project isn't sending any messages. But we do need to tell it which queue to listen for messages on which is provided as an argument to the UseAzureStorageQueues method.

Rebus also needs to know how to create the appropriate handler for each message type. The easiest way to do this is to tell it to scan an assembly for all handlers with a call to AutoRegisterHandlersFromAssemblyOf. This will register every class in the assembly that implements IHandleMessages<T>. It also means that your handler classes can take dependencies on anything that can be resolved from the ASP.NET Core DI container.

services.AutoRegisterHandlersFromAssemblyOf<VoteMessageHandler>();

Again in Startup.Configure method we need to call UseRebus. Note that this will actually attempt to start listening on the queue, so if your Storage Queue is inaccessible or your configuration is wrong, you may get an error here (another reason not to put message handlers in the same project as APIs).

app.ApplicationServices.UseRebus();

Finally, we need a message handler for our VoteMessage, which is achieved by implementing IHandleMessages<VoteMessage> and implementing the Handle method. As you can see it's very straightforward:

public class VoteMessageHandler : IHandleMessages<VoteMessage>
{
    private readonly VoteDbContext dbContext;
    private readonly ILogger<VoteMessageHandler> logger;

    public VoteMessageHandler(VoteDbContext dbContext, ILogger<VoteMessageHandler> logger)
    {
        this.dbContext = dbContext;
        this.logger = logger;
    }
    public async Task Handle(VoteMessage message)
    {
        // save to the database
    }
}

Summary

Obviously Rebus has a lot more capabilities than I'm showing here. But the nice thing about it is that there is a very low barrier of entry and you can start simple, adding basic messaging with swappable transports to your ASP.NET Core microservices with minimal effort.

Vote on HN