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; namespace Svrnty.CQRS.MinimalApi; public static class EndpointRouteBuilderExtensions { public static IEndpointRouteBuilder MapOpenHarborQueries(this IEndpointRouteBuilder endpoints, string routePrefix = "api/query") { var queryDiscovery = endpoints.ServiceProvider.GetRequiredService(); var authorizationService = endpoints.ServiceProvider.GetService(); foreach (var queryMeta in queryDiscovery.GetQueries()) { var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute(); if (ignoreAttribute != null) continue; var route = $"{routePrefix}/{queryMeta.LowerCamelCaseName}"; MapQueryPost(endpoints, route, queryMeta, authorizationService); MapQueryGet(endpoints, route, queryMeta, authorizationService); } return endpoints; } private static void MapQueryPost( IEndpointRouteBuilder endpoints, string route, IQueryMeta queryMeta, IQueryAuthorizationService? authorizationService) { var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType); endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) => { if (authorizationService != null) { var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken); if (authorizationResult == AuthorizationResult.Forbidden) return Results.StatusCode(403); if (authorizationResult == AuthorizationResult.Unauthorized) return Results.Unauthorized(); } var query = await context.Request.ReadFromJsonAsync(queryMeta.QueryType, cancellationToken); if (query == null) return Results.BadRequest("Invalid query payload"); 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($"Query_{queryMeta.LowerCamelCaseName}_Post") .WithTags("Queries") .Produces(200, queryMeta.QueryResultType) .Produces(400) .Produces(401) .Produces(403); } private static void MapQueryGet( IEndpointRouteBuilder endpoints, string route, IQueryMeta queryMeta, IQueryAuthorizationService? authorizationService) { var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryMeta.QueryType, queryMeta.QueryResultType); endpoints.MapGet(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) => { if (authorizationService != null) { var authorizationResult = await authorizationService.IsAllowedAsync(queryMeta.QueryType, cancellationToken); if (authorizationResult == AuthorizationResult.Forbidden) return Results.StatusCode(403); if (authorizationResult == AuthorizationResult.Unauthorized) return Results.Unauthorized(); } var query = Activator.CreateInstance(queryMeta.QueryType); if (query == null) return Results.BadRequest("Could not create query instance"); foreach (var property in queryMeta.QueryType.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 { } } } 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($"Query_{queryMeta.LowerCamelCaseName}_Get") .WithTags("Queries") .Produces(200, queryMeta.QueryResultType) .Produces(400) .Produces(401) .Produces(403); } public static IEndpointRouteBuilder MapOpenHarborCommands(this IEndpointRouteBuilder endpoints, string routePrefix = "api/command") { var commandDiscovery = endpoints.ServiceProvider.GetRequiredService(); var authorizationService = endpoints.ServiceProvider.GetService(); foreach (var commandMeta in commandDiscovery.GetCommands()) { var ignoreAttribute = commandMeta.CommandType.GetCustomAttribute(); if (ignoreAttribute != null) continue; var route = $"{routePrefix}/{commandMeta.LowerCamelCaseName}"; if (commandMeta.CommandResultType == null) { MapCommandWithoutResult(endpoints, route, commandMeta, authorizationService); } else { MapCommandWithResult(endpoints, route, commandMeta, authorizationService); } } return endpoints; } private static void MapCommandWithoutResult( IEndpointRouteBuilder endpoints, string route, ICommandMeta commandMeta, ICommandAuthorizationService? authorizationService) { var handlerType = typeof(ICommandHandler<>).MakeGenericType(commandMeta.CommandType); endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) => { if (authorizationService != null) { var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken); if (authorizationResult == AuthorizationResult.Forbidden) return Results.StatusCode(403); if (authorizationResult == AuthorizationResult.Unauthorized) return Results.Unauthorized(); } var command = await context.Request.ReadFromJsonAsync(commandMeta.CommandType, cancellationToken); if (command == null) return Results.BadRequest("Invalid command payload"); var handler = serviceProvider.GetRequiredService(handlerType); var handleMethod = handlerType.GetMethod("HandleAsync"); if (handleMethod == null) return Results.Problem("Handler method not found"); await (Task)handleMethod.Invoke(handler, [command, cancellationToken])!; return Results.Ok(); }) .WithName($"Command_{commandMeta.LowerCamelCaseName}") .WithTags("Commands") .Produces(200) .Produces(400) .Produces(401) .Produces(403); } private static void MapCommandWithResult( IEndpointRouteBuilder endpoints, string route, ICommandMeta commandMeta, ICommandAuthorizationService? authorizationService) { var handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandMeta.CommandType, commandMeta.CommandResultType!); endpoints.MapPost(route, async (HttpContext context, IServiceProvider serviceProvider, CancellationToken cancellationToken) => { if (authorizationService != null) { var authorizationResult = await authorizationService.IsAllowedAsync(commandMeta.CommandType, cancellationToken); if (authorizationResult == AuthorizationResult.Forbidden) return Results.StatusCode(403); if (authorizationResult == AuthorizationResult.Unauthorized) return Results.Unauthorized(); } var command = await context.Request.ReadFromJsonAsync(commandMeta.CommandType, cancellationToken); if (command == null) return Results.BadRequest("Invalid command payload"); 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, [command, cancellationToken])!; await task; var resultProperty = task.GetType().GetProperty("Result"); var result = resultProperty?.GetValue(task); return Results.Ok(result); }) .WithName($"Command_{commandMeta.LowerCamelCaseName}") .WithTags("Commands") .Produces(200, commandMeta.CommandResultType) .Produces(400) .Produces(401) .Produces(403); } }