Skip to content

Commit

Permalink
add configurable mvc configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdgun committed Aug 17, 2023
1 parent d53beac commit 2b4b6f4
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 18 deletions.
5 changes: 5 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
<NoWarn>NU1701</NoWarn>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
Expand All @@ -18,4 +19,8 @@
<RepositoryUrl>https://github.com/SharpGrip/FluentValidation.AutoValidation</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions
{
public static class EndpointRouteExtensions
{
/// <summary>
/// Adds asynchronous minimal API automatic validation to the specified <see cref="T:Microsoft.AspNetCore.Builder.RouteHandlerBuilder" />.
/// </summary>
/// <param name="routeHandlerBuilder">The route handler builder.</param>
/// <returns>The route handler builder.</returns>
public static RouteHandlerBuilder AddFluentValidationAutoValidation(this RouteHandlerBuilder routeHandlerBuilder)
{
routeHandlerBuilder.AddEndpointFilter<FluentValidationAutoValidationEndpointFilter>();

return routeHandlerBuilder;
}

/// <summary>
/// Adds asynchronous minimal API Fluent Validation automatic validation to the specified <see cref="T:Microsoft.AspNetCore.Routing.RouteGroupBuilder" />.
/// </summary>
/// <param name="routeGroupBuilder">The route group builder.</param>
/// <returns>The route group builder.</returns>
public static RouteGroupBuilder AddFluentValidationAutoValidation(this RouteGroupBuilder routeGroupBuilder)
{
routeGroupBuilder.AddEndpointFilter<FluentValidationAutoValidationEndpointFilter>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
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.Class | AttributeTargets.Method)]
public class FluentValidationAutoValidationAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration
{
public class AutoValidationMvcConfiguration
{
public bool DisableDataAnnotationsValidation { get; set; }
public ValidationStrategy ValidationStrategy = ValidationStrategy.All;

Check warning on line 8 in FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)

Check warning on line 8 in FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)

Check warning on line 8 in FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs

View workflow job for this annotation

GitHub Actions / build

Make this field 'private' and encapsulate it in a 'public' property. (https://rules.sonarsource.com/csharp/RSPEC-1104)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Enums
{
public enum ValidationStrategy
{
/// <summary>
/// Enables asynchronous automatic validation on all controllers inheriting from `Microsoft.AspNetCore.Mvc.ControllerBase`.
/// </summary>
All = 1,

/// <summary>
/// Enables asynchronous automatic validation on controllers inheriting from `Microsoft.AspNetCore.Mvc.ControllerBase` decorated with a `SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes.FluentValidationAutoValidationAttribute` attribute.
/// </summary>
Annotation = 2

//Validation strategy `ValidationStrategy.All` enables automatic validation on all instances of `ControllerBase`. Validation strategy `ValidationStrategy.Annotations` only enables automatic validation on all instances of `ControllerBase` with class or method decorations of the `FluentValidationAutoValidation` attribute.
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Filters;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Validation;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddFluentValidationAutoValidation(this IServiceCollection serviceCollection)
/// <summary>
/// Adds asynchronous MVC Fluent Validation automatic validation to the specified <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
/// <param name="autoValidationMvcConfiguration">The configuration delegate used to configure the FluentValidation AutoValidation MVC validation.</param>
/// <returns>The service collection.</returns>
public static IServiceCollection AddFluentValidationAutoValidation(this IServiceCollection serviceCollection, Action<AutoValidationMvcConfiguration>? autoValidationMvcConfiguration = null)
{
var defaultAutoValidationMvcConfiguration = new AutoValidationMvcConfiguration();

if (autoValidationMvcConfiguration != null)
{
autoValidationMvcConfiguration.Invoke(defaultAutoValidationMvcConfiguration);
serviceCollection.Configure(autoValidationMvcConfiguration);
}

if (defaultAutoValidationMvcConfiguration.DisableDataAnnotationsValidation)
{
serviceCollection.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
}

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

// Make sure we insert the `FluentValidationAutoValidationActionFilter` before the built-in `ModelStateInvalidFilter` to prevent it short-circuiting the request.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Filters
{
public class FluentValidationAutoValidationActionFilter : IAsyncActionFilter
{
private readonly IServiceProvider serviceProvider;
private readonly AutoValidationMvcConfiguration autoValidationMvcConfiguration;

public FluentValidationAutoValidationActionFilter(IServiceProvider serviceProvider)
public FluentValidationAutoValidationActionFilter(IServiceProvider serviceProvider, IOptions<AutoValidationMvcConfiguration> autoValidationMvcConfiguration)
{
this.serviceProvider = serviceProvider;
this.autoValidationMvcConfiguration = autoValidationMvcConfiguration.Value;
}

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)

Check warning on line 27 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 29 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 27 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 29 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
if (context.Controller is ControllerBase controllerBase)
{
foreach (var parameter in context.ActionDescriptor.Parameters)
var actionDescriptor = context.ActionDescriptor;

// @todo figure out a better way to retrieve the attribute since using the `context.ActionDescriptor.EndpointMetadata` is not recommended for application code

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
if (autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotation && !actionDescriptor.EndpointMetadata.OfType<FluentValidationAutoValidationAttribute>().Any())
{
await next();

return;
}

foreach (var parameter in actionDescriptor.Parameters)
{
var subject = context.ActionArguments[parameter.Name];
var parameterType = parameter.ParameterType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation
{
public class NullObjectModelValidator : IObjectModelValidator
{
public void Validate(ActionContext actionContext, ValidationStateDictionary? validationState, string prefix, object? model)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

<ItemGroup>
<PackageReference Include="FluentValidation" Version="[10.0,]" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
48 changes: 34 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,59 @@

## Introduction

SharpGrip FluentValidation AutoValidation is an extension of the [FluentValidation](https://github.com/FluentValidation/FluentValidation) 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 automation 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) 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 automation validation for minimal APIs (endpoints). It enables developers to easily implement automatic validation in
their projects.

## Installation

Register your validators with the Microsoft DI service container, for reference please see https://docs.fluentvalidation.net/en/latest/di.html.
Register your validators with the Microsoft DI service container, for instructions on settings that up please see https://docs.fluentvalidation.net/en/latest/di.html.

### MVC controllers
For MVC controllers reference NuGet package `SharpGrip.FluentValidation.AutoValidation.Mvc` (https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Mvc).

### Minimal APIs
For minimal APIs (endpoints) reference NuGet package `SharpGrip.FluentValidation.AutoValidation.Endpoints` (https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Endpoints).

## Usage

### MVC controllers
For MVC controllers reference NuGet package `SharpGrip.FluentValidation.AutoValidation.Mvc` (https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Mvc).

```
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
builder.Services.AddFluentValidationAutoValidation();
```

### Minimal APIs (endpoints)
### Minimal APIs

For minimal APIs (endpoints) reference NuGet package `SharpGrip.FluentValidation.AutoValidation.Endpoints` (https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Endpoints).

Enabling minimal API (endpoint) automatic validation can be done on both route groups and routes.

```
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions;
var endpointGroup = app.MapGroup("/some-group").AddFluentValidationAutoValidation();
endpointGroup.MapPost("/", (TestCreateModel testCreateModel) => $"Hello {testCreateModel.Name}");
endpointGroup.MapPost("/", (SomeModel someModel) => $"Hello {someModel.Name}");
app.MapPost("/", (SomeOtherModel someOtherModel) => $"Hello again {someOtherModel.Name}").AddFluentValidationAutoValidation();
```

## Configuration

### MVC controllers

| Property | Default value | Description |
|----------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DisableDataAnnotationsValidation | `false` | Disables the built-in model validation. |
| ValidationStrategy | `ValidationStrategy.All` | Configures the validation strategy. Validation strategy `ValidationStrategy.All` enables asynchronous automatic validation on all controllers inheriting from `ControllerBase`. Validation strategy `ValidationStrategy.Annotations` enables asynchronous automatic validation on controllers inheriting from `ControllerBase` decorated (class or method) with a `FluentValidationAutoValidationAttribute` attribute. |

```
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
builder.Services.AddFluentValidationAutoValidation(configuration =>
{
configuration.DisableDataAnnotationsValidation = true;
app.MapPost("/", (TestCreateModel testCreateModel) => $"Hello again {testCreateModel.Name}").AddFluentValidationAutoValidation();
// Only validate controllers decorated with the `FluentValidationAutoValidation` attribute.
configuration.ValidationStrategy = ValidationStrategy.Annotation;
});
```

0 comments on commit 2b4b6f4

Please sign in to comment.