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

264 lines
11 KiB
C#

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 MapSvrntyQueries(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())
{
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
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();
}
// Retrieve already-deserialized and validated query from HttpContext.Items
var query = context.Items[ValidationFilter<object>.ValidatedObjectKey];
if (query == null || !queryMeta.QueryType.IsInstanceOfType(query))
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);
})
.AddEndpointFilter((IEndpointFilter)Activator.CreateInstance(typeof(ValidationFilter<>).MakeGenericType(queryMeta.QueryType))!)
.WithName($"Query_{queryMeta.LowerCamelCaseName}_Post")
.WithTags("Queries")
.Accepts(queryMeta.QueryType, "application/json")
.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 MapSvrntyCommands(this IEndpointRouteBuilder endpoints, string routePrefix = "api/command")
{
var commandDiscovery = endpoints.ServiceProvider.GetRequiredService<ICommandDiscovery>();
var authorizationService = endpoints.ServiceProvider.GetService<ICommandAuthorizationService>();
foreach (var commandMeta in commandDiscovery.GetCommands())
{
var ignoreAttribute = commandMeta.CommandType.GetCustomAttribute<CommandControllerIgnoreAttribute>();
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();
}
// Retrieve already-deserialized and validated command from HttpContext.Items
var command = context.Items[ValidationFilter<object>.ValidatedObjectKey];
if (command == null || !commandMeta.CommandType.IsInstanceOfType(command))
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();
})
.AddEndpointFilter((IEndpointFilter)Activator.CreateInstance(typeof(ValidationFilter<>).MakeGenericType(commandMeta.CommandType))!)
.WithName($"Command_{commandMeta.LowerCamelCaseName}")
.WithTags("Commands")
.Accepts(commandMeta.CommandType, "application/json")
.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();
}
// Retrieve already-deserialized and validated command from HttpContext.Items
var command = context.Items[ValidationFilter<object>.ValidatedObjectKey];
if (command == null || !commandMeta.CommandType.IsInstanceOfType(command))
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);
})
.AddEndpointFilter((IEndpointFilter)Activator.CreateInstance(typeof(ValidationFilter<>).MakeGenericType(commandMeta.CommandType))!)
.WithName($"Command_{commandMeta.LowerCamelCaseName}")
.WithTags("Commands")
.Accepts(commandMeta.CommandType, "application/json")
.Produces(200, commandMeta.CommandResultType)
.Produces(400)
.Produces(401)
.Produces(403);
}
}