Replies: 3 comments 2 replies
-
Please try the following: cfg.AddOpenBehavior(typeof(RequestExplicitValidationBehavior<,>)); public class RequestExplicitValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
//this part may be a bit more complex to ensure OneOf has ValidationError(or ValidationException) as one of the parameters
private static readonly bool _isOneOfError = typeof(TResponse).IsAssignableTo(typeof(IOneOf));
public RequestExplicitValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
List<ValidationFailure>? failures = null;
foreach (IValidator<TRequest> v in _validators)
{
ValidationResult vr = await v.ValidateAsync(request, cancellationToken).ConfigureAwait(false);
if (!vr.IsValid)
{
if (failures is null)
{
failures = vr.Errors;
}
else
{
failures.AddRange(vr.Errors);
}
}
}
if (failures is { Count: > 0 })
{
if (_isOneOfError)
{
//don't like the dynamic keyword here
return (dynamic)new ValidationException(failures);
}
//can be impl via ThrowHelper
throw new ValidationException(failures);
}
return await next().ConfigureAwait(false);
}
} I've performed some simple tests, and it looks like the dynamic's overhead is much less than what results in ValidationException throwing. MemoryDiagnoser(true)]
public class OneOfVsException
{
[Benchmark]
public void Exception()
{
try
{
throw new ValidationException("test");
}
catch (ValidationException e)
{
}
}
[Benchmark]
public void OneOfAndDynamic()
{
_ = DoTest<ResponseOrValidation<string>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TResponse DoTest<TResponse>()
{
return (dynamic)new ValidationException("test");
}
|
Beta Was this translation helpful? Give feedback.
-
Having found this discussion a bit late, but here is my alternative approach to this. Instead of dynamic it is using compiled expression to use the implicit cast operator created by OneOf. It uses a constrain on the response type generic argyment that is has to be a In the logic in this behavior the pipeline continues without running the validation if the OneOf response type does not contain the custom type public sealed class OneOfValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
where TResponse : IOneOf
{
private static bool s_implicitConversionChecked;
private static Func<ValidationErrors, TResponse>? s_implicitConversionFunc;
private readonly IEnumerable<IValidator<TRequest>> _validators;
public OneOfValidationBehavior( IEnumerable<IValidator<TRequest>> validators )
{
_validators = validators;
}
public async Task<TResponse> Handle( TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken )
{
if ( s_implicitConversionFunc is null && !s_implicitConversionChecked )
{
Type responseType = typeof( TResponse );
if ( responseType.IsGenericType &&
responseType.GenericTypeArguments.Any( t => t == typeof( ValidationErrors ) ) )
{
MethodInfo? implicitConversionMethod = responseType.GetMethod( "op_Implicit", [typeof( ValidationErrors )] );
if ( implicitConversionMethod is not null )
{
ParameterExpression errorsParam = Expression.Parameter( typeof( ValidationErrors ), "e" );
s_implicitConversionFunc =
Expression.Lambda<Func<ValidationErrors, TResponse>>( Expression.Call( implicitConversionMethod, errorsParam ), errorsParam ).Compile();
}
}
s_implicitConversionChecked = true;
}
if ( s_implicitConversionFunc is not null )
{
var context = new ValidationContext<TRequest>( request );
ValidationResult[] validationResults = await Task.WhenAll( _validators.Select( v => v.ValidateAsync( context, cancellationToken ) ) );
ValidationResult validationResult = new ValidationResult( validationResults );
if ( !validationResult.IsValid )
{
IEnumerable<ValidationError> errors = validationResult.Errors
.Select( e => new ValidationError( e.PropertyName, e.ErrorMessage, e.AttemptedValue ) );
return s_implicitConversionFunc( new ValidationErrors( errors ) );
}
}
TResponse res = await next();
return res;
}
} [InProcess]
[MemoryDiagnoser]
public class OneOfBenchMarkTest
{
private static readonly MethodInfo ImplicitCastTestA = typeof( OneOf<TestA, TestB> ).GetMethod( "op_Implicit", [typeof( TestA )] )!;
private static readonly Func<TestA, OneOf<TestA, TestB>> ExpressionFunc;
private static readonly TestA Instance = new();
private static readonly object[] Parameters = [Instance];
static OneOfBenchMarkTest()
{
ParameterExpression aParam = Expression.Parameter( typeof( TestA ), "a" );
var lambda = Expression.Lambda<Func<TestA, OneOf<TestA, TestB>>>( Expression.Call( ImplicitCastTestA, aParam ), aParam );
ExpressionFunc = lambda.Compile();
}
[Benchmark]
public void ConvertUsingReflection()
{
_ = ImplicitCast<OneOf<TestA, TestB>>();
}
[Benchmark]
public void ConvertUsingDynamic()
{
_ = Dynamic<OneOf<TestA, TestB>>();
}
[Benchmark]
public void ConvertUsingExpression()
{
_ = ExpressionFunc( Instance );
}
private TResponse Dynamic<TResponse>()
{
return (dynamic)Instance;
}
private TResponse ImplicitCast<TResponse>()
{
return (TResponse)ImplicitCastTestA.Invoke( null, Parameters )!;
}
private class TestA()
{
}
private class TestB()
{
}
}
|
Beta Was this translation helpful? Give feedback.
-
I literally just did this same thing but with my Ardalis.Result package instead of OneOf: |
Beta Was this translation helpful? Give feedback.
-
Hello there. I got a question about a Pipeline-Behaviour in conjunction with OneOf and FluentValidation. A simple example:
A simple create person request & response:
public record CreatePersonCommandResponse(long NewPersonId);
public record CreatePersonCommand : IRequest<OneOf<CreatePersonCommandResponse, ValidationError>>
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
}
I would like to express something like this: Either the command executes and everything is fine or any kind of validation error occured. One solution is the nuget package 'OneOf' for discriminated unions.
I create an AuthorizationBehaviour-Class that should handle the cases in which the user is not authenticated to access the handler:
internal sealed class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, OneOf<TResponse, ValidationError>> where TRequest : IRequest<OneOf<TResponse, ValidationError>>
My registration looks like this:
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(executing);
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehaviour<,>));
});
Can anyone tell me, why my Behaviour is never called?
Best regards!
Michael
Beta Was this translation helpful? Give feedback.
All reactions