graphql fluent validation implementation with middleware.
This commit is contained in:
parent
afb8b534bb
commit
ffcfc60df1
@ -18,6 +18,7 @@
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery.Abstractions\PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery\PoweredSoft.CQRS.DynamicQuery.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.GraphQL.FluentValidation\PoweredSoft.CQRS.GraphQL.FluentValidation.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.GraphQL.HotChocolate\PoweredSoft.CQRS.GraphQL.HotChocolate.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS\PoweredSoft.CQRS.csproj" />
|
||||
</ItemGroup>
|
||||
|
@ -19,6 +19,7 @@ using PoweredSoft.CQRS.AspNetCore.Mvc;
|
||||
using PoweredSoft.CQRS.DynamicQuery;
|
||||
using PoweredSoft.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.CQRS.DynamicQuery.AspNetCore;
|
||||
using PoweredSoft.CQRS.GraphQL.FluentValidation;
|
||||
using PoweredSoft.CQRS.GraphQL.HotChocolate;
|
||||
using PoweredSoft.Data;
|
||||
using PoweredSoft.Data.Core;
|
||||
@ -50,7 +51,9 @@ namespace Demo
|
||||
services.AddPoweredSoftDataServices();
|
||||
services.AddPoweredSoftDynamicQuery();
|
||||
|
||||
services.AddPoweredSoftCQRS();
|
||||
services
|
||||
.AddPoweredSoftCQRS();
|
||||
|
||||
services
|
||||
.AddControllers()
|
||||
.AddPoweredSoftQueries()
|
||||
@ -65,8 +68,9 @@ namespace Demo
|
||||
.AddMutationType(d => d.Name("Mutation"))
|
||||
.AddPoweredSoftMutations();
|
||||
|
||||
services.AddPoweredSoftGraphQLFluentValidation();
|
||||
|
||||
//services.AddSwaggerGen();
|
||||
services.AddSwaggerGen();
|
||||
}
|
||||
|
||||
private void AddDynamicQueries(IServiceCollection services)
|
||||
|
10
PoweredSoft.CQRS.GraphQL.Abstractions/IGraphQLFieldError.cs
Normal file
10
PoweredSoft.CQRS.GraphQL.Abstractions/IGraphQLFieldError.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.Abstractions
|
||||
{
|
||||
public interface IGraphQLFieldError
|
||||
{
|
||||
string Field { get; set; }
|
||||
List<string> Errors { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.Abstractions
|
||||
{
|
||||
public interface IGraphQLValidationResult
|
||||
{
|
||||
bool IsValid { get; }
|
||||
|
||||
List<IGraphQLFieldError> Errors { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.Abstractions
|
||||
{
|
||||
|
||||
public interface IGraphQLValidationService
|
||||
{
|
||||
Task<IGraphQLValidationResult> ValidateObjectAsync(object subject, CancellationToken cancellationToken = default);
|
||||
Task<IGraphQLValidationResult> ValidateAsync<T>(T subject, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,11 @@
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.FluentValidation
|
||||
{
|
||||
public class GraphQLFieldError : IGraphQLFieldError
|
||||
{
|
||||
public string Field { get; set; }
|
||||
public List<string> Errors { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using FluentValidation.Results;
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.FluentValidation
|
||||
{
|
||||
public class GraphQLFluentValidationResult : IGraphQLValidationResult
|
||||
{
|
||||
public bool IsValid => Errors.Count == 0;
|
||||
public List<IGraphQLFieldError> Errors { get; } = new List<IGraphQLFieldError>();
|
||||
|
||||
public static GraphQLFluentValidationResult From(ValidationResult result)
|
||||
{
|
||||
var model = new GraphQLFluentValidationResult();
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
var fieldError = new GraphQLFieldError
|
||||
{
|
||||
Field = error.PropertyName
|
||||
};
|
||||
fieldError.Errors.Add(error.ErrorMessage);
|
||||
model.Errors.Add(fieldError);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using FluentValidation;
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.FluentValidation
|
||||
{
|
||||
public class GraphQLFluentValidationService : IGraphQLValidationService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public GraphQLFluentValidationService(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task<IGraphQLValidationResult> ValidateAsync<T>(T subject, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var validationService = serviceProvider.GetService(typeof(IValidator<T>)) as IValidator<T>;
|
||||
if (validationService == null)
|
||||
return new GraphQLValidResult();
|
||||
|
||||
var result = await validationService.ValidateAsync(subject, cancellationToken);
|
||||
if (!result.IsValid)
|
||||
return GraphQLFluentValidationResult.From(result);
|
||||
|
||||
return new GraphQLValidResult();
|
||||
}
|
||||
|
||||
public async Task<IGraphQLValidationResult> ValidateObjectAsync(object subject, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var validatorType = typeof(IValidator<>).MakeGenericType(subject.GetType());
|
||||
var validationService = serviceProvider.GetService(validatorType) as IValidator;
|
||||
if (validationService == null)
|
||||
return new GraphQLValidResult();
|
||||
|
||||
var result = await validationService.ValidateAsync(new ValidationContext<object>(subject), cancellationToken);
|
||||
if (!result.IsValid)
|
||||
return GraphQLFluentValidationResult.From(result);
|
||||
|
||||
return new GraphQLValidResult();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.FluentValidation
|
||||
{
|
||||
public class GraphQLValidResult : IGraphQLValidationResult
|
||||
{
|
||||
public bool IsValid => true;
|
||||
public List<IGraphQLFieldError> Errors { get; } = new List<IGraphQLFieldError>();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="9.5.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.GraphQL.Abstractions\PoweredSoft.CQRS.GraphQL.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.FluentValidation
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddPoweredSoftGraphQLFluentValidation(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IGraphQLValidationService, GraphQLFluentValidationService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
desc.Name("Mutation");
|
||||
foreach (var m in commandDiscovery.GetCommands())
|
||||
{
|
||||
var queryField = desc.Field(m.LowerCamelCaseName);
|
||||
var mutationField = desc.Field(m.LowerCamelCaseName);
|
||||
|
||||
Type typeToGet;
|
||||
if (m.CommandResultType == null)
|
||||
@ -30,15 +30,15 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
typeToGet = typeof(ICommandHandler<,>).MakeGenericType(m.CommandType, m.CommandResultType);
|
||||
|
||||
if (m.CommandResultType == null)
|
||||
queryField.Type(typeof(int?));
|
||||
mutationField.Type(typeof(int?));
|
||||
else
|
||||
queryField.Type(m.CommandResultType);
|
||||
mutationField.Type(m.CommandResultType);
|
||||
|
||||
//queryField.Use((sp, d) => new MutationAuthorizationMiddleware(m.CommandType, d));
|
||||
|
||||
if (m.CommandType.GetProperties().Length == 0)
|
||||
{
|
||||
queryField.Resolve(async ctx =>
|
||||
mutationField.Resolve(async ctx =>
|
||||
{
|
||||
var queryArgument = Activator.CreateInstance(m.CommandType);
|
||||
return await HandleMutation(m.CommandResultType != null, ctx, typeToGet, queryArgument);
|
||||
@ -47,21 +47,16 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
continue;
|
||||
}
|
||||
|
||||
queryField.Argument("params", t => t.Type(m.CommandType));
|
||||
mutationField.Argument("params", t => t.Type(m.CommandType));
|
||||
|
||||
queryField.Resolve(async ctx =>
|
||||
mutationField.Resolve(async ctx =>
|
||||
{
|
||||
var queryArgument = ctx.ArgumentValue<object>("params");
|
||||
return await HandleMutation(m.CommandResultType != null, ctx, typeToGet, queryArgument);
|
||||
});
|
||||
|
||||
// TODO.
|
||||
//if (m.MutationObjectRequired)
|
||||
// queryField.Use<MutationParamRequiredMiddleware>();
|
||||
|
||||
// TODO.
|
||||
//if (m.ValidateMutationObject)
|
||||
// queryField.Use<MutationValidationMiddleware>();
|
||||
mutationField.Use<MutationParamRequiredMiddleware>();
|
||||
mutationField.Use<MutationValidationMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
using HotChocolate;
|
||||
using HotChocolate.Resolvers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
{
|
||||
public class MutationParamRequiredMiddleware
|
||||
{
|
||||
private readonly FieldDelegate _next;
|
||||
|
||||
public MutationParamRequiredMiddleware(FieldDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(IMiddlewareContext context)
|
||||
{
|
||||
var queryArgument = context.ArgumentValue<object>("params");
|
||||
if (queryArgument == null)
|
||||
{
|
||||
context.Result = ErrorBuilder.New()
|
||||
.SetMessage("mutation argument is required")
|
||||
.SetCode("400")
|
||||
.SetPath(context.Path)
|
||||
.AddLocation(context.Selection.SyntaxNode)
|
||||
.Build();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
using HotChocolate;
|
||||
using HotChocolate.Resolvers;
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
{
|
||||
public class MutationValidationMiddleware
|
||||
{
|
||||
private readonly FieldDelegate _next;
|
||||
|
||||
public MutationValidationMiddleware(FieldDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(IMiddlewareContext context)
|
||||
{
|
||||
var queryArgument = context.ArgumentValue<object>("params");
|
||||
if (queryArgument != null)
|
||||
{
|
||||
var service = context.Service<IGraphQLValidationService>();
|
||||
var result = await service.ValidateObjectAsync(queryArgument, context.RequestAborted);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
var eb = ErrorBuilder.New()
|
||||
.SetMessage("There are some validations errors")
|
||||
.SetCode("ValidationError")
|
||||
.SetPath(context.Path)
|
||||
.AddLocation(context.Selection.SyntaxNode);
|
||||
|
||||
foreach (var error in result.Errors)
|
||||
eb.SetExtension(error.Field, error.Errors);
|
||||
|
||||
context.Result = eb.Build();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.GraphQL.Abstractions\PoweredSoft.CQRS.GraphQL.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -57,10 +57,7 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
if (q.QueryObjectRequired)
|
||||
queryField.Use<QueryParamRequiredMiddleware>();*/
|
||||
|
||||
/* TODO
|
||||
if (q.ValidateQueryObject)
|
||||
queryField.Use<QueryValidationMiddleware>();
|
||||
*/
|
||||
queryField.Use<QueryValidationMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using HotChocolate;
|
||||
using HotChocolate.Resolvers;
|
||||
using Newtonsoft.Json;
|
||||
using PoweredSoft.CQRS.GraphQL.Abstractions;
|
||||
|
||||
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||
{
|
||||
public class QueryValidationMiddleware
|
||||
{
|
||||
private readonly FieldDelegate _next;
|
||||
|
||||
public QueryValidationMiddleware(FieldDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(IMiddlewareContext context)
|
||||
{
|
||||
var queryArgument = context.ArgumentValue<object>("params");
|
||||
if (queryArgument != null)
|
||||
{
|
||||
var service = context.Service<IGraphQLValidationService>();
|
||||
var result = await service.ValidateObjectAsync(queryArgument, context.RequestAborted);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
var eb = ErrorBuilder.New()
|
||||
.SetMessage("There are some validations errors")
|
||||
.SetCode("ValidationError")
|
||||
.SetPath(context.Path)
|
||||
.AddLocation(context.Selection.SyntaxNode);
|
||||
|
||||
foreach (var error in result.Errors)
|
||||
eb.SetExtension(error.Field, error.Errors);
|
||||
|
||||
context.Result = eb.Build();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQue
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.GraphQL.HotChocolate", "PoweredSoft.CQRS.GraphQL.HotChocolate\PoweredSoft.CQRS.GraphQL.HotChocolate.csproj", "{BF8E3B0D-8651-4541-892F-F607C5E80F9B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.GraphQL.Abstractions", "PoweredSoft.CQRS.GraphQL.Abstractions\PoweredSoft.CQRS.GraphQL.Abstractions.csproj", "{C18DD3EB-56A8-4576-BB31-04AE724E6E25}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.GraphQL.FluentValidation", "PoweredSoft.CQRS.GraphQL.FluentValidation\PoweredSoft.CQRS.GraphQL.FluentValidation.csproj", "{BB134663-BAB0-45C4-A6E0-34F296FCA7AE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -69,6 +73,14 @@ Global
|
||||
{BF8E3B0D-8651-4541-892F-F607C5E80F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF8E3B0D-8651-4541-892F-F607C5E80F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF8E3B0D-8651-4541-892F-F607C5E80F9B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C18DD3EB-56A8-4576-BB31-04AE724E6E25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C18DD3EB-56A8-4576-BB31-04AE724E6E25}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C18DD3EB-56A8-4576-BB31-04AE724E6E25}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C18DD3EB-56A8-4576-BB31-04AE724E6E25}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB134663-BAB0-45C4-A6E0-34F296FCA7AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB134663-BAB0-45C4-A6E0-34F296FCA7AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB134663-BAB0-45C4-A6E0-34F296FCA7AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB134663-BAB0-45C4-A6E0-34F296FCA7AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
Loading…
Reference in New Issue
Block a user