Working with a generic class that uses a type that should be of generic type
I have followed Nick Chapsas' tutorial to avoid throwing a ValidationException to treat validation errors and instead rely on LanguageExt.Common.Result<>
from LanguageExt library.
I have managed to develop a working solution that relies on MediatR (which uses queries, commands and validators for those), but I feel I am violating the DRY principle.
The relevant code is below:
A sample command
public class CreateDummyModelCommand : IRequest<Result<int>>
{
public string? Code { get; set; }
public string? Name { get; set; }
}
/// <summary>
/// this relies on Nick Chapsas idea of not using ValidationExceptions, but return Values:
/// https://www.youtube.com/watch?v=a1ye9eGTB98
/// </summary>
internal class CreateDummyModelCommandHandler : IRequestHandler<CreateDummyModelCommand, Result<int>>
{
private readonly IValidationUtils<CreateDummyModelCommand, int> _validationUtils;
public CreateDummyModelCommandHandler(IValidationUtils<CreateDummyModelCommand, int> validationUtils)
{
_validationUtils = validationUtils;
}
public async Task<Result<int>> Handle(CreateDummyModelCommand request, CancellationToken token)
{
var validationResult = await _validationUtils.IsValid(request, token);
if (validationResult.IsFaulted)
return validationResult;
var r = new Random();
return await Task.FromResult(r.Next(1000));
}
}
Result<A>
provides an implicit cast operator that allow to return a Result<A>
and the function to return an A
.
I will add ValidationUtils
class for completeness, but I think it is less relevant in this case:
internal class ValidationUtils<TReq, TRes> : IValidationUtils<TReq, TRes>
where TReq : class, IBaseRequest
{
private readonly IValidator<TReq> _validator;
public ValidationUtils(IValidator<TReq> validator)
{
_validator = validator;
}
public async Task<Result<TRes>> IsValid(TReq request, CancellationToken token = default)
{
var validationResult = await _validator.ValidateAsync(request, token);
if (!validationResult.IsValid)
{
var validationException = new ValidationException(validationResult.Errors);
return new Result<TRes>(validationException);
}
var dummyObj = Activator.CreateInstance<TRes>();
return dummyObj;
}
}
What I do not like is that virtually any command must include that validation logic at the beginning and I feel that it can be put in a MediatR pipeline behaviour that would execute for each command and trigger the validation, if one is defined. My non-working code is currently the following:
internal class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>, IBaseRequest
where TResponse: struct
{
private readonly IValidationUtils<TRequest, TResponse> _validationUtils;
///
public ValidationBehaviour(IValidationUtils<TRequest, TResponse> validationUtils)
{
_validationUtils = validationUtils;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken token,
RequestHandlerDelegate<TResponse> next)
{
bool isResult = typeof(TResponse).GetGenericTypeDefinition() == typeof(Result<>);
if (!isResult)
return await next();
var validationResult = await _validationUtils.IsValid(request, token);
if (validationResult.IsFaulted)
return validationResult as dynamic;
return await next();
}
}
This compiles, but it does not work because since TResponse
is actually a Result<>
, validationResult
is a Result<Result<>>
.
One way would have been to work with another generic type to indicate the underlying type of the Result<>, but I cannot change the behavior's signature. While using Result<> usage is convenient, I could break this dependency and use my own class, but I feel I am reinventing the wheel here.
Any idea about how to make this work?
0 comment threads