Skip to content

Commit 741e05c

Browse files
authored
improve interceptorsa and result factories (#78)
1 parent eed287b commit 741e05c

File tree

15 files changed

+189
-81
lines changed

15 files changed

+189
-81
lines changed

FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,31 @@ public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter
1818
{
1919
if (argument != null && argument.GetType().IsCustomType() && serviceProvider.GetValidator(argument.GetType()) is IValidator validator)
2020
{
21-
// ReSharper disable once SuspiciousTypeConversion.Global
2221
var validatorInterceptor = validator as IValidatorInterceptor;
2322
var globalValidationInterceptor = serviceProvider.GetService<IGlobalValidationInterceptor>();
2423

2524
IValidationContext validationContext = new ValidationContext<object>(argument);
2625

2726
if (validatorInterceptor != null)
2827
{
29-
validationContext = validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext) ?? validationContext;
28+
validationContext = await validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
3029
}
3130

3231
if (globalValidationInterceptor != null)
3332
{
34-
validationContext = globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext) ?? validationContext;
33+
validationContext = await globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
3534
}
3635

3736
var validationResult = await validator.ValidateAsync(validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted);
3837

3938
if (validatorInterceptor != null)
4039
{
41-
validationResult = validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext) ?? validationResult;
40+
validationResult = await validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
4241
}
4342

4443
if (globalValidationInterceptor != null)
4544
{
46-
validationResult = globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext) ?? validationResult;
45+
validationResult = await globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
4746
}
4847

4948
if (!validationResult.IsValid)

FluentValidation.AutoValidation.Endpoints/src/Interceptors/IGlobalValidationInterceptor.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using FluentValidation;
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using FluentValidation;
24
using FluentValidation.Results;
35
using Microsoft.AspNetCore.Http;
46

@@ -11,7 +13,27 @@ namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors
1113
/// </summary>
1214
public interface IGlobalValidationInterceptor
1315
{
14-
public IValidationContext? BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext);
15-
public ValidationResult? AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext);
16+
/// <summary>
17+
/// Executes custom logic before the validation process. Allows intercepting and altering the validation context prior to the execution of the validation rules.
18+
/// </summary>
19+
/// <param name="endpointFilterInvocationContext">The context of the currently executing endpoint filter, providing access to details about the HTTP request and endpoint.</param>
20+
/// <param name="validationContext">The validation context containing information about the object being validated.</param>
21+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
22+
/// <returns>
23+
/// A transformed or new <see cref="IValidationContext"/> instance to be used in the validation process, or null if no changes need to be applied.
24+
/// </returns>
25+
public Task<IValidationContext?> BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, CancellationToken cancellationToken = default);
26+
27+
/// <summary>
28+
/// Executes custom logic after the validation process. Allows intercepting and altering the validation result or performing additional operations after the validation has been completed.
29+
/// </summary>
30+
/// <param name="endpointFilterInvocationContext">The context of the currently executing endpoint filter, providing access to details about the HTTP request and endpoint.</param>
31+
/// <param name="validationContext">The validation context containing information about the object that was validated.</param>
32+
/// <param name="validationResult">The result of the validation process, including validation errors if any exist.</param>
33+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
34+
/// <returns>
35+
/// A modified or new <see cref="ValidationResult"/> instance, or null if no changes are required to the validation result.
36+
/// </returns>
37+
public Task<ValidationResult?> AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default);
1638
}
1739
}

FluentValidation.AutoValidation.Endpoints/src/Interceptors/IValidatorInterceptor.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,5 @@
55
///
66
/// The interceptor methods of instances of this interface will only get called when the implementing validator gets validated.
77
/// </summary>
8-
public interface IValidatorInterceptor : IGlobalValidationInterceptor
9-
{
10-
}
8+
public interface IValidatorInterceptor : IGlobalValidationInterceptor;
119
}

FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Threading.Tasks;
45
using FluentValidation;
6+
using FluentValidation.Results;
57
using Microsoft.AspNetCore.Http;
68
using Microsoft.AspNetCore.Mvc;
79
using Microsoft.AspNetCore.Mvc.Controllers;
@@ -47,6 +49,8 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
4749
return;
4850
}
4951

52+
var validationResults = new Dictionary<IValidationContext, ValidationResult>();
53+
5054
foreach (var parameter in controllerActionDescriptor.Parameters)
5155
{
5256
if (actionExecutingContext.ActionArguments.TryGetValue(parameter.Name, out var subject))
@@ -68,24 +72,25 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
6872

6973
if (validatorInterceptor != null)
7074
{
71-
validationContext = validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
75+
validationContext = await validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
7276
}
7377

7478
if (globalValidationInterceptor != null)
7579
{
76-
validationContext = globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
80+
validationContext = await globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
7781
}
7882

7983
var validationResult = await validator.ValidateAsync(validationContext, actionExecutingContext.HttpContext.RequestAborted);
84+
validationResults.Add(validationContext, validationResult);
8085

8186
if (validatorInterceptor != null)
8287
{
83-
validationResult = validatorInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
88+
validationResult = await validatorInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult;
8489
}
8590

8691
if (globalValidationInterceptor != null)
8792
{
88-
validationResult = globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
93+
validationResult = await globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult;
8994
}
9095

9196
if (!validationResult.IsValid)
@@ -106,7 +111,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
106111
var problemDetailsFactory = serviceProvider.GetRequiredService<ProblemDetailsFactory>();
107112
var validationProblemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionExecutingContext.HttpContext, actionExecutingContext.ModelState);
108113

