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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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