Skip to content

Commit

Permalink
Fixed some fluent validation issues
Browse files Browse the repository at this point in the history
  • Loading branch information
david-driscoll committed May 8, 2021
1 parent 19d5022 commit 34316c8
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,29 @@ namespace Rocket.Surgery.LaunchPad.AspNetCore.OpenApi.Validation.Swashbuckle
/// </summary>
public class FluentValidationOperationFilter : IOperationFilter
{
private readonly IValidatorFactory _validatorFactory;
private readonly ILogger _logger;
private readonly SwaggerGenOptions _swaggerGenOptions;
private readonly IValidatorFactory? _validatorFactory;
private readonly IReadOnlyList<FluentValidationRule> _rules;

/// <summary>
/// Initializes a new instance of the <see cref="FluentValidationOperationFilter"/> class.
/// </summary>
/// <param name="swaggerGenOptions">Swagger generation options.</param>
/// <param name="validatorFactory">FluentValidation factory.</param>
/// <param name="validatorFactory">The validator factory.</param>
/// <param name="rules">External FluentValidation rules. External rule overrides default rule with the same name.</param>
/// <param name="loggerFactory">Logger factory.</param>
/// <param name="options">Schema generation options.</param>
public FluentValidationOperationFilter(
IOptions<SwaggerGenOptions> swaggerGenOptions,
IValidatorFactory? validatorFactory = null,
IValidatorFactory validatorFactory,
IEnumerable<FluentValidationRule>? rules = null,
ILoggerFactory? loggerFactory = null,
IOptions<FluentValidationSwaggerGenOptions>? options = null
)
{
_swaggerGenOptions = swaggerGenOptions.Value;
_validatorFactory = validatorFactory;
_swaggerGenOptions = swaggerGenOptions.Value;
_logger = loggerFactory?.CreateLogger(typeof(FluentValidationRules)) ?? NullLogger.Instance;
_rules = new DefaultFluentValidationRuleProvider(options).GetRules().ToArray().OverrideRules(rules);
}
Expand All @@ -66,12 +66,6 @@ private void ApplyInternal(OpenApiOperation operation, OperationFilterContext co
if (operation.Parameters == null)
return;

if (_validatorFactory == null)
{
_logger.LogWarning(0, "ValidatorFactory is not provided. Please register FluentValidation");
return;
}

var schemaIdSelector = _swaggerGenOptions.SchemaGeneratorOptions.SchemaIdSelector ?? new SchemaGeneratorOptions().SchemaIdSelector;

foreach (var operationParameter in operation.Parameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
Expand All @@ -19,26 +18,26 @@ namespace Rocket.Surgery.LaunchPad.AspNetCore.OpenApi.Validation.Swashbuckle
/// </summary>
public class FluentValidationRules : ISchemaFilter
{
private readonly IServiceProvider _serviceProvider;
private readonly IValidatorFactory _validatorFactory;
private readonly FluentValidationSwaggerGenOptions _options;
private readonly ILogger _logger;
private readonly IReadOnlyList<FluentValidationRule> _rules;

/// <summary>
/// Initializes a new instance of the <see cref="FluentValidationRules"/> class.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="validatorFactory">The validator factory.</param>
/// <param name="rules">External FluentValidation rules. External rule overrides default rule with the same name.</param>
/// <param name="loggerFactory"><see cref="ILoggerFactory"/> for logging. Can be null.</param>
/// <param name="options">Schema generation options.</param>
public FluentValidationRules(
IServiceProvider serviceProvider,
IValidatorFactory validatorFactory,
IEnumerable<FluentValidationRule>? rules = null,
ILoggerFactory? loggerFactory = null,
IOptions<FluentValidationSwaggerGenOptions>? options = null
)
{
_serviceProvider = serviceProvider;
_validatorFactory = validatorFactory;
_options = options?.Value ?? new FluentValidationSwaggerGenOptions();
_logger = loggerFactory?.CreateLogger(typeof(FluentValidationRules)) ?? NullLogger.Instance;
_rules = new DefaultFluentValidationRuleProvider(options).GetRules().ToArray().OverrideRules(rules);
Expand All @@ -47,12 +46,10 @@ public FluentValidationRules(
/// <inheritdoc />
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
using var scope = _serviceProvider.CreateScope();
var validatorFactory = scope.ServiceProvider.GetRequiredService<IValidatorFactory>();
IValidator? validator = null;
try
{
validator = validatorFactory.GetValidator(context.Type);
validator = _validatorFactory.GetValidator(context.Type);
}
catch (Exception e)
{
Expand Down
8 changes: 6 additions & 2 deletions src/Foundation/Conventions/FluentValidationConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ public void Register(IConventionContext context, IConfiguration configuration, I
.GetCandidateAssemblies("FluentValidation");
foreach (var item in new AssemblyScanner(assemblies.SelectMany(z => z.DefinedTypes).Select(x => x.AsType())))
{
services.TryAddEnumerable(ServiceDescriptor.Describe(item.InterfaceType, item.ValidatorType, _options.ValidationLifetime));
services.TryAddEnumerable(ServiceDescriptor.Describe(item.InterfaceType, item.ValidatorType, ServiceLifetime.Singleton));
}

services.TryAdd(ServiceDescriptor.Describe(typeof(IValidatorFactory), typeof(ValidatorFactory), _options.ValidationLifetime));
if (services.FirstOrDefault(z => z.ServiceType == typeof(IValidatorFactory)) is { } s && s.Lifetime != ServiceLifetime.Singleton)
{
services.Remove(s);
}
services.TryAdd(ServiceDescriptor.Describe(typeof(IValidatorFactory), typeof(ValidatorFactory), ServiceLifetime.Singleton));
services.TryAddEnumerable(ServiceDescriptor.Describe(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>), _options.MediatorLifetime));
}
}
Expand Down
4 changes: 0 additions & 4 deletions src/Foundation/FoundationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,5 @@ public class FoundationOptions
/// </summary>
public ServiceLifetime MediatorLifetime { get; set; } = ServiceLifetime.Transient;

/// <summary>
/// The lifetime for validation services
/// </summary>
public ServiceLifetime ValidationLifetime { get; } = ServiceLifetime.Scoped;
}
}
2 changes: 1 addition & 1 deletion src/HotChocolate/Conventions/GraphqlConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void Register(IConventionContext context, IConfiguration configuration, I
ServiceDescriptor.Describe(
typeof(IValidatorProvider),
typeof(FairyBreadValidatorProvider),
services.FirstOrDefault(z => z.ServiceType == typeof(IValidatorFactory))?.Lifetime ?? _foundationOptions.ValidationLifetime
ServiceLifetime.Singleton
)
);
services.TryAddSingleton<IValidationErrorsHandler, DefaultValidationErrorsHandler>();
Expand Down
61 changes: 42 additions & 19 deletions test/AspNetCore.Tests/Validation/UnitTestBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FakeItEasy;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Rocket.Surgery.LaunchPad.AspNetCore.OpenApi.Validation;
Expand All @@ -19,7 +21,8 @@ public SchemaGenerator SchemaGenerator(params IValidator[] validators)

public SchemaGenerator SchemaGenerator(
Action<SchemaGeneratorOptions> configureGenerator = null,
Action<JsonSerializerOptions> configureSerializer = null)
Action<JsonSerializerOptions> configureSerializer = null
)
{
var generatorOptions = new SchemaGeneratorOptions();
configureGenerator?.Invoke(generatorOptions);
Expand All @@ -33,7 +36,12 @@ public SchemaGenerator SchemaGenerator(
private void ConfigureGenerator(SchemaGeneratorOptions options, params IValidator[] validators)
{
IValidatorFactory validatorFactory = new CustomValidatorFactory(validators);
options.SchemaFilters.Add(new FluentValidationRules(validatorFactory));

options.SchemaFilters.Add(
new FluentValidationRules(
validatorFactory
)
);
}
}

Expand All @@ -54,7 +62,8 @@ public SchemaBuilder<T> ConfigureFVSwaggerGenOptions(Action<FluentValidationSwag
public OpenApiSchema AddRule<TProperty>(
Expression<Func<T, TProperty>> propertyExpression,
Action<IRuleBuilderInitial<T, TProperty>>? configureRule = null,
Action<OpenApiSchema>? schemaCheck = null)
Action<OpenApiSchema>? schemaCheck = null
)
{
IRuleBuilderInitial<T, TProperty> ruleBuilder = Validator.RuleFor(propertyExpression);
configureRule?.Invoke(ruleBuilder);
Expand All @@ -72,12 +81,17 @@ public OpenApiSchema AddRule<TProperty>(

public static class TestExtensions
{
public static OpenApiSchema GenerateSchemaForValidator<T>(this SchemaRepository schemaRepository, IValidator<T> validator, FluentValidationSwaggerGenOptions? fluentValidationSwaggerGenOptions = null)
public static OpenApiSchema GenerateSchemaForValidator<T>(
this SchemaRepository schemaRepository,
IValidator<T> validator,
FluentValidationSwaggerGenOptions? fluentValidationSwaggerGenOptions = null
)
{
OpenApiSchema schema = CreateSchemaGenerator(
new []{ validator },
fluentValidationSwaggerGenOptions: fluentValidationSwaggerGenOptions)
.GenerateSchema(typeof(T), schemaRepository);
new[] { validator },
fluentValidationSwaggerGenOptions: fluentValidationSwaggerGenOptions
)
.GenerateSchema(typeof(T), schemaRepository);

if (schema.Reference?.Id != null)
schema = schemaRepository.Schemas[schema.Reference.Id];
Expand All @@ -87,23 +101,32 @@ public static OpenApiSchema GenerateSchemaForValidator<T>(this SchemaRepository

public static SchemaGenerator CreateSchemaGenerator(
IValidator[] validators,
FluentValidationSwaggerGenOptions? fluentValidationSwaggerGenOptions = null)
FluentValidationSwaggerGenOptions? fluentValidationSwaggerGenOptions = null
)
{
return CreateSchemaGenerator(options =>
{
IValidatorFactory validatorFactory = new CustomValidatorFactory(validators);

options.SchemaFilters.Add(new FluentValidationRules(
validatorFactory: validatorFactory,
rules: null,
loggerFactory: null,
options: fluentValidationSwaggerGenOptions != null ? new OptionsWrapper<FluentValidationSwaggerGenOptions>(fluentValidationSwaggerGenOptions) : null));
});
return CreateSchemaGenerator(
options =>
{
IValidatorFactory validatorFactory = new CustomValidatorFactory(validators);

options.SchemaFilters.Add(
new FluentValidationRules(
validatorFactory,
rules: null,
loggerFactory: null,
options: fluentValidationSwaggerGenOptions != null
? new OptionsWrapper<FluentValidationSwaggerGenOptions>(fluentValidationSwaggerGenOptions)
: null
)
);
}
);
}

public static SchemaGenerator CreateSchemaGenerator(
Action<SchemaGeneratorOptions>? configureGenerator = null,
Action<JsonSerializerOptions>? configureSerializer = null)
Action<JsonSerializerOptions>? configureSerializer = null
)
{
var generatorOptions = new SchemaGeneratorOptions();
configureGenerator?.Invoke(generatorOptions);
Expand Down

0 comments on commit 34316c8

Please sign in to comment.