In this article, we are going to explore the key differences between the Publish and Send methods in MediatR, a powerful .NET library for component communication.

To download the source code for this article, you can visit our GitHub repository.

We will see MediatR in action as we explore its implementation with requests, notifications, and behaviors.

Let’s start.

The Mediator Pattern

The mediator pattern is a software design pattern that enables indirect communication between software components, promoting loose coupling by introducing a mediator object as an intermediary.

By utilizing the Mediator pattern, components are decoupled and unaware of each other, reducing direct dependencies. This enhances flexibility and modularity, simplifying the addition or modification of components without impacting the entire system.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Now, let’s delve into how MediatR empowers the realization of these principles.

The MediatR Library

MediatR is a popular open-source library for implementing the Mediator pattern in .NET applications. It provides a simple and elegant way to handle communication between different parts of an application by encapsulating the logic of sending and handling messages.

There are two primary methods for sending messages: Publish and Send. Understanding the differences and trade-offs between these methods is crucial. This understanding will greatly assist you in making informed design decisions for your application.

Configuring an ASP.NET Core API with MediatR

To begin, let’s install the MediatR package: dotnet add package MediatR

Next, in the Program.cs file, we are going to register MediatR:

builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

To ensure the effective usage of MediatR, it is crucial to set up its configuration properly.

After successful installation and configuration, we can proceed with creating a new controller responsible for sending messages with MediatR:

[ApiController]
[Route("api/users")]
public class UserController : Controller
{
     private readonly IMediator _mediator;

     public UserController(IMediator mediator)
     {
          _mediator = mediator;
     }
}

In the UserController, we are going to handle user-related operations within the API. We utilize the IMediator interface, which we inject through the constructor injection. This injection enables the controller to access MediatR functionality for effectively handling messages and requests.

Besides using the IMediator interface, starting from the MediatR library version 9.0 this interface has been split into two separate interfaces ISender and IPublisher. The ISender provides the Send() method capability and IPublisher the Publish() method capability. If you want to know more about this, you can find that in one of our articles about MediatR and CQRS.

The Send Method in MediatR

The Send() method in MediatR facilitates efficient communication between components by handling commands and queries. It is key in promoting decoupling and separation of concerns within the application, resulting in enhanced flexibility and maintainability.

To initiate the process, we first need to create a command class:

public class CreateUserCommand : IRequest<int>
{
     public string UserName { get; set; } = string.Empty;
     public string Email { get; set; } = string.Empty;
}

The CreateUserCommandHandler

Let’s explore the class responsible for creating a new user and returning its ID. This handler class is crucial for the user creation process:

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
     private readonly IUserRepository _userRepository;

     public CreateUserCommandHandler(IUserRepository userRepository) =>
          _userRepository = userRepository;

     public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
     {
          var user = new User { UserName = request.UserName, Email = request.Email };

          return await _userRepository.CreateUserAsync(user);
     }
}

We have a class name CreateUserCommandHandler that implements the IRequestHandler<CreateUserCommand, int> interface. This handler is responsible for processing the CreateUserCommand by creating a new user with the provided username and email. The IUserRepository dependency is injected into the handler’s constructor. The Handle() method asynchronously creates the user using the repository and returns the ID of the newly created user.

Invoking and Verifying Our Request

To invoke our request, we can add the CreateUser() action to our UserController:

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult<int>> CreateUser(CreateUserCommand command)
{
     int userId = await _mediator.Send(command);

     return CreatedAtAction(nameof(CreateUser), userId);
}

This action expects a CreateUserCommand parameter and sends it to MediatR using the _mediator.Send() method. Upon invoking the request, it retrieves the result userId and includes it in the CreatedAtAction response. This response serves as the identifier for the newly created user.

Once we send a request to this endpoint, we can verify that MediatR is working as expected:

MediatR Send method in action

The Publish Method in MediatR

The Publish() method in MediatR serves as a one-way communication mechanism, allowing for the broadcast of messages to multiple subscribers within the application. Its primary function is to facilitate the distribution of messages without expecting any response or results in return. 

Let’s create an event that notifies about created users:

public class UserCreatedEvent : INotification
{
     public int UserId { get; set; }
     public string UserName { get; set; } = string.Empty;
}

The UserCreatedEvent class holds a UserId and the corresponding UserName.

The UserCreatedEventHandler

Next, let’s define the UserCreatedEventHandler class for handling the UserCreatedEvent. It implements the INotificationHandler interface and receives the UserCreatedEvent as a parameter:

public class UserCreatedEventHandler : INotificationHandler<UserCreatedEvent>
{
     private readonly IUserRepository _userRepository;
     private readonly INotificationService _notificationService;

     public UserCreatedEventHandler(IUserRepository userRepository, INotificationService notificationService)
     {
          _userRepository = userRepository;
          _notificationService = notificationService;
     }

     public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
     {
          var admin = await _userRepository.GetAdminUserAsync();
          string message = $"New user created: ID: {notification.UserId}, UserName: {notification.UserName}";

          await _notificationService.SendNotificationAsync(admin.UserId, message);
     }
}
       

The Handle() method retrieves the admin user from the repository and creates a notification message with the ID and username of the newly created user. 

This event handler is essential for managing user creation events and executing additional actions or notifications based on these events.

Invoking and Verifying our Request

To invoke our request, we can add the NotifyUser() action to our UserController:

[HttpPost("notify")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> NotifyUser(UserCreatedEvent notification)
{
     await _mediator.Publish(notification);

     return Ok();
}

The NotifyUser() action accepts a UserCreatedEvent notification parameter. Using the _mediator.Publish() method, we publish the notification by executing our handler. Finally, we return an HTTP 200 OK status code.

Next, let’s open Postman and send a new request to our endpoint notify:

MediatR Publish Method in Action

Conclusion

Throughout this article, we explored the usage of MediatR for command and event handling. We examined the differences between the Publish and Send methods and discussed their specific use cases.

The Publish method enables one-to-many communication by broadcasting messages without expecting a response. It promotes loose coupling, and dynamic subscriptions, and is well-suited for event-driven architectures.

On the flip side, the Send method facilitates one-to-one communication with an expected response. This can be used for requests or commands which expect an immediate response.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!