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 @@ -20,10 +20,8 @@ public FluentValidationAutoValidationEndpointFilter(IServiceProvider serviceProv

public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext endpointFilterInvocationContext, EndpointFilterDelegate next)
{
for (var i = 0; i < endpointFilterInvocationContext.Arguments.Count; i++)
foreach (var argument in endpointFilterInvocationContext.Arguments)
{
var argument = endpointFilterInvocationContext.Arguments[i];

if (argument != null && argument.GetType().IsCustomType() && serviceProvider.GetValidator(argument.GetType()) is IValidator validator)
{
// ReSharper disable once SuspiciousTypeConversion.Global
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes
{
[AttributeUsage(AttributeTargets.Parameter)]
public class AutoValidateAlways : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter)]
public class AutoValidateNever : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AutoValidationAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes
{
[Obsolete("Attribute is obsolete and will be removed in v2. Use the [AutoValidation] attribute instead.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FluentValidationAutoValidationAttribute : Attribute
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
var endpoint = actionExecutingContext.HttpContext.GetEndpoint();
var controllerActionDescriptor = (ControllerActionDescriptor) actionExecutingContext.ActionDescriptor;

if (autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotations &&
endpoint != null && !endpoint.Metadata.OfType<FluentValidationAutoValidationAttribute>().Any())
if (endpoint != null &&
((autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotations &&
!endpoint.Metadata.OfType<FluentValidationAutoValidationAttribute>().Any() && !endpoint.Metadata.OfType<AutoValidationAttribute>().Any()) ||
endpoint.Metadata.OfType<AutoValidateNever>().Any()))
{
HandleUnvalidatedEntries(actionExecutingContext);

Expand All @@ -54,15 +56,14 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
{
if (actionExecutingContext.ActionArguments.TryGetValue(parameter.Name, out var subject))
{
var parameterInfo = ((ControllerParameterDescriptor) parameter).ParameterInfo;
var parameterType = parameter.ParameterType;
var bindingSource = parameter.BindingInfo?.BindingSource;

if (subject != null && parameterType.IsCustomType() &&
((autoValidationMvcConfiguration.EnableBodyBindingSourceAutomaticValidation && bindingSource == BindingSource.Body) ||
(autoValidationMvcConfiguration.EnableFormBindingSourceAutomaticValidation && bindingSource == BindingSource.Form) ||
(autoValidationMvcConfiguration.EnableQueryBindingSourceAutomaticValidation && bindingSource == BindingSource.Query) ||
(autoValidationMvcConfiguration.EnablePathBindingSourceAutomaticValidation && bindingSource == BindingSource.Path) ||
(autoValidationMvcConfiguration.EnableCustomBindingSourceAutomaticValidation && bindingSource == BindingSource.Custom)))
var hasAutoValidateAlwaysAttribute = parameterInfo.HasCustomAttribute<AutoValidateAlways>();
var hasAutoValidateNeverAttribute = parameterInfo.HasCustomAttribute<AutoValidateNever>();

if (subject != null && parameterType.IsCustomType() && !hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)))
{
if (serviceProvider.GetValidator(parameterType) is IValidator validator)
{
Expand Down Expand Up @@ -121,6 +122,15 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
await next();
}

private bool HasValidBindingSource(BindingSource? bindingSource)
{
return (autoValidationMvcConfiguration.EnableBodyBindingSourceAutomaticValidation && bindingSource == BindingSource.Body) ||
(autoValidationMvcConfiguration.EnableFormBindingSourceAutomaticValidation && bindingSource == BindingSource.Form) ||
(autoValidationMvcConfiguration.EnableQueryBindingSourceAutomaticValidation && bindingSource == BindingSource.Query) ||
(autoValidationMvcConfiguration.EnablePathBindingSourceAutomaticValidation && bindingSource == BindingSource.Path) ||
(autoValidationMvcConfiguration.EnableCustomBindingSourceAutomaticValidation && bindingSource == BindingSource.Custom);
}

private void HandleUnvalidatedEntries(ActionExecutingContext context)
{
if (autoValidationMvcConfiguration.DisableBuiltInModelValidation)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Linq;
using System.Reflection;

namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions
{
public static class ParameterInfoExtensions
{
public static bool HasCustomAttribute<TAttribute>(this ParameterInfo parameterInfo) where TAttribute : Attribute
{
return parameterInfo.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(TAttribute));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ public static bool IsCustomType(this Type? type)

return type != null && type.IsClass && !type.IsEnum && !type.IsValueType && !type.IsPrimitive && !builtInTypes.Contains(type);
}

public static bool HasCustomAttribute<TAttribute>(this Type type) where TAttribute : Attribute
{
return type.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(TAttribute));
}
}
}
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@

## Introduction

SharpGrip FluentValidation AutoValidation is an extension of the [FluentValidation](https://github.com/FluentValidation/FluentValidation) (v10+) library enabling automatic asynchronous validation in
MVC controllers and minimal APIs (endpoints).
The library [FluentValidation.AspNetCore](https://github.com/FluentValidation/FluentValidation.AspNetCore) is no longer being maintained and is unsupported. As a result, support for automatic
validation provided by this library is no longer available.
This library re-introduces this functionality for MVC controllers and introduces automatic validation for minimal APIs (endpoints). It enables developers to easily implement automatic validation in
their projects.
SharpGrip FluentValidation AutoValidation is an extension of the [FluentValidation](https://github.com/FluentValidation/FluentValidation) (v10+) library enabling automatic asynchronous validation in MVC controllers and minimal APIs (endpoints).
The library [FluentValidation.AspNetCore](https://github.com/FluentValidation/FluentValidation.AspNetCore) is no longer being maintained and is unsupported. As a result, support for automatic validation provided by this library is no longer available.
This library re-introduces this functionality for MVC controllers and introduces automatic validation for minimal APIs (endpoints). It enables developers to easily implement automatic validation in their projects.

## Installation

Expand Down Expand Up @@ -120,14 +117,25 @@ public class CustomResultFactory : IFluentValidationAutoValidationResultFactory
public IResult CreateResult(EndpointFilterInvocationContext context, ValidationResult validationResult)
{
var validationProblemErrors = validationResult.ToValidationProblemErrors();

return Results.ValidationProblem(validationProblemErrors, "Some details text.", "Some instance text.", (int) HttpStatusCode.BadRequest, "Some title.");
}
}
```

## Validation attributes

### MVC controllers
Customizing automatic validation behavior is achievable through the use of attributes.

The `[AutoValidateAlways]` attribute can be applied to a controller parameter, compelling automatic validation to disregard the validation check for a valid binding source.
This proves useful when the `ApiBehaviorOptions.SuppressInferBindingSourcesForParameters` option is enabled, and a custom model is used, with parameters bound from multiple binding sources.

The `[AutoValidateNever]` attribute can be placed on a controller class, controller method, or controller parameter, instructing automatic validation to be skipped.

## Validation interceptors

Note: Using validation interceptors is considered to be an advanced feature and is not needed for most use cases.
**Note:** Using validation interceptors is considered to be an advanced feature and is not needed for most use cases.

Validation interceptors allow you to intercept and alter the validation process by either implementing the `IGlobalValidationInterceptor` interface in a custom class or by implementing
the `IValidatorInterceptor` on a single validator.
Expand Down Expand Up @@ -217,7 +225,6 @@ public class CustomGlobalValidationInterceptor : IValidationInterceptor
}
}


// Example of a single validator interceptor.
private class TestValidator : AbstractValidator<TestModel>, IValidatorInterceptor
{
Expand Down