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.