basic query and mutation discovery, next dynamic queries get added through extensions :)

This commit is contained in:
David Lebee 2021-02-03 19:51:23 -05:00
parent bd1b948a19
commit afb8b534bb
14 changed files with 258 additions and 10 deletions

View File

@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="9.5.0" /> <PackageReference Include="FluentValidation.AspNetCore" Version="9.5.0" />
<PackageReference Include="HotChocolate.AspNetCore" Version="11.0.9" />
<PackageReference Include="PoweredSoft.Data" Version="2.0.0" /> <PackageReference Include="PoweredSoft.Data" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.6.3" /> <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" />
@ -17,6 +18,7 @@
<ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery.Abstractions\PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj" /> <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.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj" />
<ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery\PoweredSoft.CQRS.DynamicQuery.csproj" /> <ProjectReference Include="..\PoweredSoft.CQRS.DynamicQuery\PoweredSoft.CQRS.DynamicQuery.csproj" />
<ProjectReference Include="..\PoweredSoft.CQRS.GraphQL.HotChocolate\PoweredSoft.CQRS.GraphQL.HotChocolate.csproj" />
<ProjectReference Include="..\PoweredSoft.CQRS\PoweredSoft.CQRS.csproj" /> <ProjectReference Include="..\PoweredSoft.CQRS\PoweredSoft.CQRS.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -4,6 +4,7 @@ using Demo.DynamicQueries;
using Demo.Queries; using Demo.Queries;
using FluentValidation; using FluentValidation;
using FluentValidation.AspNetCore; using FluentValidation.AspNetCore;
using HotChocolate.Types;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.HttpsPolicy;
@ -18,6 +19,7 @@ using PoweredSoft.CQRS.AspNetCore.Mvc;
using PoweredSoft.CQRS.DynamicQuery; using PoweredSoft.CQRS.DynamicQuery;
using PoweredSoft.CQRS.DynamicQuery.Abstractions; using PoweredSoft.CQRS.DynamicQuery.Abstractions;
using PoweredSoft.CQRS.DynamicQuery.AspNetCore; using PoweredSoft.CQRS.DynamicQuery.AspNetCore;
using PoweredSoft.CQRS.GraphQL.HotChocolate;
using PoweredSoft.Data; using PoweredSoft.Data;
using PoweredSoft.Data.Core; using PoweredSoft.Data.Core;
using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery;
@ -56,7 +58,15 @@ namespace Demo
.AddPoweredSoftDynamicQueries() .AddPoweredSoftDynamicQueries()
.AddFluentValidation(); .AddFluentValidation();
services.AddSwaggerGen(); services
.AddGraphQLServer()
.AddQueryType(d => d.Name("Query"))
.AddPoweredSoftQueries()
.AddMutationType(d => d.Name("Mutation"))
.AddPoweredSoftMutations();
//services.AddSwaggerGen();
} }
private void AddDynamicQueries(IServiceCollection services) private void AddDynamicQueries(IServiceCollection services)
@ -100,18 +110,19 @@ namespace Demo
app.UseAuthorization(); app.UseAuthorization();
app.UseSwagger(); //app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint. // specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c => //app.UseSwaggerUI(c =>
{ //{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); // c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
}); //});
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapGraphQL();
}); });
} }
} }

View File

@ -33,5 +33,19 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery
public virtual Type CommandType { get; } public virtual Type CommandType { get; }
public virtual Type ServiceType { get; } public virtual Type ServiceType { get; }
public virtual Type CommandResultType { get; } public virtual Type CommandResultType { get; }
public string LowerCamelCaseName
{
get
{
if (string.IsNullOrEmpty(Name))
return Name;
var name = Name;
var firstLetter = Char.ToLowerInvariant(name[0]);
var ret = $"{firstLetter}{name.Substring(1)}";
return ret;
}
}
} }
} }

View File

@ -8,5 +8,6 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery
Type CommandType { get; } Type CommandType { get; }
Type ServiceType { get; } Type ServiceType { get; }
Type CommandResultType { get; } Type CommandResultType { get; }
string LowerCamelCaseName { get; }
} }
} }

View File

@ -12,5 +12,6 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery
Type ServiceType { get; } Type ServiceType { get; }
Type QueryResultType { get; } Type QueryResultType { get; }
string Category { get; } string Category { get; }
string LowerCamelCaseName { get; }
} }
} }

View File

@ -28,5 +28,19 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery
public virtual Type ServiceType { get; } public virtual Type ServiceType { get; }
public virtual Type QueryResultType { get; } public virtual Type QueryResultType { get; }
public virtual string Category => "BasicQuery"; public virtual string Category => "BasicQuery";
public string LowerCamelCaseName
{
get
{
if (string.IsNullOrEmpty(Name))
return Name;
var name = Name;
var firstLetter = Char.ToLowerInvariant(name[0]);
var ret = $"{firstLetter}{name.Substring(1)}";
return ret;
}
}
} }
} }

View File

@ -21,7 +21,7 @@ namespace PoweredSoft.CQRS.AspNetCore.Mvc
var genericType = controller.ControllerType.GenericTypeArguments[0]; var genericType = controller.ControllerType.GenericTypeArguments[0];
var commandDiscovery = this.serviceProvider.GetRequiredService<ICommandDiscovery>(); var commandDiscovery = this.serviceProvider.GetRequiredService<ICommandDiscovery>();
var command = commandDiscovery.FindCommand(genericType); var command = commandDiscovery.FindCommand(genericType);
controller.ControllerName = command.Name; controller.ControllerName = command.LowerCamelCaseName;
} }
} }
} }

View File

