I have a .NET Core Web Api Application which is arranged in the following manner -
- Controller layer which injects Business Service
- Business Service which injects Unit Of Work (to interact with database)
- Business Service might also make a call to a FluentValidation class
- FluentValidation will inject the Unit Of Work to perform database checks (Exists, etc.)
So having said all of that here is an example. If I want to create a User in the system I have a route/method called "PostUser" located inside of the "UsersController". The "UsersController" injects the "UserService". The "UserService" has a method called "CreateUser". So inside of the "PostUser" method of the controller it looks like this -
var user = _userService.CreateUser(user);
Now inside of the "CreateUser" method it looks like this -
UserValidation validation = new UserValidation(UnitOfWork, DatabaseOperation.Create);
ValidationResult validationResult = await validation.ValidateAsync(user);
So the UnitOfWork is passed into the UserService via dependency injection and then passed along to the FluentValidation class "UserValidation" so the validation class can perform database checks. I also pass an enum into the UserValidation class to specify whether or not the validation is intended for an Update or a Create.
The User object I am trying to validate will have properties such as "Role" and "Company" and I also have separate validation classes for each (RoleValidation and CompanyValidation). Both of these validation classes will also pass in a UnitOfWork and whether or not this is a create or an update.
Here is an example of my UserValidation Class -
public class UserValidation : AbstractValidator<UserDTO>
{
private IUnitOfWork _unitOfWork;
public UserValidation(IUnitOfWork unitOfWork, DatabaseOperation databaseOperation)
{
_unitOfWork = unitOfWork;
if (databaseOperation == DatabaseOperation.Create)
{
// Do Create specific validation
}
RuleFor(x => x.Company)
.SetValidator(new CompanyValidator(_unitOfWork, databaseOperation));
}
}
Now understanding all of this I wanted to create Unit Tests for my "UserService" class. But I believe in order to properly do this I would need to Mock out the FluentValidation class in some cases and as you can see in my "UserService" CreateUser method I instantiate the concrete class for my Validation. So in order to do this I would have to create an interface for each of my fluentvalidation classes and inject them into the business services that use them. So I did the following in my Startup.cs file -
services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));
So now after doing that I can inject the IValidator into my UserService Constructor and use that instead of instatiating a Concrete class inside of my UserService methods.
So with this brings me to ask the following questions.
- In your opinion, the way I already have my project structured, is this the best way to use dependency injection with FluentValidation and allow for unit testing of the service method along with unit testing of the FluentValidation class?
- Is there a better way using dependency injection with FluentValidation to do all of this, and at the same time let the FluentValidation class know if it is a "Create" or an "Update", instead of creating one class called "UserCreateValidation" and "UserUpdateValidation" or passing in a variable "DatabaseOperation" to the constructor of the Validator?
- Appending to (2) when trying to setup the FluentValidation DependencyInjection I am having trouble passing in the "DatabaseOperation" variable
services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>(), <How to figure out if its a create or an update>)); - On Top of that I will have to also add two lines to the "Startup.cs" file to create the Scoped DependencyInjection of the "CompanyValidation" and the "RoleValidation" to be used inside of the "UserValidation" and both of those validation classes will also pass in whether or not it is an update or a create.
Any help/suggestions would be appreciated. I am really stuck on this issue. If anyone needs anymore clarification on the issues I am facing please do not hesitate to ask.
Thank You