109-
actionExecutingContext.Result = fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails);
114+
actionExecutingContext.Result = await fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails, validationResults);
110115

111116
return;
112117
}
@@ -124,10 +129,7 @@ private bool IsValidController(object controller)
124129
return false;
125130
}
126131

127-
return controller is ControllerBase ||
128-
controllerType.HasCustomAttribute<ControllerAttribute>() ||
129-
controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
130-
controllerType.InheritsFromTypeWithNameEndingIn("Controller");
132+
return controller is ControllerBase || controllerType.HasCustomAttribute<ControllerAttribute>() || controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) || controllerType.InheritsFromTypeWithNameEndingIn("Controller");
131133
}
132134

133135
private bool HasValidBindingSource(BindingSource? bindingSource)

FluentValidation.AutoValidation.Mvc/src/Interceptors/IGlobalValidationInterceptor.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using FluentValidation;
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using FluentValidation;
24
using FluentValidation.Results;
35
using Microsoft.AspNetCore.Mvc.Filters;
46

@@ -11,7 +13,27 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors
1113
/// </summary>
1214
public interface IGlobalValidationInterceptor
1315
{
14-
public IValidationContext? BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext);
15-
public ValidationResult? AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext);
16+
/// <summary>
17+
/// Executes custom logic before the validation process. Allows intercepting and altering the validation context prior to the execution of the validation rules.
18+
/// </summary>
19+
/// <param name="actionExecutingContext">The context of the currently executing action, providing access to details about the HTTP request and action.</param>
20+
/// <param name="validationContext">The validation context containing information about the object being validated.</param>
21+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
22+
/// <returns>
23+
/// A transformed or new <see cref="IValidationContext"/> instance to be used in the validation process, or null if no changes need to be applied.
24+
/// </returns>
25+
public Task<IValidationContext?> BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, CancellationToken cancellationToken = default);
26+
27+
/// <summary>
28+
/// Executes custom logic after the validation process. Allows intercepting and altering the validation result or performing additional operations after the validation has been completed.
29+
/// </summary>
30+
/// <param name="actionExecutingContext">The context of the currently executing action, providing access to details about the HTTP request and action.</param>
31+
/// <param name="validationContext">The validation context containing information about the object that was validated.</param>
32+
/// <param name="validationResult">The result of the validation process, including validation errors if any exist.</param>
33+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
34+
/// <returns>
35+
/// A modified or new <see cref="ValidationResult"/> instance, or null if no changes are required to the validation result.
36+
/// </returns>
37+
public Task<ValidationResult?> AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default);
1638
}
1739
}

FluentValidation.AutoValidation.Mvc/src/Interceptors/IValidatorInterceptor.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,5 @@
55
///
66
/// The interceptor methods of instances of this interface will only get called when the implementing validator gets validated.
77
/// </summary>
8-
public interface IValidatorInterceptor : IGlobalValidationInterceptor
9-
{
10-
}
8+
public interface IValidatorInterceptor : IGlobalValidationInterceptor;
119
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using FluentValidation;
4+
using FluentValidation.Results;
5+
using Microsoft.AspNetCore.Mvc;
26
using Microsoft.AspNetCore.Mvc.Filters;
37

48
namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results
59
{
610
public class FluentValidationAutoValidationDefaultResultFactory : IFluentValidationAutoValidationResultFactory
711
{
8-
public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails)
12+
public Task<IActionResult?> CreateActionResult(ActionExecutingContext context, ValidationProblemDetails validationProblemDetails, IDictionary<IValidationContext, ValidationResult> validationResults)
913
{
10-
return new BadRequestObjectResult(validationProblemDetails);
14+
return Task.FromResult<IActionResult?>(new BadRequestObjectResult(validationProblemDetails));
1115
}
1216
}
1317
}

FluentValidation.AutoValidation.Mvc/src/Results/IFluentValidationAutoValidationResultFactory.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using FluentValidation;
4+
using FluentValidation.Results;
5+
using Microsoft.AspNetCore.Mvc;
26
using Microsoft.AspNetCore.Mvc.Filters;
37

48
namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results
@@ -10,7 +14,8 @@ public interface IFluentValidationAutoValidationResultFactory
1014
/// </summary>
1115
/// <param name="context">The <see cref="ActionExecutingContext"/> associated with the current request/response.</param>
1216
/// <param name="validationProblemDetails">The <see cref="ValidationProblemDetails"/> instance object containing the validation failures.</param>
13-
/// <returns>The <see cref="IActionResult"/> object to be executed by the controller action.</returns>
14-
public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails);
17+
/// <param name="validationResults">The dictionary of <see cref="ValidationResult"/> instances keyed by the models containing the validation results from all validators that were executed.</param>
18+
/// <returns>The <see cref="IActionResult"/> object to be executed by the controller action or null to prevent short-circuiting the action.</returns>
19+
public Task<IActionResult?> CreateActionResult(ActionExecutingContext context, ValidationProblemDetails validationProblemDetails, IDictionary<IValidationContext, ValidationResult> validationResults);
1520
}
1621
}

0 commit comments

Comments
 (0)