added dynamic queries for minimal api
This commit is contained in:
parent
a0426aa0d1
commit
5ba351de9c
@ -24,7 +24,10 @@
|
|||||||
"Bash(timeout:*)",
|
"Bash(timeout:*)",
|
||||||
"Bash(tasklist:*)",
|
"Bash(tasklist:*)",
|
||||||
"Bash(dotnet build:*)",
|
"Bash(dotnet build:*)",
|
||||||
"Bash(dotnet --list-sdks:*)"
|
"Bash(dotnet --list-sdks:*)",
|
||||||
|
"Bash(dotnet sln:*)",
|
||||||
|
"Bash(pkill:*)",
|
||||||
|
"Bash(python3:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@ -0,0 +1,340 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Svrnty.CQRS.Abstractions;
|
||||||
|
using Svrnty.CQRS.Abstractions.Discovery;
|
||||||
|
using Svrnty.CQRS.Abstractions.Security;
|
||||||
|
using Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||||
|
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||||
|
using Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||||
|
using Svrnty.CQRS.DynamicQuery.Discover;
|
||||||
|
using PoweredSoft.DynamicQuery.Core;
|
||||||
|
|
||||||
|
namespace Svrnty.CQRS.DynamicQuery.MinimalApi;
|
||||||
|
|
||||||
|
public static class EndpointRouteBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IEndpointRouteBuilder MapSvrntyDynamicQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query")
|
||||||
|
{
|
||||||
|
var queryDiscovery = endpoints.ServiceProvider.GetRequiredService<IQueryDiscovery>();
|
||||||
|
var authorizationService = endpoints.ServiceProvider.GetService<IQueryAuthorizationService>();
|
||||||
|
|
||||||
|
foreach (var queryMeta in queryDiscovery.GetQueries())
|
||||||
|
{
|
||||||
|
// Only process dynamic queries
|
||||||
|
if (queryMeta.Category != "DynamicQuery")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
|
||||||
|
if (ignoreAttribute != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (queryMeta is not DynamicQueryMeta dynamicQueryMeta)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}";
|
||||||
|
|
||||||
|
if (dynamicQueryMeta.ParamsType == null)
|
||||||
|
{
|
||||||
|
// DynamicQuery<TSource, TDestination>
|
||||||
|
MapDynamicQueryPost(endpoints, route, dynamicQueryMeta, authorizationService);
|
||||||
|
MapDynamicQueryGet(endpoints, route, dynamicQueryMeta, authorizationService);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// DynamicQuery<TSource, TDestination, TParams>
|
||||||
|
MapDynamicQueryWithParamsPost(endpoints, route, dynamicQueryMeta, authorizationService);
|
||||||
|
MapDynamicQueryWithParamsGet(endpoints, route, dynamicQueryMeta, authorizationService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MapDynamicQueryPost(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
DynamicQueryMeta dynamicQueryMeta,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
{
|
||||||
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
|
var queryType = typeof(IDynamicQuery<,>).MakeGenericType(sourceType, destinationType);
|
||||||
|
var resultType = typeof(IQueryExecutionResult<>).MakeGenericType(destinationType);
|
||||||
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType);
|
||||||
|
var requestBodyType = typeof(DynamicQuery<,>).MakeGenericType(sourceType, destinationType);
|
||||||
|
|
||||||
|
// Create a delegate that properly handles the typed deserialization
|
||||||
|
var mapPostMethod = typeof(EndpointRouteBuilderExtensions)
|
||||||
|
.GetMethod(nameof(MapDynamicQueryPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
|
.MakeGenericMethod(sourceType, destinationType);
|
||||||
|
|
||||||
|
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType, authorizationService])!;
|
||||||
|
|
||||||
|
endpoint
|
||||||
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_Post")
|
||||||
|
.WithTags("DynamicQueries")
|
||||||
|
.Accepts(requestBodyType, "application/json")
|
||||||
|
.Produces(200, resultType)
|
||||||
|
.Produces(400)
|
||||||
|
.Produces(401)
|
||||||
|
.Produces(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteHandlerBuilder MapDynamicQueryPostTyped<TSource, TDestination>(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
Type queryType,
|
||||||
|
Type handlerType,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
where TSource : class
|
||||||
|
where TDestination : class
|
||||||
|
{
|
||||||
|
return endpoints.MapPost(route, async (
|
||||||
|
DynamicQuery<TSource, TDestination> query,
|
||||||
|
HttpContext context,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (authorizationService != null)
|
||||||
|
{
|
||||||
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
|
if (authorizationResult == AuthorizationResult.Forbidden)
|
||||||
|
return Results.StatusCode(403);
|
||||||
|
if (authorizationResult == AuthorizationResult.Unauthorized)
|
||||||
|
return Results.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = serviceProvider.GetRequiredService(handlerType);
|
||||||
|
var handleMethod = handlerType.GetMethod("HandleAsync");
|
||||||
|
if (handleMethod == null)
|
||||||
|
return Results.Problem("Handler method not found");
|
||||||
|
|
||||||
|
var task = (Task)handleMethod.Invoke(handler, [query, cancellationToken])!;
|
||||||
|
await task;
|
||||||
|
|
||||||
|
var resultProperty = task.GetType().GetProperty("Result");
|
||||||
|
var result = resultProperty?.GetValue(task);
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MapDynamicQueryGet(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
DynamicQueryMeta dynamicQueryMeta,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
{
|
||||||
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
|
var queryType = typeof(IDynamicQuery<,>).MakeGenericType(sourceType, destinationType);
|
||||||
|
var resultType = typeof(IQueryExecutionResult<>).MakeGenericType(destinationType);
|
||||||
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType);
|
||||||
|
var requestQueryType = typeof(DynamicQuery<,>).MakeGenericType(sourceType, destinationType);
|
||||||
|
|
||||||
|
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (authorizationService != null)
|
||||||
|
{
|
||||||
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
|
if (authorizationResult == AuthorizationResult.Forbidden)
|
||||||
|
return Results.StatusCode(403);
|
||||||
|
if (authorizationResult == AuthorizationResult.Unauthorized)
|
||||||
|
return Results.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = Activator.CreateInstance(requestQueryType);
|
||||||
|
if (query == null)
|
||||||
|
return Results.BadRequest("Could not create query instance");
|
||||||
|
|
||||||
|
// Bind query string parameters to query object
|
||||||
|
foreach (var property in requestQueryType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (!property.CanWrite)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var queryStringValue = context.Request.Query[property.Name].FirstOrDefault();
|
||||||
|
if (queryStringValue != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var convertedValue = Convert.ChangeType(queryStringValue, property.PropertyType);
|
||||||
|
property.SetValue(query, convertedValue);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Skip properties that can't be converted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = serviceProvider.GetRequiredService(handlerType);
|
||||||
|
var handleMethod = handlerType.GetMethod("HandleAsync");
|
||||||
|
if (handleMethod == null)
|
||||||
|
return Results.Problem("Handler method not found");
|
||||||
|
|
||||||
|
var task = (Task)handleMethod.Invoke(handler, [query, cancellationToken])!;
|
||||||
|
await task;
|
||||||
|
|
||||||
|
var resultProperty = task.GetType().GetProperty("Result");
|
||||||
|
var result = resultProperty?.GetValue(task);
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
})
|
||||||
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_Get")
|
||||||
|
.WithTags("DynamicQueries")
|
||||||
|
.Produces(200, resultType)
|
||||||
|
.Produces(400)
|
||||||
|
.Produces(401)
|
||||||
|
.Produces(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MapDynamicQueryWithParamsPost(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
DynamicQueryMeta dynamicQueryMeta,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
{
|
||||||
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
|
var paramsType = dynamicQueryMeta.ParamsType!;
|
||||||
|
var queryType = typeof(IDynamicQuery<,,>).MakeGenericType(sourceType, destinationType, paramsType);
|
||||||
|
var resultType = typeof(IQueryExecutionResult<>).MakeGenericType(destinationType);
|
||||||
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType);
|
||||||
|
var requestBodyType = typeof(DynamicQuery<,,>).MakeGenericType(sourceType, destinationType, paramsType);
|
||||||
|
|
||||||
|
var mapPostMethod = typeof(EndpointRouteBuilderExtensions)
|
||||||
|
.GetMethod(nameof(MapDynamicQueryWithParamsPostTyped), BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
|
.MakeGenericMethod(sourceType, destinationType, paramsType);
|
||||||
|
|
||||||
|
var endpoint = (RouteHandlerBuilder)mapPostMethod.Invoke(null, [endpoints, route, queryType, handlerType, authorizationService])!;
|
||||||
|
|
||||||
|
endpoint
|
||||||
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_WithParams_Post")
|
||||||
|
.WithTags("DynamicQueries")
|
||||||
|
.Accepts(requestBodyType, "application/json")
|
||||||
|
.Produces(200, resultType)
|
||||||
|
.Produces(400)
|
||||||
|
.Produces(401)
|
||||||
|
.Produces(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteHandlerBuilder MapDynamicQueryWithParamsPostTyped<TSource, TDestination, TParams>(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
Type queryType,
|
||||||
|
Type handlerType,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
where TSource : class
|
||||||
|
where TDestination : class
|
||||||
|
where TParams : class
|
||||||
|
{
|
||||||
|
return endpoints.MapPost(route, async (
|
||||||
|
DynamicQuery<TSource, TDestination, TParams> query,
|
||||||
|
HttpContext context,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (authorizationService != null)
|
||||||
|
{
|
||||||
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
|
if (authorizationResult == AuthorizationResult.Forbidden)
|
||||||
|
return Results.StatusCode(403);
|
||||||
|
if (authorizationResult == AuthorizationResult.Unauthorized)
|
||||||
|
return Results.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = serviceProvider.GetRequiredService(handlerType);
|
||||||
|
var handleMethod = handlerType.GetMethod("HandleAsync");
|
||||||
|
if (handleMethod == null)
|
||||||
|
return Results.Problem("Handler method not found");
|
||||||
|
|
||||||
|
var task = (Task)handleMethod.Invoke(handler, [query, cancellationToken])!;
|
||||||
|
await task;
|
||||||
|
|
||||||
|
var resultProperty = task.GetType().GetProperty("Result");
|
||||||
|
var result = resultProperty?.GetValue(task);
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MapDynamicQueryWithParamsGet(
|
||||||
|
IEndpointRouteBuilder endpoints,
|
||||||
|
string route,
|
||||||
|
DynamicQueryMeta dynamicQueryMeta,
|
||||||
|
IQueryAuthorizationService? authorizationService)
|
||||||
|
{
|
||||||
|
var sourceType = dynamicQueryMeta.SourceType;
|
||||||
|
var destinationType = dynamicQueryMeta.DestinationType;
|
||||||
|
var paramsType = dynamicQueryMeta.ParamsType!;
|
||||||
|
var queryType = typeof(IDynamicQuery<,,>).MakeGenericType(sourceType, destinationType, paramsType);
|
||||||
|
var resultType = typeof(IQueryExecutionResult<>).MakeGenericType(destinationType);
|
||||||
|
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType);
|
||||||
|
var requestQueryType = typeof(DynamicQuery<,,>).MakeGenericType(sourceType, destinationType, paramsType);
|
||||||
|
|
||||||
|
endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (authorizationService != null)
|
||||||
|
{
|
||||||
|
var authorizationResult = await authorizationService.IsAllowedAsync(queryType, cancellationToken);
|
||||||
|
if (authorizationResult == AuthorizationResult.Forbidden)
|
||||||
|
return Results.StatusCode(403);
|
||||||
|
if (authorizationResult == AuthorizationResult.Unauthorized)
|
||||||
|
return Results.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = Activator.CreateInstance(requestQueryType);
|
||||||
|
if (query == null)
|
||||||
|
return Results.BadRequest("Could not create query instance");
|
||||||
|
|
||||||
|
// Bind query string parameters to query object
|
||||||
|
foreach (var property in requestQueryType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (!property.CanWrite)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var queryStringValue = context.Request.Query[property.Name].FirstOrDefault();
|
||||||
|
if (queryStringValue != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var convertedValue = Convert.ChangeType(queryStringValue, property.PropertyType);
|
||||||
|
property.SetValue(query, convertedValue);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Skip properties that can't be converted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = serviceProvider.GetRequiredService(handlerType);
|
||||||
|
var handleMethod = handlerType.GetMethod("HandleAsync");
|
||||||
|
if (handleMethod == null)
|
||||||
|
return Results.Problem("Handler method not found");
|
||||||
|
|
||||||
|
var task = (Task)handleMethod.Invoke(handler, [query, cancellationToken])!;
|
||||||
|
await task;
|
||||||
|
|
||||||
|
var resultProperty = task.GetType().GetProperty("Result");
|
||||||
|
var result = resultProperty?.GetValue(task);
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
})
|
||||||
|
.WithName($"DynamicQuery_{dynamicQueryMeta.LowerCamelCaseName}_WithParams_Get")
|
||||||
|
.WithTags("DynamicQueries")
|
||||||
|
.Produces(200, resultType)
|
||||||
|
.Produces(400)
|
||||||
|
.Produces(401)
|
||||||
|
.Produces(403);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<IsAotCompatible>false</IsAotCompatible>
|
||||||
|
<LangVersion>14</LangVersion>
|
||||||
|
<Company>Svrnty</Company>
|
||||||
|
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||||
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<RepositoryUrl>https://github.com/svrnty/dotnet-cqrs</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<IncludeSource>true</IncludeSource>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery.Abstractions\Svrnty.CQRS.DynamicQuery.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery\Svrnty.CQRS.DynamicQuery.csproj" />
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery.AspNetCore\Svrnty.CQRS.DynamicQuery.AspNetCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@ -27,6 +27,10 @@ public static class EndpointRouteBuilderExtensions
|
|||||||
if (ignoreAttribute != null)
|
if (ignoreAttribute != null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Skip dynamic queries - they are handled by MapSvrntyDynamicQueries
|
||||||
|
if (queryMeta.Category == "DynamicQuery")
|
||||||
|
continue;
|
||||||
|
|
||||||
var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}";
|
var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}";
|
||||||
|
|
||||||
MapQueryPost(endpoints, route, queryMeta, authorizationService);
|
MapQueryPost(endpoints, route, queryMeta, authorizationService);
|
||||||
|
|||||||
@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.Grpc.Generators
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.Sample", "Svrnty.Sample\Svrnty.Sample.csproj", "{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.Sample", "Svrnty.Sample\Svrnty.Sample.csproj", "{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.DynamicQuery.MinimalApi", "Svrnty.CQRS.DynamicQuery.MinimalApi\Svrnty.CQRS.DynamicQuery.MinimalApi.csproj", "{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -187,6 +189,18 @@ Global
|
|||||||
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x64.Build.0 = Release|Any CPU
|
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x86.ActiveCfg = Release|Any CPU
|
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x86.Build.0 = Release|Any CPU
|
{B7CEE5D0-A7CA-4C2C-AE42-923A8CF9B854}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1D0E3388-5E4B-4C0E-B826-ACF256FF7C84}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@ -5,6 +5,8 @@ using Svrnty.CQRS.FluentValidation;
|
|||||||
using Svrnty.Sample;
|
using Svrnty.Sample;
|
||||||
using Svrnty.Sample.Grpc.Extensions;
|
using Svrnty.Sample.Grpc.Extensions;
|
||||||
using Svrnty.CQRS.MinimalApi;
|
using Svrnty.CQRS.MinimalApi;
|
||||||
|
using Svrnty.CQRS.DynamicQuery;
|
||||||
|
using Svrnty.CQRS.DynamicQuery.MinimalApi;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -24,6 +26,13 @@ builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
|
|||||||
// Register query handlers with CQRS
|
// Register query handlers with CQRS
|
||||||
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
||||||
|
|
||||||
|
// Register PoweredSoft.DynamicQuery services
|
||||||
|
builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, SimpleAsyncQueryableService>();
|
||||||
|
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
|
||||||
|
|
||||||
|
// Register dynamic query for User entity with queryable provider
|
||||||
|
builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>();
|
||||||
|
|
||||||
// Register discovery services for MinimalApi
|
// Register discovery services for MinimalApi
|
||||||
builder.Services.AddDefaultCommandDiscovery();
|
builder.Services.AddDefaultCommandDiscovery();
|
||||||
builder.Services.AddDefaultQueryDiscovery();
|
builder.Services.AddDefaultQueryDiscovery();
|
||||||
@ -50,6 +59,7 @@ app.UseSwaggerUI();
|
|||||||
// Map MinimalApi endpoints for commands and queries
|
// Map MinimalApi endpoints for commands and queries
|
||||||
app.MapSvrntyCommands();
|
app.MapSvrntyCommands();
|
||||||
app.MapSvrntyQueries();
|
app.MapSvrntyQueries();
|
||||||
|
app.MapSvrntyDynamicQueries();
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("Auto-Generated gRPC Server with Reflection, Validation, MinimalApi and Swagger");
|
Console.WriteLine("Auto-Generated gRPC Server with Reflection, Validation, MinimalApi and Swagger");
|
||||||
|
|||||||
72
Svrnty.Sample/SimpleAsyncQueryableService.cs
Normal file
72
Svrnty.Sample/SimpleAsyncQueryableService.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using PoweredSoft.Data.Core;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace Svrnty.Sample;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple in-memory implementation of IAsyncQueryableService for testing/demo purposes
|
||||||
|
/// </summary>
|
||||||
|
public class SimpleAsyncQueryableService : IAsyncQueryableService
|
||||||
|
{
|
||||||
|
public IEnumerable<IAsyncQueryableHandlerService> Handlers { get; } = Array.Empty<IAsyncQueryableHandlerService>();
|
||||||
|
|
||||||
|
public Task<List<TSource>> ToListAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TSource?> FirstOrDefaultAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TSource?> FirstOrDefaultAsync<TSource>(IQueryable<TSource> queryable, Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.FirstOrDefault(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TSource?> LastOrDefaultAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.LastOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TSource?> LastOrDefaultAsync<TSource>(IQueryable<TSource> queryable, Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.LastOrDefault(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> AnyAsync<TSource>(IQueryable<TSource> queryable, Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.Any(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> AllAsync<TSource>(IQueryable<TSource> queryable, Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.All(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> CountAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<long> LongCountAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.LongCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TSource?> SingleOrDefaultAsync<TSource>(IQueryable<TSource> queryable, Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.SingleOrDefault(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> AnyAsync<TSource>(IQueryable<TSource> queryable, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(queryable.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncQueryableHandlerService? GetAsyncQueryableHandler<TSource>(IQueryable<TSource> queryable)
|
||||||
|
{
|
||||||
|
return null; // No special handling needed for in-memory queries
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,6 +30,8 @@
|
|||||||
<ProjectReference Include="..\Svrnty.CQRS.Grpc.Generators\Svrnty.CQRS.Grpc.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\Svrnty.CQRS.Grpc.Generators\Svrnty.CQRS.Grpc.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
<ProjectReference Include="..\Svrnty.CQRS.FluentValidation\Svrnty.CQRS.FluentValidation.csproj" />
|
<ProjectReference Include="..\Svrnty.CQRS.FluentValidation\Svrnty.CQRS.FluentValidation.csproj" />
|
||||||
<ProjectReference Include="..\Svrnty.CQRS.MinimalApi\Svrnty.CQRS.MinimalApi.csproj" />
|
<ProjectReference Include="..\Svrnty.CQRS.MinimalApi\Svrnty.CQRS.MinimalApi.csproj" />
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery\Svrnty.CQRS.DynamicQuery.csproj" />
|
||||||
|
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery.MinimalApi\Svrnty.CQRS.DynamicQuery.MinimalApi.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Import the proto generation targets for testing (in production this would come from the NuGet package) -->
|
<!-- Import the proto generation targets for testing (in production this would come from the NuGet package) -->
|
||||||
|
|||||||
23
Svrnty.Sample/UserQueryableProvider.cs
Normal file
23
Svrnty.Sample/UserQueryableProvider.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||||
|
|
||||||
|
namespace Svrnty.Sample;
|
||||||
|
|
||||||
|
public class UserQueryableProvider : IQueryableProvider<User>
|
||||||
|
{
|
||||||
|
// In-memory sample data for demonstration
|
||||||
|
private static readonly List<User> SampleUsers = new()
|
||||||
|
{
|
||||||
|
new User { Id = 1, Name = "Alice Smith", Email = "alice@example.com" },
|
||||||
|
new User { Id = 2, Name = "Bob Johnson", Email = "bob@example.com" },
|
||||||
|
new User { Id = 3, Name = "Charlie Brown", Email = "charlie@example.com" },
|
||||||
|
new User { Id = 4, Name = "Diana Prince", Email = "diana@example.com" },
|
||||||
|
new User { Id = 5, Name = "Eve Adams", Email = "eve@example.com" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public Task<IQueryable<User>> GetQueryableAsync(object query, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Return in-memory queryable for demonstration
|
||||||
|
// The query parameter can be used to apply custom filters or transformations if needed
|
||||||
|
return Task.FromResult(SampleUsers.AsQueryable());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user