Fullstack Hub

[Count: 1]

In the previous part, we created the load user queries and understood how FluentValidation works to validate the request data. In this part, we will keep extending the UserManagement.Application project and add the add, update, delete commands, and validation rules for each functionality.

Let’s Start

Just for revision, the add, update and delete functions would be user commands.

Add User Command

Craete a new class AddUserCommand in User -> Commands folder and replace its content with following code:

namespace UserManagement.Application.User.Commands
{
    using AutoMapper;
    using MediatR;
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using UserManagement.Application.Common.BaseClass;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Domain.UnitOfWork;
    public class AddUserCommand : IRequest<int>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DOB { 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 class AddNewUserHandler : ApplicationBase, IRequestHandler<AddUserCommand, int>
        {
            public AddNewUserHandler(IConfigConstants constant, IMapper mapper, IUnitOfWork unitOfWork)
                : base(constant, unitOfWork, mapper)
            {
            }

            public async Task<int> Handle(AddUserCommand request, CancellationToken cancellationToken)
            {
                var user = new UserManagement.Domain.Entities.User
                {
                    FirstName = request.FirstName,
                    LastName = request.LastName,
                    DOB = request.DOB,
                    Gender = request.Gender.ToUpper(),
                    EmailAddress = request.EmailAddress,
                    PhoneNumber = request.PhoneNumber,
                    City = request.City,
                    State = request.State,
                    Zip = request.Zip,
                    Country = request.Country
                };
                this.UnitOfWork.StartTransaction();
                var res = UnitOfWork.Users.AddUser(user).Result;
                this.UnitOfWork.Commit();
                return await Task.Run(() => res, cancellationToken);
            }
        }
    }
}

As we discussed earlier, the MediatR pattern has two main parts, the request, and the handler. The AddUserCommand implementing IRequest interface is a request containing all the properties sent from front-end to API to command. The AddNewUserHandler is a handler class, extending our custom base class ApplicationBase and implementing generic IRequestHandler taking AddUserCommand type input parameter and int type return value.

Thanks to .NET Core built-in dependency injection provider/service, we are specifying the required classes interface in the AddNewUserHandler constructor and passing it to the base ApplicationBase class and they are automatically being initialized by the .NET Core dependency injection service. Check the DependencyInjection classes in UserManagement.Application and UserManagement.Persistence projects.

In the Handle function, we are creating and initializing the User entity object and calling the AddUser function from UserRepository through the UnitOfWork class object. Since we are treating each add, update, and deleting database operation as a transaction, we are explicitly calling the StartTransaction method from UnitOfWork class where we are starting the transaction by this.dbConnection.BeginTransaction() statement. After calling functions from the user repository, we are calling the Commit function to commit the corresponding functionality to the database, in our case the add user record. In summary, put all repository functions between StartTransaction and Commit statements.

Finally, we are sending the result back to calling function, the AddUserCommand is returning the integer value.

Add User Command Validation

Create a new class AddUserCommandValidator in the User -> Commands folder and add the following code in it:

namespace UserManagement.Application.User.Commands
{
    using FluentValidation;
    using System.Linq;
    using UserManagement.Application.Common.Interfaces;
    public class AddUserCommandValidator : AbstractValidator<AddUserCommand>
    {
        public AddUserCommandValidator(IConfigConstants constant)
        {
            this.RuleFor(v => v.FirstName).NotEmpty().WithMessage(constant.MSG_USER_NULLFIRSTNAME);
            this.RuleFor(v => v.LastName).NotEmpty().WithMessage(constant.MSG_USER_NULLLASTNAME);
            this.RuleFor(v => v.DOB).NotEmpty().WithMessage(constant.MSG_USER_NULLDOB);
            this.RuleFor(v => v.Gender).Must(x => (new string[] { "M", "F", "m", "f" }).Contains(x)).WithMessage(constant.MSG_USER_NULLGENDER);
            this.RuleFor(v => v.EmailAddress).NotEmpty().WithMessage(constant.MSG_USER_NULLEMAILADDR);
            this.RuleFor(v => v.PhoneNumber).NotEmpty().WithMessage(constant.MSG_USER_NULLPHNUM);
            this.RuleFor(v => v.City).NotEmpty().WithMessage(constant.MSG_USER_NULLCITY);
            this.RuleFor(v => v.State).NotEmpty().WithMessage(constant.MSG_USER_NULLSTATE);
            this.RuleFor(v => v.Country).NotEmpty().WithMessage(constant.MSG_USER_NULLCOUNTRY);
        }
    }
}

