Is it possible to use Result objects in IPipelineBehaviourHandlers ? #918
Replies: 2 comments 1 reply
-
I suppose a more generalised version of this question is: Is it possible to swap out the return object in an implementation of public class MyBehaviour<TRequest,TResult> : IPipelineBehaviour<TRequest,TResult>
where TRequest : IRequest<TResult>
where TResult : OneOf<???,Problem>
{
// Implementation goes here
} The ??? denotes information that is missing. Is there a mechanism implemented to make this kind of assertion to the implementation? If registered with concrete types, it works, but that is then pointless as there would be one handler registered for each services.AddTransient(typeof(IPipelineBehaviour<DevCmd,Oneof<Object,ProblemDetails>>), typeof(ValidationBehaviour<OneOf<Object,ProblemDetails>>); |
Beta Was this translation helpful? Give feedback.
-
I cannot say I found a solution but I made it work with inheritance and added the pipeline functionality to the I switched to using public abstract class ARequest<TResponse> : IRequest<OneOf<TResponse,Problem>>
{
internal Guid MediatorRequestId { init; get; } = Guid.NewGuid();
public Guid GetRequestId() => MediatorRequestId;
internal Stopwatch Stopwatch { init; get; } = new Stopwatch();
public TimeSpan GetElapsedTime() => Stopwatch.Elapsed;
}
namespace Championpoker.Backoffice.Application.Cqrs.Common;
internal abstract class ARequestHandler<TRequest,TResponse> : IRequestHandler<TRequest,OneOf<TResponse, Problem>>
where TRequest : ARequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
private readonly ILogger<ARequestHandler<TRequest, TResponse>> _logger;
public ARequestHandler(
ILogger<ARequestHandler<TRequest,TResponse>> logger,
IEnumerable<IValidator<TRequest>> validators)
{
_logger = logger;
_validators = validators;
}
public abstract Task<OneOf<TResponse, Problem>> HandleImpl(TRequest request, CancellationToken cancellationToken);
public async Task<OneOf<TResponse, Problem>> Handle(TRequest request, CancellationToken cancellationToken)
{
// TIME THE OPERATION
request.Stopwatch.Restart();
// ------------------------------------------------------------------------------------------------------------
// PERFORM THE ACTUAL OPERATION
var response = await GenerateResponseAsync(request, cancellationToken);
await PostProcessResponseAsync(request, response, cancellationToken);
// ------------------------------------------------------------------------------------------------------------
// END TIMING
request.Stopwatch.Stop();
return response;
}
private async Task<OneOf<TResponse, Problem>> GenerateResponseAsync(TRequest request, CancellationToken cancellationToken)
{
// LOG INCOMING REQUESTS
// This allows for tracing what happened in the past
_logger.LogInformation("[Processing: {RequestId}] {Request}", request.MediatorRequestId, JsonSerializer.Serialize(request));
// VALIDATE INCOMING REQUESTS
// I am deeply agitated that this cannot be achieved through an IPipelineBehaviour, but apparently it is not
// possible to use OneOf in IPipelineHandlers to return a Problem Instance there
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(x => x.ValidateAsync(context, cancellationToken)));
var failures = validationResults.Where(r => r.Errors.Any()).SelectMany(x => x.Errors).ToList();
if (failures.Any())
return Problem.RequestValidationFailed(failures.Select(x => x.ErrorMessage));
}
// WRAP LOGIC INTO TRY-CATCH
// We do not want to expose Exceptions to the consumer. If an exception is thrown, it is converted to a Problem instance
// and the user is informed about the failed request that way
try
{
return await HandleImpl(request, cancellationToken);
}
catch (Exception e)
{
return Problem.ModelExceptionCaught(e);
}
}
private async Task PostProcessResponseAsync(
TRequest request,
OneOf<TResponse, Problem> response,
CancellationToken cancellationToken)
{
await Task.CompletedTask;
// LOG REASONS FOR FAILED REQUESTS
if (response.IsT1)
_logger.LogWarning("[Failed: {RequestId}] {Response}", request.MediatorRequestId, JsonSerializer.Serialize(response.AsT1));
}
} And then each actual request looks similar to this: public class LastUpdatedQuery : ARequest<DateTimeOffset>
{
}
internal class LastUpdatedQueryHandler : ARequestHandler<LastUpdatedQuery, DateTimeOffset>
{
private readonly IApplicationDbContext _dbContext;
public LastUpdatedQueryHandler(
ILogger<ARequestHandler<LastUpdatedQuery, DateTimeOffset>> logger,
IEnumerable<IValidator<LastUpdatedQuery>> validators,
IApplicationDbContext dbContext)
: base(logger, validators)
{
_dbContext = dbContext;
}
public override async Task<OneOf<DateTimeOffset, Problem>> HandleImpl(LastUpdatedQuery request, CancellationToken cancellationToken)
{
DateTimeOffset lastImported = await _dbContext.ActivitySummaries
.Select(x => x.InclFrom)
.OrderByDescending(x => x)
.FirstOrDefaultAsync(cancellationToken);
return lastImported;
}
} |
Beta Was this translation helpful? Give feedback.
-
My project uses https://github.com/mcintyre321/OneOf (similar to
Result
objects) to avoid throwing exceptions. It works well withIRequest<OneOf<T, ProblemDetails>>
andIRequestHandler<TRequest,OneOf<T,ProblemDetails>>
, but now I am implementing validation viaIPipelineBehaviour<TRequest, TResult>
and want to returnProblemDetails
if validation fails andnext()
if the validation succeeds. The problem here is that, even thoughTResult
is guaranteed to beOneOf<T,ProblemDetails>
for someT
, the methodHandle(...)
has no way of knowing that and hence does not let me return objects the way I want.I tried declaring it as
public class ValidationBehaviour<TRequest,TResult> : IPipelineBehaviour<TRequest,OneOf<TResult,ProblemDetails>>
but in that case the handler is not invoked, even though registered asservices.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
The same would go with some
Result<T>
. Is there a workaround for this issue or is there no way around throwingException
s ?Beta Was this translation helpful? Give feedback.
All reactions