https://github.com/simoncropp/graphql.validation
Add FluentValidation support to GraphQL.net
https://github.com/simoncropp/graphql.validation
Last synced: about 1 year ago
JSON representation
Add FluentValidation support to GraphQL.net
- Host: GitHub
- URL: https://github.com/simoncropp/graphql.validation
- Owner: SimonCropp
- License: mit
- Created: 2019-01-13T02:00:50.000Z (over 7 years ago)
- Default Branch: main
- Last Pushed: 2025-04-03T05:39:04.000Z (about 1 year ago)
- Last Synced: 2025-04-03T06:32:12.970Z (about 1 year ago)
- Language: C#
- Homepage:
- Size: 1.54 MB
- Stars: 45
- Watchers: 4
- Forks: 6
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- Funding: .github/FUNDING.yml
- License: license.txt
- Code of conduct: code_of_conduct.md
Awesome Lists containing this project
README
#
GraphQL.Validation
[](https://ci.appveyor.com/project/SimonCropp/graphql-validation)
[](https://www.nuget.org/packages/GraphQL.FluentValidation/)
Add [FluentValidation](https://fluentvalidation.net/) support to [GraphQL.net](https://github.com/graphql-dotnet/graphql-dotnet)
**See [Milestones](../../milestones?state=closed) for release notes.**
### Powered by
[](https://jb.gg/OpenSourceSupport)
## NuGet package
https://nuget.org/packages/GraphQL.FluentValidation/
## Usage
### Define validators
Given the following input:
```cs
public class MyInput
{
public string Content { get; set; } = null!;
}
```
snippet source | anchor
And graph:
```cs
public class MyInputGraph :
InputObjectGraphType
{
public MyInputGraph() =>
Field("content");
}
```
snippet source | anchor
A custom validator can be defined as follows:
```cs
public class MyInputValidator :
AbstractValidator
{
public MyInputValidator() =>
RuleFor(_ => _.Content)
.NotEmpty();
}
```
snippet source | anchor
### Setup Validators
Validators need to be added to the `ValidatorTypeCache`. This should be done once at application startup.
```cs
var validatorCache = new ValidatorInstanceCache();
validatorCache.AddValidatorsFromAssembly(assemblyContainingValidators);
var schema = new Schema();
schema.UseFluentValidation();
var executer = new DocumentExecuter();
```
snippet source | anchor
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
Validation needs to be added to any instance of `ExecutionOptions`.
```cs
var options = new ExecutionOptions
{
Schema = schema,
Query = queryString,
Variables = inputs
};
options.UseFluentValidation(validatorCache);
var executionResult = await executer.ExecuteAsync(options);
```
snippet source | anchor
### UserContext must be a dictionary
This library needs to be able to pass the list of validators, in the form of `ValidatorTypeCache`, through the graphql context. The only way of achieving this is to use the `ExecutionOptions.UserContext`. To facilitate this, the type passed to `ExecutionOptions.UserContext` has to implement `IDictionary`. There are two approaches to achieving this:
#### 1. Have the user context class implement IDictionary
Given a user context class of the following form:
```cs
public class MyUserContext(string myProperty) :
Dictionary
{
public string MyProperty { get; } = myProperty;
}
```
snippet source | anchor
The `ExecutionOptions.UserContext` can then be set as follows:
```cs
var options = new ExecutionOptions
{
Schema = schema,
Query = queryString,
Variables = inputs,
UserContext = new MyUserContext
(
myProperty: "the value"
)
};
options.UseFluentValidation(validatorCache);
```
snippet source | anchor
#### 2. Have the user context class exist inside a IDictionary
```cs
var options = new ExecutionOptions
{
Schema = schema,
Query = queryString,
Variables = inputs,
UserContext = new Dictionary
{
{
"MyUserContext",
new MyUserContext
(
myProperty: "the value"
)
}
}
};
options.UseFluentValidation(validatorCache);
```
snippet source | anchor
#### No UserContext
If no instance is passed to `ExecutionOptions.UserContext`:
```cs
var options = new ExecutionOptions
{
Schema = schema,
Query = queryString,
Variables = inputs
};
options.UseFluentValidation(validatorCache);
```
snippet source | anchor
Then the `UseFluentValidation` method will instantiate it to a new `Dictionary`.
### Trigger validation
To trigger the validation, when reading arguments use `GetValidatedArgument` instead of `GetArgument`:
```cs
public class Query :
ObjectGraphType
{
public Query() =>
Field("inputQuery")
.Argument("input")
.Resolve(context =>
{
var input = context.GetValidatedArgument("input");
return new Result
{
Data = input.Content
};
}
);
}
```
snippet source | anchor
### Difference from IValidationRule
The validation implemented in this project has nothing to do with the validation of the incoming GraphQL
request, which is described in the [official specification](http://spec.graphql.org/June2018/#sec-Validation).
[GraphQL.NET](https://github.com/graphql-dotnet/graphql-dotnet) has a concept of [validation rules](https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL/Validation/IValidationRule.cs)
that would work **before** request execution stage. In this project validation occurs for input arguments
**at the request execution stage**. This additional validation complements but does not replace the standard
set of validation rules.
## Testing
### Integration
A full end-to-en test can be run against the GraphQL controller:
```cs
public class GraphQLControllerTests
{
[Fact]
public async Task RunQuery()
{
using var server = GetTestServer();
using var client = server.CreateClient();
var query = """
{
inputQuery(input: {content: "TheContent"}) {
data
}
}
""";
var body = new
{
query
};
var serialized = JsonConvert.SerializeObject(body);
using var content = new StringContent(
serialized,
Encoding.UTF8,
"application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "graphql")
{
Content = content
};
using var response = await client.SendAsync(request);
await Verify(response);
}
static TestServer GetTestServer()
{
var builder = new WebHostBuilder();
builder.UseStartup();
return new(builder);
}
}
```
snippet source | anchor
### Unit
Unit tests can be run a specific field of a query:
```cs
public class QueryTests
{
[Fact]
public async Task RunInputQuery()
{
var field = new Query().GetField("inputQuery")!;
var userContext = new GraphQLUserContext();
FluentValidationExtensions.AddCacheToContext(
userContext,
ValidatorCacheBuilder.Instance);
var input = new MyInput
{
Content = "TheContent"
};
var fieldContext = new ResolveFieldContext
{
Arguments = new Dictionary
{
{
"input", new(input, ArgumentSource.Variable)
}
},
UserContext = userContext
};
var result = await field.Resolver!.ResolveAsync(fieldContext);
await Verify(result);
}
[Fact]
public Task RunInvalidInputQuery()
{
Thread.CurrentThread.CurrentUICulture = new("en-US");
var field = new Query().GetField("inputQuery")!;
var userContext = new GraphQLUserContext();
FluentValidationExtensions.AddCacheToContext(
userContext,
ValidatorCacheBuilder.Instance);
var input = new MyInput
{
Content = null!
};
var fieldContext = new ResolveFieldContext
{
Arguments = new Dictionary
{
{
"input", new(input, ArgumentSource.Variable)
}
},
UserContext = userContext
};
var exception = Assert.Throws(
() => field.Resolver!.ResolveAsync(fieldContext));
return Verify(exception.Message);
}
}
```
snippet source | anchor
## Icon
[Shield](https://thenounproject.com/term/shield/1893182/) designed by [Maxim Kulikov](https://thenounproject.com/maxim221/) from [The Noun Project](https://thenounproject.com)