dotnet-cqrs/Svrnty.CQRS.DynamicQuery.MinimalApi/EndpointRouteBuilderExtensions.cs

341 lines
14 KiB
C#

#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.Attributes;
using Svrnty.CQRS.Abstractions.Discovery;
using Svrnty.CQRS.Abstractions.Security;
using Svrnty.CQRS.DynamicQuery;
using Svrnty.CQRS.DynamicQuery.Abstractions;
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<IgnoreQueryAttribute>();
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);
}
}