We already have created a validator class for GetSingleUserQuery class, above given AddUserCommandValidator is of the same architecture so not going to waste time explaining it again, just go through the code and see the FluentValidation flexibility and ease of use.

Update User Command

Create a new class UpdateUserCommand in the User -> Commands folder and replace its code with the following:

namespace UserManagement.Application.User.Commands
{
    using AutoMapper;
    using MediatR;
    using System.Threading;
    using System.Threading.Tasks;
    using UserManagement.Application.Common.BaseClass;
    using UserManagement.Application.Common.Exceptions;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Domain.UnitOfWork;
    public class UpdateUserCommand : IRequest<bool>
    {
        public int UserID { 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 class UpdateUserHandler : ApplicationBase, IRequestHandler<UpdateUserCommand, bool>
        {
            public UpdateUserHandler(IConfigConstants constant, IMapper mapper, IUnitOfWork unitOfWork)
                : base(constant, unitOfWork, mapper)
            {
            }

            public async Task<bool> Handle(UpdateUserCommand request, CancellationToken cancellationToken)
            {
                var user = await this.UnitOfWork.Users.GetUser(request.UserID);
                if (user == null)
                {
                    throw new NotFoundException($"The User ID {request.UserID} is not found");
                }

                user.PhoneNumber = request.PhoneNumber;
                user.City = request.City;
                user.State = request.State;
                user.Zip = request.Zip;
                user.Country = request.Country;
                this.UnitOfWork.StartTransaction();
                var res = UnitOfWork.Users.UpdateUser(user).Result;
                this.UnitOfWork.Commit();
                return await Task.Run(() => res, cancellationToken);
            }
        }
    }
}

The above class is for updating the user, first, it is searching the existing user by UserID, if it doesn’t exist, throwing a NotFoundException with our custom message. Just for revision, we created a NotFoundException in the previous part. Later, we will create the custom exception handler middleware in the API project to return the message and HTTP status code to the API and front-end application.

Update User Command Validator

Let’s add the update user validation rules that are pretty self-explanatory. Add the UpdateUserCommandValidator class in the User -> Commands folder and add the following code in it:

namespace UserManagement.Application.User.Commands
{
    using FluentValidation;
    using UserManagement.Application.Common.Interfaces;
    public class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
    {
        public UpdateUserCommandValidator(IConfigConstants constant)
        {
            this.RuleFor(v => v.UserID).GreaterThan(0).WithMessage(constant.MSG_USER_NULLUSERID);
            this.RuleFor(v => v.PhoneNumber).NotEmpty().WithMessage(constant.MSG_USER_NULLPHNUM);
            this.RuleFor(v => v.City).NotEmpty().WithMessage(constant.MSG_USER_NULLCITY);
            this.RuleFor(v => v.State).NotEmpty().WithMessage(constant.MSG_USER_NULLSTATE);
            this.RuleFor(v => v.Country).NotEmpty().WithMessage(constant.MSG_USER_NULLCOUNTRY);
        }
    }
}

Delete User Command

Delete user will only take UserID as an input variable, create a new class DeleteUserCommand in the User -> Commands folder and replace the code with following:

namespace UserManagement.Application.User.Commands
{
    using AutoMapper;
    using MediatR;
    using System.Threading;
    using System.Threading.Tasks;
    using UserManagement.Application.Common.BaseClass;
    using UserManagement.Application.Common.Interfaces;
    using UserManagement.Domain.UnitOfWork;
    public class DeleteUserCommand : IRequest<bool>
    {
        public int UserID { get; set; }
        public class DeleteUserHandler : ApplicationBase, IRequestHandler<DeleteUserCommand, bool>
        {
            public DeleteUserHandler(IConfigConstants constant, IMapper mapper, IUnitOfWork unitOfWork)
                : base(constant, unitOfWork, mapper)
            {
            }

            public async Task<bool> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
            {
                this.UnitOfWork.StartTransaction();
                var res = UnitOfWork.Users.DeleteUser(request.UserID).Result;
                this.UnitOfWork.Commit();
                return await Task.Run(() => res, cancellationToken);
            }
        }
    }
}

Delete User Command Validator

For the delete user command, we need to check if the UserID parameter is provided, let’s create a validation rule to verify that condition. Create a DeleteUserCommandValidator class in the User -> Command folder and add the following code to it:

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

That’s it for UserManagement.Application project, in the next part, we will create UserManagement.API project and try to understand it step by step.

Yaseer Mumtaz

Leave a Reply

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