@ -21,7 +21,7 @@ namespace PoweredSoft.CQRS.AspNetCore.Mvc
var genericType = controller.ControllerType.GenericTypeArguments[0]; var genericType = controller.ControllerType.GenericTypeArguments[0];
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>(); var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
var query = queryDiscovery.FindQuery(genericType); var query = queryDiscovery.FindQuery(genericType);
controller.ControllerName = query.Name; controller.ControllerName = query.LowerCamelCaseName;
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc
var genericType = controller.ControllerType.GenericTypeArguments[0]; var genericType = controller.ControllerType.GenericTypeArguments[0];
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>(); var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
var query = queryDiscovery.FindQuery(genericType); var query = queryDiscovery.FindQuery(genericType);
controller.ControllerName = query.Name; controller.ControllerName = query.LowerCamelCaseName;
} }
} }
} }

View File

@ -0,0 +1,84 @@
using HotChocolate.Resolvers;
using HotChocolate.Types;
using PoweredSoft.CQRS.Abstractions;
using PoweredSoft.CQRS.Abstractions.Discovery;
using System;
using System.Collections.Generic;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
{
public class MutationObjectType : ObjectTypeExtension
{
private readonly ICommandDiscovery commandDiscovery;
public MutationObjectType(ICommandDiscovery commandDiscovery) : base()
{
this.commandDiscovery = commandDiscovery;
}
protected override void Configure(IObjectTypeDescriptor desc)
{
desc.Name("Mutation");
foreach (var m in commandDiscovery.GetCommands())
{
var queryField = desc.Field(m.LowerCamelCaseName);
Type typeToGet;
if (m.CommandResultType == null)
typeToGet = typeof(ICommandHandler<>).MakeGenericType(m.CommandType);
else
typeToGet = typeof(ICommandHandler<,>).MakeGenericType(m.CommandType, m.CommandResultType);
if (m.CommandResultType == null)
queryField.Type(typeof(int?));
else
queryField.Type(m.CommandResultType);
//queryField.Use((sp, d) => new MutationAuthorizationMiddleware(m.CommandType, d));
if (m.CommandType.GetProperties().Length == 0)
{
queryField.Resolve(async ctx =>
{
var queryArgument = Activator.CreateInstance(m.CommandType);
return await HandleMutation(m.CommandResultType != null, ctx, typeToGet, queryArgument);
});
continue;
}
queryField.Argument("params", t => t.Type(m.CommandType));
queryField.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>();
}
}
private async System.Threading.Tasks.Task<object> HandleMutation(bool hasResult, IResolverContext ctx, Type typeToGet, object queryArgument)
{
dynamic service = ctx.Service(typeToGet);
if (hasResult)
{
var result = await service.HandleAsync((dynamic)queryArgument);
return result;
}
else
{
await service.HandleAsync((dynamic)queryArgument);
return null;
}
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HotChocolate" Version="11.0.9" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,74 @@
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using PoweredSoft.CQRS.Abstractions;
using PoweredSoft.CQRS.Abstractions.Discovery;
using System;
using System.Text;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
{
public class QueryObjectType : ObjectTypeExtension
{
private readonly IQueryDiscovery queryDiscovery;
public QueryObjectType(IQueryDiscovery queryDiscovery) : base()
{
this.queryDiscovery = queryDiscovery;
}
protected override void Configure(IObjectTypeDescriptor desc)
{
desc.Name("Query");
foreach (var q in queryDiscovery.GetQueries())
{
if (q.Category != "BasicQuery")
return;
var queryField = desc.Field(q.LowerCamelCaseName);
var typeToGet = typeof(IQueryHandler<,>).MakeGenericType(q.QueryType, q.QueryResultType);
queryField.Type(q.QueryResultType);
// TODO.
// always required.
//queryField.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
if (q.QueryType.GetProperties().Length == 0)
{
queryField.Resolve(async ctx =>
{
var queryArgument = Activator.CreateInstance(q.QueryType);
return await HandleQuery(ctx, typeToGet, queryArgument);
});
continue;
}
queryField.Argument("params", t => t.Type(q.QueryType));
queryField.Resolve(async ctx =>
{
var queryArgument = ctx.ArgumentValue<object>("params");
return await HandleQuery(ctx, typeToGet, queryArgument);
});
/*
if (q.QueryObjectRequired)
queryField.Use<QueryParamRequiredMiddleware>();*/
/* TODO
if (q.ValidateQueryObject)
queryField.Use<QueryValidationMiddleware>();
*/
}
}
private async System.Threading.Tasks.Task<object> HandleQuery(IResolverContext resolverContext, Type typeToGet, object queryArgument)
{
dynamic service = resolverContext.Service(typeToGet);
var result = await service.HandleAsync((dynamic)queryArgument);
return result;
}
}
}

View File

@ -0,0 +1,22 @@
using HotChocolate;
using HotChocolate.Execution.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
{
public static class RequestExecutorBuilderExtensions
{
public static IRequestExecutorBuilder AddPoweredSoftQueries(this IRequestExecutorBuilder builder)
{
builder.AddTypeExtension<QueryObjectType>();
return builder;
}
public static IRequestExecutorBuilder AddPoweredSoftMutations(this IRequestExecutorBuilder builder)
{
builder.AddTypeExtension<MutationObjectType>();
return builder;
}
}
}

View File

@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQue
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQuery.AspNetCore", "PoweredSoft.CQRS.DynamicQuery.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQuery.AspNetCore", "PoweredSoft.CQRS.DynamicQuery.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}"
EndProject 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
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -63,6 +65,10 @@ Global
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|Any CPU.Build.0 = Release|Any CPU {0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|Any CPU.Build.0 = Release|Any CPU
{BF8E3B0D-8651-4541-892F-F607C5E80F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE