diff --git a/readme.md b/readme.md index 4cfaff4a..5e30c798 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,35 @@ public class MyInputValidator : -### Setup Validators +### Setup Validation with the GraphQL Builder + +If you are using the GraphQL builder to configure your app and you want to use dependency injection, you can use the builder to configure FluentValidation. You must first call one of the `AddValidatorsFrom*` methods from +[FluentValidation.DependencyInjectionExtensions](https://www.nuget.org/packages/FluentValidation.DependencyInjectionExtensions/) to register your validators with the service collection. Then call the `UseFluentValidation()` extension method when configuring GraphQL. + + +```cs +var builder = WebApplication.CreateBuilder(args); + +var validatorAssembly = /* Get assembly containing validators */; +builder.Services.AddValidatorsFromAssembly(validatorAssembly); + +builder.Services.AddGraphQL( + b => b.AddSchema() + .UseFluentValidation() + // Other GraphQL configuration options... +); + +// Other DI and Asp.Net setup... + +``` + +Note: If you are using a `Startup` class instead of top-level statements, the above configuration will go in your `Startup.ConfigureServices()` method. + +### Setup Validation Manually + +If you aren't using the GraphQL builder extensions to configure your project, you can manually add the pieces needed to support FluentValidation. + +#### Build the Validator Cache Validators need to be added to the `ValidatorTypeCache`. This should be done once at application startup. @@ -79,13 +107,8 @@ var executer = new DocumentExecuter(); Generally `ValidatorTypeCache` is scoped per app and can be collocated with `Schema`, `DocumentExecuter` initialization. -Dependency Injection can be used for validators. Create a `ValidatorTypeCache` with the -`useDependencyInjection: true` parameter and call one of the `AddValidatorsFrom*` methods from -[FluentValidation.DependencyInjectionExtensions](https://www.nuget.org/packages/FluentValidation.DependencyInjectionExtensions/) -package in the `Startup`. By default, validators are added to the DI container with a transient lifetime. - -### Add to ExecutionOptions +#### Add to ExecutionOptions Validation needs to be added to any instance of `ExecutionOptions`. diff --git a/src/GraphQL.FluentValidation/FluentValidationExtensions.cs b/src/GraphQL.FluentValidation/FluentValidationExtensions.cs index 2af48f90..56fae1f9 100644 --- a/src/GraphQL.FluentValidation/FluentValidationExtensions.cs +++ b/src/GraphQL.FluentValidation/FluentValidationExtensions.cs @@ -1,4 +1,5 @@ using FluentValidation; +using GraphQL.DI; using GraphQL.FluentValidation; using GraphQL.Instrumentation; using GraphQL.Types; @@ -28,4 +29,63 @@ public static void UseFluentValidation(this ISchema schema) ValidationMiddleware validationMiddleware = new(); schema.FieldMiddleware.Use(validationMiddleware); } + + /// + /// Configures GraphQL to use FluentValidation, using a custom . + /// + /// + /// The GraphQL builder. + /// + /// + /// The cache used to resolve validator types. + /// + /// + /// The instance; + /// + /// + /// is null. + /// -or- + /// is null. + /// + public static IGraphQLBuilder UseFluentValidation(this IGraphQLBuilder builder, IValidatorCache validatorCache) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (validatorCache is null) + { + throw new ArgumentNullException(nameof(validatorCache)); + } + + builder.UseMiddleware(); + + validatorCache.Freeze(); + builder.ConfigureExecutionOptions(eo => eo.SetCache(validatorCache)); + + return builder; + } + + /// + /// Configures GraphQL to use FluentValidation, with validators resolved using dependency injection. + /// + /// + /// The GraphQL builder. + /// + /// + /// The instance; + /// + /// + /// is null. + /// + public static IGraphQLBuilder UseFluentValidation(this IGraphQLBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.UseFluentValidation(new ValidatorServiceProviderCache()); + } } \ No newline at end of file diff --git a/src/GraphQL.FluentValidation/ValidatorServiceProviderCache.cs b/src/GraphQL.FluentValidation/ValidatorServiceProviderCache.cs new file mode 100644 index 00000000..81e726cd --- /dev/null +++ b/src/GraphQL.FluentValidation/ValidatorServiceProviderCache.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; + +namespace GraphQL.FluentValidation; + +/// +/// Uses the to determine what validators are available and resolve validator instances. +/// +sealed class ValidatorServiceProviderCache : IValidatorCache +{ + /// + public bool IsFrozen => true; + + /// + public void Freeze() + { + // Intentionally empty. + } + + /// + public bool TryGetValidators(Type argumentType, IServiceProvider? provider, [NotNullWhen(true)] out IEnumerable? validators) + { + var validatorType = typeof(IValidator<>).MakeGenericType(argumentType); + try + { + validators = provider!.GetServices(validatorType).Cast(); + return true; + } + catch (InvalidOperationException) + { + validators = null; + return false; + } + } + + /// + public void AddResult(AssemblyScanner.AssemblyScanResult result) => + throw new InvalidOperationException("Method not supported. The service provider cache uses IServiceProvider to determine what validators are available."); +}