Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,31 @@ public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter
{
if (argument != null && argument.GetType().IsCustomType() && serviceProvider.GetValidator(argument.GetType()) is IValidator validator)
{
// ReSharper disable once SuspiciousTypeConversion.Global
var validatorInterceptor = validator as IValidatorInterceptor;
var globalValidationInterceptor = serviceProvider.GetService<IGlobalValidationInterceptor>();

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

if (validatorInterceptor != null)
{
validationContext = validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext) ?? validationContext;
validationContext = await validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
}

if (globalValidationInterceptor != null)
{
validationContext = globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext) ?? validationContext;
validationContext = await globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext;
}

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

if (validatorInterceptor != null)
{
validationResult = validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext) ?? validationResult;
validationResult = await validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
}

if (globalValidationInterceptor != null)
{
validationResult = globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext) ?? validationResult;
validationResult = await globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult;
}

if (!validationResult.IsValid)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FluentValidation;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;

Expand All @@ -11,7 +13,27 @@ namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors
/// </summary>
public interface IGlobalValidationInterceptor
{
public IValidationContext? BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext);
public ValidationResult? AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext);
/// <summary>
/// Executes custom logic before the validation process. Allows intercepting and altering the validation context prior to the execution of the validation rules.
/// </summary>
/// <param name="endpointFilterInvocationContext">The context of the currently executing endpoint filter, providing access to details about the HTTP request and endpoint.</param>
/// <param name="validationContext">The validation context containing information about the object being validated.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// A transformed or new <see cref="IValidationContext"/> instance to be used in the validation process, or null if no changes need to be applied.
/// </returns>
public Task<IValidationContext?> BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, CancellationToken cancellationToken = default);

/// <summary>
/// Executes custom logic after the validation process. Allows intercepting and altering the validation result or performing additional operations after the validation has been completed.
/// </summary>
/// <param name="endpointFilterInvocationContext">The context of the currently executing endpoint filter, providing access to details about the HTTP request and endpoint.</param>
/// <param name="validationContext">The validation context containing information about the object that was validated.</param>
/// <param name="validationResult">The result of the validation process, including validation errors if any exist.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// A modified or new <see cref="ValidationResult"/> instance, or null if no changes are required to the validation result.
/// </returns>
public Task<ValidationResult?> AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@
///
/// The interceptor methods of instances of this interface will only get called when the implementing validator gets validated.
/// </summary>
public interface IValidatorInterceptor : IGlobalValidationInterceptor
{
}
public interface IValidatorInterceptor : IGlobalValidationInterceptor;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
Expand Down Expand Up @@ -47,6 +49,8 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
return;
}

var validationResults = new Dictionary<IValidationContext, ValidationResult>();

foreach (var parameter in controllerActionDescriptor.Parameters)
{
if (actionExecutingContext.ActionArguments.TryGetValue(parameter.Name, out var subject))
Expand All @@ -68,24 +72,25 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC

if (validatorInterceptor != null)
{
validationContext = validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
validationContext = await validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

if (globalValidationInterceptor != null)
{
validationContext = globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
validationContext = await globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

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

if (validatorInterceptor != null)
{
validationResult = validatorInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
validationResult = await validatorInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult;
}

if (globalValidationInterceptor != null)
{
validationResult = globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
validationResult = await globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult;
}

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

actionExecutingContext.Result = fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails);
actionExecutingContext.Result = await fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails, validationResults);

return;
}
Expand All @@ -124,10 +129,7 @@ private bool IsValidController(object controller)
return false;
}

return controller is ControllerBase ||
controllerType.HasCustomAttribute<ControllerAttribute>() ||
controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
controllerType.InheritsFromTypeWithNameEndingIn("Controller");
return controller is ControllerBase || controllerType.HasCustomAttribute<ControllerAttribute>() || controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) || controllerType.InheritsFromTypeWithNameEndingIn("Controller");
}

private bool HasValidBindingSource(BindingSource? bindingSource)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FluentValidation;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc.Filters;

Expand All @@ -11,7 +13,27 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors
/// </summary>
public interface IGlobalValidationInterceptor
{
public IValidationContext? BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext);
public ValidationResult? AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext);
/// <summary>
/// Executes custom logic before the validation process. Allows intercepting and altering the validation context prior to the execution of the validation rules.
/// </summary>
/// <param name="actionExecutingContext">The context of the currently executing action, providing access to details about the HTTP request and action.</param>
/// <param name="validationContext">The validation context containing information about the object being validated.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// A transformed or new <see cref="IValidationContext"/> instance to be used in the validation process, or null if no changes need to be applied.
/// </returns>
public Task<IValidationContext?> BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, CancellationToken cancellationToken = default);

/// <summary>
/// Executes custom logic after the validation process. Allows intercepting and altering the validation result or performing additional operations after the validation has been completed.
/// </summary>
/// <param name="actionExecutingContext">The context of the currently executing action, providing access to details about the HTTP request and action.</param>
/// <param name="validationContext">The validation context containing information about the object that was validated.</param>
/// <param name="validationResult">The result of the validation process, including validation errors if any exist.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// A modified or new <see cref="ValidationResult"/> instance, or null if no changes are required to the validation result.
/// </returns>
public Task<ValidationResult?> AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@
///
/// The interceptor methods of instances of this interface will only get called when the implementing validator gets validated.
/// </summary>
public interface IValidatorInterceptor : IGlobalValidationInterceptor
{
}
public interface IValidatorInterceptor : IGlobalValidationInterceptor;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results
{
public class FluentValidationAutoValidationDefaultResultFactory : IFluentValidationAutoValidationResultFactory
{
public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails)
public Task<IActionResult?> CreateActionResult(ActionExecutingContext context, ValidationProblemDetails validationProblemDetails, IDictionary<IValidationContext, ValidationResult> validationResults)
{
return new BadRequestObjectResult(validationProblemDetails);
return Task.FromResult<IActionResult?>(new BadRequestObjectResult(validationProblemDetails));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results
Expand All @@ -10,7 +14,8 @@ public interface IFluentValidationAutoValidationResultFactory
/// </summary>
/// <param name="context">The <see cref="ActionExecutingContext"/> associated with the current request/response.</param>
/// <param name="validationProblemDetails">The <see cref="ValidationProblemDetails"/> instance object containing the validation failures.</param>
/// <returns>The <see cref="IActionResult"/> object to be executed by the controller action.</returns>
public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails);
/// <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>
/// <returns>The <see cref="IActionResult"/> object to be executed by the controller action or null to prevent short-circuiting the action.</returns>
public Task<IActionResult?> CreateActionResult(ActionExecutingContext context, ValidationProblemDetails validationProblemDetails, IDictionary<IValidationContext, ValidationResult> validationResults);
}
}
Loading
Loading