Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
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