Fullstack Hub

[Count: 1]

In the previous part, we pretty much set up the base for the Application’s project Command and Queries e.g. centralized logging, unhandled and validation exception handling, generic Automapping interface, and Mapping Profile, etc. In this part, let’s create load user queries and understand how it works, how fluent validation is used to validate the model, how Automapper helps us to map the DTO to the entity, and how we call the repositories to perform database operations.

Let’s Start

Continuing from the previous part, let add the DependencyInjection class to create the ServiceCollection extension method that is used to handle IOC (inversion of control, in simple words, initialize and return the class objects where required). This class would later be called from the API project’s Startup class. Create a new class in a root folder by right-clicking on UserManagement.Application project and selecting Add -> New Class. Replace its content with the following code:

using AutoMapper;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using UserManagement.Application.Common.Behaviors;
using UserManagement.Application.Common.Behaviours;

namespace UserManagement.Application
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddApplication(this IServiceCollection services)
        {
            services.AddAutoMapper(Assembly.GetExecutingAssembly());
            services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
            services.AddMediatR(Assembly.GetExecutingAssembly());
            services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));
            services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
            services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
            return services;
        }
    }
}

These are all dependency injection handlers for the behaviors classes we created in the last part. Click here to understand the difference between AddScoped, AddTransient, and AddSingleton.

Let’s start developing the exciting part now, the command and queries. In the root folder, create a new folder User and create the following subfolders in it:

  • Commands
  • Queries
  • DTO
  • VM

Let create UserDTO class first, add a new class in DTO foler and name it UserDTO, paste following code in it:

namespace UserManagement.Application.User.DTO
{
    using AutoMapper;
    using System;
    using UserManagement.Application.Common.Mappings;
    public class UserDTO : IMapFrom<Domain.Entities.User>
    {
        public int UserID { get; set; }
        public string Salutation { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DOB { get; set; }
        public int Age { get; set; }
        public string Gender { get; set; }
        public string EmailAddress { get; set; }
        public string PhoneNumber { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }
        public string Country { get; set; }
        public void Mapping(Profile profile)
        {
            profile.CreateMap<Domain.Entities.User, UserDTO>()
                 .ForMember(d => d.Salutation, opt => opt.MapFrom(s => s.Gender.ToUpper() == "M" ? "Hi Sir!" : "Hi Ma'am!"))
                 .ForMember(d => d.Age, opt => opt.MapFrom(s => DateTime.Today.Year - s.DOB.Year))
                 ;
            profile.CreateMap<UserDTO, Domain.Entities.User>();
        }
    }
}

Hopefully, now we will be cleared why the heck we created an IMapFrom interface and what is the purpose of the Mapping function that we are implementing here. You can see, we are specifying the custom rules for Salutation property based on Gender property value. Also, we are calculating the Age from the user’s date of birth because it doesn’t make sense to store age in the database. For the rest of the properties, since the name in both DTO and User entity class match, it would automatically be mapped themselves.

Next, create the UserVM, a view model class that is sole to be returned to the front-end application. In our case, it is pretty simple, just a list of UserDTO but you can have anything depending upon your front-end application requirement. Create a class UserVM in the VM folder and replace its content with the following:

namespace UserManagement.Application.User.VM
{
    using System.Collections.Generic;
    using UserManagement.Application.User.DTO;
    public class UserVM
    {
        public IList<UserDTO> UserList { get; set; }
    }
}

As discussed in previous parts, all the gets calls would be treated as queries, and the rest of the operations e.g. add, update and delete as commands, let’s create a query class to load all users. But, hold on before moving to create a query class, let’s think a little bit about architecture we created so far.

Base Application Class

As discussed in previous parts, we are going to use the UnitOfWork to call the functions from the repositories class, Mapper to map the entities, and ConfigConstants to load the configurable values from the appsettings.json file. In .NET Core, we have built-in IOC (Inversion of Control) functionality available to take care of these dependencies like one we created in the previous step, a DependencyInjection class. Since pretty much, all user queries and commands classes will use these dependencies, I think this is cool if we create a base class to initialize these dependencies and let all classes inherit this base class. Create a new folder BaseClass in the Common folder and add a class ApplicationBase in it. Add the following code to it:

namespace UserManagement.Application.Common.BaseClass
{
    using AutoMapper;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Domain.UnitOfWork;
    public class ApplicationBase
    {
        public IUnitOfWork UnitOfWork { get; set; }
        public IConfigConstants ConfigConstants { get; set; }
        public IMapper Mapper { get; set; }

        public ApplicationBase(IConfigConstants configConstants, IUnitOfWork unitOfWork, IMapper mapper)
        {
            ConfigConstants = configConstants;
            UnitOfWork = unitOfWork;
            Mapper = mapper;
        }
    }
}
Create User Query Classes

Let’s create a get all users as our first query class. Create a new class GetAllUserQuery in the User -> Queries folder and replace its code with the following:

namespace UserManagement.Application.User.Queries
{
    using AutoMapper;
    using MediatR;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using UserManagement.Application.Common.BaseClass;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Application.User.DTO;
    using UserManagement.Application.User.VM;
    using UserManagement.Domain.UnitOfWork;
    public class GetAllUserQuery : IRequest<UserVM>
    {
        public class GetAllUserHandler : ApplicationBase, IRequestHandler<GetAllUserQuery, UserVM>
        {
            public GetAllUserHandler(IConfigConstants constant, IMapper mapper, IUnitOfWork unitOfWork)
                : base(constant, unitOfWork, mapper)
            {
            }

