Microservices Architecture 1/20 — Implement CQRS in .NET Core WebApi project with MediatR

Shan Ke
3 min readAug 19, 2019

Installing MediatR

Install-Package MediatR.Extensions.Microsoft.DependencyInjection

Creating Commands

using MediatR;

namespace Users.API.Application.Commands
{
public class CreateUserCommand : IRequest<Models.User>
{
public string Name { get; set; }
public string Email { get; set; }
public decimal MonthlySalary { get; set; }
public decimal MonthlyExpenses { get; set; }
}
}

Creating CommandHandlers

using MediatR;
using RabbitMQ.EventBus.AspNetCore;
using System;
using System.Threading;
using System.Threading.Tasks;
using Users.API.Application.Commands;
using Users.API.Application.Events;
using Users.API.Infrastructure.Repositories;
using Users.API.Models;

namespace Users.API.Application.CommandHandlers
{
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Models.User>
{
private readonly IRabbitMQEventBus _eventBus;
private readonly IUserRepository _userRepository;

public CreateUserCommandHandler(IRabbitMQEventBus eventBus, IUserRepository userRepository)
{
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}

public async Task<User> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
//
if (await _userRepository.GetUserByEmailAsync(request.Email) != null)
{
throw new FluentValidation.ValidationException("Email has been registered.");
}

var userCreated = await _userRepository.CreateUserAsync(request.Name, request.Email, request.MonthlySalary, request.MonthlyExpenses);

if (userCreated == null)
{
throw new Exception("Error in Creating user.");
}

//BR:Zip Pay allows credit for up to $1000, so if monthly salary - monthly expenses is less than
//$1000 we should not create an account for the user and return an error
if (userCreated.MonthlySalary - userCreated.MonthlyExpenses >= 1000)
{
_eventBus.Publish(new QualifiedUserCreatedEvent(userCreated), "zip.new.users", "qualified");
}

return userCreated;
}
}
}

Controllers

using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
using Users.API.Application.Commands;
using Users.API.Application.Queries;

namespace Users.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger<UsersController> _logger;

public UsersController(IMediator mediator, ILogger<UsersController> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

/// <summary>
/// Create a user
/// </summary>
/// <param name="command">User info</param>
/// <returns>User created</returns>
[HttpPost]
public async Task<IActionResult> CreateAsync(CreateUserCommand command)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

try
{
var userCreated = await _mediator.Send(command);

if (userCreated == null)
{
throw new Exception("Error in creating user");
}

return CreatedAtAction(nameof(GetByIdAsync), new { id = userCreated.Id }, userCreated);
}
catch (FluentValidation.ValidationException vex)
{
return BadRequest(new { vex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message, command);

return StatusCode(StatusCodes.Status500InternalServerError, new
{
message = "Error in creating user."
});
}
}

/// <summary>
/// Get desired user by id
/// </summary>
/// <param name="id">User Id</param>
/// <returns>Desired user</returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetByIdAsync(Guid id)
{
try
{
var user = await _mediator.Send(new GetUserByIdQuery(id));

if (user == null)
{
return NoContent();
}

return Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);

return StatusCode(StatusCodes.Status500InternalServerError, new
{
message = $"Error in retrieving user by id:{id}"
});
}
}

/// <summary>
/// Get all users (no pagination)
/// </summary>
/// <returns>All users</returns>
[HttpGet]
public async Task<IActionResult> GetAsync()
{
try
{
var users = await _mediator.Send(new GetUsersQuery());

if (users == null || !users.Any())
{
return NoContent();
}

return Ok(users);

}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);

return StatusCode(StatusCodes.Status500InternalServerError, new
{
message = "Error in retrieving users."
});
}
}
}
}

Configure Startup.cs

namespace Users.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMediatR(typeof(Startup));

services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseMvc();
}
}
}

--

--

Shan Ke

.NET Architect & Developer, C#, ASP.NET, .NET Core, Python, Angular, Linux, Oracle, MongoDB, Microservices, Azure