Skip to content
Open
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
37 changes: 30 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,35 @@ public class MyInputValidator :
<!-- endSnippet -->


### 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<YourSchemaType>()
.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.

Expand All @@ -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`.

Expand Down
60 changes: 60 additions & 0 deletions src/GraphQL.FluentValidation/FluentValidationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentValidation;
using GraphQL.DI;
using GraphQL.FluentValidation;
using GraphQL.Instrumentation;
using GraphQL.Types;
Expand Down Expand Up @@ -28,4 +29,63 @@ public static void UseFluentValidation(this ISchema schema)
ValidationMiddleware validationMiddleware = new();
schema.FieldMiddleware.Use(validationMiddleware);
}

/// <summary>
/// Configures GraphQL to use FluentValidation, using a custom <see cref="IValidatorCache"/>.
/// </summary>
/// <param name="builder">
/// The GraphQL builder.
/// </param>
/// <param name="validatorCache">
/// The cache used to resolve validator types.
/// </param>
/// <returns>
/// The <paramref name="builder"/> instance;
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/> is null.
/// -or-
/// <paramref name="validatorCache"/> is null.
/// </exception>
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<ValidationMiddleware>();

validatorCache.Freeze();
builder.ConfigureExecutionOptions(eo => eo.SetCache(validatorCache));

return builder;
}

/// <summary>
/// Configures GraphQL to use FluentValidation, with validators resolved using dependency injection.
/// </summary>
/// <param name="builder">
/// The GraphQL builder.
/// </param>
/// <returns>
/// The <paramref name="builder"/> instance;
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/> is null.
/// </exception>
public static IGraphQLBuilder UseFluentValidation(this IGraphQLBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.UseFluentValidation(new ValidatorServiceProviderCache());
}
}
40 changes: 40 additions & 0 deletions src/GraphQL.FluentValidation/ValidatorServiceProviderCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Diagnostics.CodeAnalysis;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;

namespace GraphQL.FluentValidation;

/// <summary>
/// Uses the <see cref="IServiceProvider"/> to determine what validators are available and resolve validator instances.
/// </summary>
sealed class ValidatorServiceProviderCache : IValidatorCache
{
/// <inheritdoc />
public bool IsFrozen => true;

/// <inheritdoc />
public void Freeze()
{
// Intentionally empty.
}

/// <inheritdoc />
public bool TryGetValidators(Type argumentType, IServiceProvider? provider, [NotNullWhen(true)] out IEnumerable<IValidator>? validators)
{
var validatorType = typeof(IValidator<>).MakeGenericType(argumentType);
try
{
validators = provider!.GetServices(validatorType).Cast<IValidator>();
return true;
}
catch (InvalidOperationException)
{
validators = null;
return false;
}
}

/// <inheritdoc />
public void AddResult(AssemblyScanner.AssemblyScanResult result) =>
throw new InvalidOperationException("Method not supported. The service provider cache uses IServiceProvider to determine what validators are available.");
}