            public async Task<UserVM> Handle(GetAllUserQuery request, CancellationToken cancellationToken)
            {
                var res = Mapper.Map(UnitOfWork.Users.GetAllUsers().Result, new List<UserDTO>());
                return await Task.FromResult(new UserVM() { UserList = res });
            }
        }
    }
}

Let’s try to understand the basic concept behind this class, we are using a mediator pattern for communication to avoid dependencies chaos. A MediatR package helps us to achieve this goal and we already have added the dependencies in previous parts. To implement the MediatR, we basically need two classes, a request and a second is the handler. The request class can be input parameters e.g. DTO class object that we want to insert, update, or search parameters, etc. In the above class, GetAllUserQuery is a request that is implementing an IRequest generic interface from MediatR class with UserVM as a return type. This class doesn’t have a property since we don’t need any to load all users but you would see them in add, update, delete, etc commands. GetAllUserHandler is a kind of embedded class acting as a handler, inheriting from ApplicationBase that we created in the previous step and IRequestHandler MediatR interfaces taking GewtAllUserQuery request class and return type UserVM. The IRequestHandler has only one method Handle where we write our business logic.

To load all the users, we are calling the GetAllUsers method from UserRepository, this should be clear now how we are not directly talking to UserRepository but using UnitOfWork. Once the result is returning from the repository, we are using Mapper.Map function to map the list of user entities to the list of UserDTO. Remember, we already put all the configuration in UserDTO class. Finally, we are sending the UserVM object to the calling function after initializing it with the list of UserDTO, in our case, it would be API.

Next, let’s create a get user by user id query, create a new class GetSingleUserQuery in User -> Queries folder and replace its code with the following:

namespace UserManagement.Application.User.Queries
{
    using AutoMapper;
    using MediatR;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using UserManagement.Application.Common.BaseClass;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Application.User.DTO;
    using UserManagement.Application.User.VM;
    using UserManagement.Domain.UnitOfWork;

    public class GetSingleUserQuery : IRequest<UserVM>
    {
        public int UserID { get; set; }
        public class GetSingleUserHandler : ApplicationBase, IRequestHandler<GetSingleUserQuery, UserVM>
        {
            public GetSingleUserHandler(IConfigConstants constant, IMapper mapper, IUnitOfWork unitOfWork)
                : base(constant, unitOfWork, mapper)
            {
            }

            public async Task<UserVM> Handle(GetSingleUserQuery request, CancellationToken cancellationToken)
            {
                var res = this.Mapper.Map(this.UnitOfWork.Users.GetUser(request.UserID).Result, new UserDTO());
                return await Task.FromResult(new UserVM() { UserList = new List<UserDTO> { res } });
            }
        }
    }
}

The code architecture is almost the same as GetAllUserQuery but just one point to understand here, you can see we now have one UserID property (an input parameter that the user will send from the front-end application and it will travel from API project to here) in the GetSingleUserQuery request class, in the Handle method, this request class is passing as an input parameter request and we are using it as request.UserID to pass it to the GetUser method. We can have as many properties in the request class as we required and we will use them in the same way you are seeing here.

Add Get User by ID Validation

For GetSingleUserQuery, we are receiving UserID as a request to filter the result. It does make sense to validate the UserID value to be great than zero before calling the User Repository GetUser method. We are going to use the FluentValdiation to validate the UserID that provides great flexibility for validation rules. It also provides the loose coupling by having validation rules in different classes and extending the generic AbstractValidatorclass taking the query or command class as a parameter type. Once we provide the query or command class in the AbstractValidator type, all the properties in the request class are accessible to apply the validation rules.

Once we will Add the service in UserManagement.API project’s Startup.cs class and create the ApiExceptionFilterAttribute , you would get a better picture of how FluentValidation validates the model classes, call the Common -> Exceptions -> ValidationException class to consolidate all the error messages to send to the API/front-end application.

Create a GetSingleUserQueryValidator class in User -> Queries folder and replace its coding with following:

namespace UserManagement.Application.User.Queries
{
    using FluentValidation;
    using UserManagement.Application.Common.Interfaces;
    public class GetSingleUserQueryValidator : AbstractValidator<GetSingleUserQuery>
    {
        public GetSingleUserQueryValidator(IConfigConstants constant)
        {
            this.RuleFor(v => v.UserID).GreaterThan(0).WithMessage(constant.MSG_USER_NULLUSERID);
        }
    }
}

In the above class, you can see we are extending validator from AbstractValidator class and specifying the parameter type GetSingleUserQuery that is a request in our MediatR pattern, now we can access the UserID property from that class. Click here to see the FluentValidation documentation. Instead of hard coding the error message, we are loading them from the appsettings.json file through ConfigConstants class so that they can be easily overridden without making a code change.

Let’s create add, update and delete user commands in next part.

Yaseer Mumtaz

Leave a Reply

Your email address will not be published. Required fields are marked *