Skip to content

Commit f5c48a7

Browse files
committed
Merge branch 'release/2.0' into 74-allow-validation-of-the-struct-dto-or-explicitly-document-unsupported-types
2 parents fdfb378 + 741e05c commit f5c48a7

21 files changed

Lines changed: 195 additions & 88 deletions

Directory.Build.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<PropertyGroup>
33
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
44
<Nullable>enable</Nullable>
5-
<LangVersion>8.0</LangVersion>
65
<NoWarn>NU1701</NoWarn>
76
<EmbedUntrackedSources>true</EmbedUntrackedSources>
87
<IncludeSymbols>true</IncludeSymbols>

FluentValidation.AutoValidation.Endpoints/FluentValidation.AutoValidation.Endpoints.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
20+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.102">
2121
<PrivateAssets>all</PrivateAssets>
2222
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2323
</PackageReference>

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/FluentValidation.AutoValidation.Mvc.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
20+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.102">
2121
<PrivateAssets>all</PrivateAssets>
2222
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2323
</PackageReference>

FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static IServiceCollection AddFluentValidationAutoValidation(this IService
5151
}
5252

5353
// Create a default instance of the `ModelStateInvalidFilter` to access the non static property `Order` in a static context.
54-
var modelStateInvalidFilter = new ModelStateInvalidFilter(new ApiBehaviorOptions {InvalidModelStateResponseFactory = context => new OkResult()}, NullLogger.Instance);
54+
var modelStateInvalidFilter = new ModelStateInvalidFilter(new ApiBehaviorOptions {InvalidModelStateResponseFactory = _ => new OkResult()}, NullLogger.Instance);
5555

5656
// Make sure we insert the `FluentValidationAutoValidationActionFilter` before the built-in `ModelStateInvalidFilter` to prevent it short-circuiting the request.
5757
serviceCollection.Configure<MvcOptions>(options => options.Filters.Add<FluentValidationAutoValidationActionFilter>(modelStateInvalidFilter.Order - 1));

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
}

0 commit comments

Comments
 (0)