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.

Comments

Comment by Truebzyq

Rebus? Never again, author is closing bugs in git without checking it. Problems with Concurency Exceptions deep inside library. Messages deffered can occur multiple times on queue, which leads to messages multiplications. Library is using wrong assumption that it should run only in one thread. If you will start using it on multiple threads you will see problems. Rebus plugins libs are using wrong Nuget dependencies numbers so when you will update it Nuget wont tell you that you should update Castel.Windsor etc but it will start throwing exceptions on runtime. Poor quality library

Truebzyq
Comment by Maulik Modi

@truebzyq:disqus , would you mind creating a github issue on this repo with steps to reproduce- https://github.com/rebus-or...
I got excited with the easy programming model and almost sold of to low barrier of entry thing.

Maulik Modi
Comment by Maulik Modi

Is this library used in production ?

Maulik Modi
Comment by Truebzyq

Yes, offcourse i have created 3 tickets, and after a week or two author simply throw it away and close it, this is bug-free library because author is simply closing bugs without checking it. Like i've said: Rebus? Never again. Few days ago i have found another problem, when i have used async/await with Func<t>, Rebus was enqueuing all Func<t> calls and ignoring await, simply start 50000 Func<t> and run it in the end. Is this await normal behaviour? No it isnt, because author made its own thread pool with his own logic for running handlers. The other thing is IoC Handler dependency resolving. Do you know how long Handler lives? Is it transient or similar to PerWebRequest like in MVC? No it is cached and all things injected in constructor are cashed too, so if you are using IOC you need to use antypattern "ServiceLocator" in handler method to have proper per service call dependenies. I am working in project with rebus 3 years right now and i can make a book about this library and problems that are inside :) We need to create our own semaphore factory to be sure that handler will be called only once per type of messages, becasue Rebus "IsNew" is set... in the end of handler method. I know that new Rebus has new fetures, propably many fixes too, but to update it i need to update whole DataBase, and DataBase client because its got a problem with plugins and dependencies. So.... never again

Truebzyq