In the last part, we implemented the database logic by Repository and unit of Work design patterns with Dapper & Dapper.Contrib packages. In this part, let’s implement the business logic in UserManagement.Application. As discussed in Part 1, we will be using CQRS (Command Query Segregation Principle) and Mediator pattern to loosely couple the API and Application layer connection. We will see the implementation in action but first let’s create the base classes e.g. logging, validation behavior, exception handling, etc in this part.
Let’s Start
In UserManagement.Application project, we will add a few useful functionalities e.g centralized logging, exception handling, and model validation. These all functionalities are already implemented in Jason Taylor Clean Architecture with .NET Core article and we will just try to understand the flow of how these functionalities are working. Before moving further, let’s add the dependencies for the Application project so that we don’t get any error in upcoming steps.
Add Dependencies
Right Click on Dependencies folder and select option Manage NuGet Packages… Search and install following packages:
- AutoMapper Version 10.X.X
- AutoMapper.Extensions.Microsoft.DependencyInjection Version 8.X.X
- FluentValidation Version 9.X.X
- FluentValidation.DependencyInjectionExtensions Version 9.X.X
- MediatR.Extensions.Microsoft.DependencyInjection Version 8.X.X
- Microsoft.Extensions.DependencyInjection.Abstractions Version 3.X.X
- Microsoft.Extensions.Logging Version 3.X.X
- Newtonsoft.Json Version 12.X.X
- Add UserManagement.Domain project dependency by right-clicking on Dependencies and select Add Project References…
Loagging Each Request
Create a new folder Behaviors in Common folder and add a nee class LoggingBehaviour
in it, replace the code with the following:
namespace UserManagement.Application.Common.Behaviors
{
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest>
{
private readonly ILogger _logger;
public LoggingBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
string userName = string.Empty;
await Task.Run(() => _logger.LogInformation("UserManagement Request: {Name} {@UserName} {@Request}",
requestName, userName, request));
}
}
}
The Process
function in the above class will execute for each API call, get the API name and log it in console, currently, I am not implementing the user authentication but if you follow Jason’s article, you can get both logged in user and request name. Since we are implementing the IRequestPreProcessor
interface and adding the MediatR service in DependencyInjection in the upcoming class, we do not need to explicitly call this class, it will automatically be called before each API command/query execution that provides a great deal of loosely coupled architecture. In the future, I would update this article to log in to the text file.
Logging the Time Taking APIs
I am taking this class straight from Jason’s article that is logging a long time taking APIs. The only change I made is a configurable time in milliseconds from the appsettings.json file. Create a new class PerformanceBehaviour
in the Behaviors folder and add the following to it:
namespace UserManagement.Application.Common.Behaviors
{
using MediatR;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using UserManagement.Application.Common.Interfaces;
public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly Stopwatch _timer;
private readonly ILogger<TRequest> _logger;
private readonly IConfigConstants _constact;
public PerformanceBehaviour(ILogger<TRequest> logger, IConfigConstants constants)
{
_timer = new Stopwatch();
_logger = logger;
_constact = constants;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_timer.Start();
var response = await next();
_timer.Stop();
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
if (elapsedMilliseconds > _constact.LongRunningProcessMilliseconds)
{
var requestName = typeof(TRequest).Name;
var userName = string.Empty;
_logger.LogWarning("UserManagement Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserName} {@Request}",
requestName, elapsedMilliseconds, userName, request);
}
return response;
}
}
}
The main thing to understand in this class is implementing generic IPipelineBehavior
interface that adds an additional behavior before going to the next function. IPipelineBehavior
has a handler method where we add our custom implementation that is supposed to be executed before moving to the next delegate functionality e.g. executing API command or query handlers. We can also log it in a text file rather than only logging in a running console.
Loagging Unhandled Exception
Again, got straight from Jason’s article and work exactly like long time taken APIs additional behavior class, create a new class UnhandledExceptionBehaviour
in the Behaviors folder, and replace the code with the following:
namespace UserManagement.Application.Common.Behaviours
{
using MediatR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<TRequest> _logger;
public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
try
{
return await next();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;
_logger.LogError(ex, "UserManagement Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
throw;
}
}
}
}
Add one more class NotFoundException
in the same folder that we will use if there is no record found in the database e.g. to update the user, we first will check if the user exists in the database and if not found, will throw NotFoundException
. Replace the code in NotFoundException
with following code:
namespace UserManagement.Application.Common.Exceptions
{
using System;
public class NotFoundException : Exception
{
public NotFoundException()
: base()
{
}
public NotFoundException(string message)
: base(message)
{
}
public NotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
public NotFoundException(string name, object key)
: base($"Entity \"{name}\" ({key}) was not found.")
{
}
}
}
Model Class Validation Behavior
This is quite an interesting behavior class that in summary, validates all model validation rules we specify for command/query using the Fluent Validation package, get the failure one (the one that is not matching our validation rules), and send to our custom validation class (extended from Exception base class) where we group errors by model class property name and corresponding error message that we define in command/query validation class. This list of property names and errors travels to UserManagement.API project’s CustomExceptionHandlerMiddleware
class we serialize to JSON and send it to the front end.
I understand this is quite difficult to comprehend now since we didn’t create the command/query and corresponding validation classes for adding, updating, deleting, and loading users. Once we will create those and add CustomExceptionHandlerMiddleware in userManagement.API project, hopefully, everything will start making sense. For now, just understand this class consolidates all the model validation messages and sends them to the front-end.
Create a new class ValidationBehaviour
in Behaviors folder and repalce it’s content with the following:
namespace UserManagement.Application.Common.Behaviours
{
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using MediatR;
using ValidationException = UserManagement.Application.Common.Exceptions.ValidationException;
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
}
In the Handle
function, a generic code is checking if there is any validation errors exist, it is storing it to the failures
list variable and passing it to ValidationException
class that we are going to create next.
Exception Handling
Go ahead and create a new folder Exceptions in the Common folder. Create a new class ValidationException
in the Exceptions folder and replace its content with the following code:
namespace UserManagement.Application.Common.Exceptions
{
using FluentValidation.Results;
using System;
using System.Collections.Generic;
using System.Linq;
public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}
public IDictionary<string, string[]> Errors { get; }
}
}
In the ValidationException
constructor, we are receiving the failures
and creating a list by propertyName, and its corresponding error message. This list travels to the API project where we serialize it to JSON and send it to the front-end application. Just remember, it’s totally up to you how to manage these errors and properties, I just standardized it as a propertyName and error message key-value pair so that it should be easy to manage on the front-end (in our case, Angular) application.
AutoMapper Configuration Classes
As discussed in previous parts, we will be using the AutoMapper package to map two entities e.g. DTO to DB Entity and vice versa. We can implement the generic interface IMapFrom
that has only one method Mapping
helping us to create the dynamic mapping and reverse mapping profile. Create a new folder Mappings in Common folder and add an interface IMapFrom
in it, replace the code with following:
namespace UserManagement.Application.Common.Mappings
{
using AutoMapper;
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType()).ReverseMap();
}
}
Make sure Application, Domain, and Persistance projects are using .NET Standard 2.1.
So each of our DTO (if required) will implement the IMapFrom
interface, pass the source mapping class type, and implement the Mapping
function if there is custom mapping required. If there is no mapping provided in the Mapping function, the matching properties (by name) would be automatically mapped to the destination class object. We will see this in action while creating the UserDTO that will implement the IMapFrom interface and pass the User database entity class type. The ReverseMapping
extension method helps to map the reverse the source and destination classes e.g. UserDTO can be mapped to User and User entity can also be mapped to UserDTO.
We need one more class that is MappingProfile
, create it in Mappings folder and add following script in it:
namespace UserManagement.Application.Common.Mappings
{
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
}
This class is basically used for the Application project’s unit tests and it is used to create the Mapper class object. So basically, from current running classes, we are looking for classes implementing IMapFrom
interface (e.g. UserDTO
), getting their Mapping
function, and invoking it. In upcoming parts, we will use this class to create a MapperConfiguration
class object and will call its CreateMapper
function to initialize the Mapper object. This will help us to enable the mapping functionality from the unit tests. This would make more sense once we will implement user commands/queries and unit tests.
Let’s create the user command/queries and APIs in next part.