mega cleanup :D
This commit is contained in:
parent
ed01f58a0c
commit
facc8d7851
@ -30,7 +30,8 @@
|
||||
"Bash(python3:*)",
|
||||
"Bash(grpcurl:*)",
|
||||
"Bash(lsof:*)",
|
||||
"Bash(xargs kill -9)"
|
||||
"Bash(xargs kill -9)",
|
||||
"Bash(dotnet run:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Marks a command to be ignored by all endpoint generators (AspNetCore MVC, MinimalApi, gRPC)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class IgnoreCommandAttribute : Attribute
|
||||
{
|
||||
}
|
||||
11
Svrnty.CQRS.Abstractions/Attributes/IgnoreQueryAttribute.cs
Normal file
11
Svrnty.CQRS.Abstractions/Attributes/IgnoreQueryAttribute.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Marks a query to be ignored by all endpoint generators (AspNetCore MVC, MinimalApi, gRPC)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class IgnoreQueryAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CommandControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class QueryControllerAuthorizationAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class QueryControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Company>Svrnty</Company>
|
||||
<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>
|
||||
</Project>
|
||||
@ -1,61 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class DynamicQueryController<TUnderlyingQuery, TSource, TDestination> : Controller
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
[HttpPost, QueryControllerAuthorization]
|
||||
public async Task<IQueryExecutionResult<TDestination>> HandleAsync(
|
||||
[FromBody] DynamicQuery<TSource, TDestination> query,
|
||||
[FromServices]IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>> queryHandler
|
||||
)
|
||||
{
|
||||
var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted);
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpGet, QueryControllerAuthorization]
|
||||
public async Task<IQueryExecutionResult<TDestination>> HandleGetAsync(
|
||||
[FromQuery] DynamicQuery<TSource, TDestination> query,
|
||||
[FromServices] IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>> queryHandler
|
||||
)
|
||||
{
|
||||
var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class DynamicQueryController<TUnderlyingQuery, TSource, TDestination, TParams> : Controller
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
[HttpPost, QueryControllerAuthorization]
|
||||
public async Task<IQueryExecutionResult<TDestination>> HandleAsync(
|
||||
[FromBody] DynamicQuery<TSource, TDestination, TParams> query,
|
||||
[FromServices] IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>> queryHandler
|
||||
)
|
||||
{
|
||||
var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted);
|
||||
return result;
|
||||
}
|
||||
|
||||
[HttpGet, QueryControllerAuthorization]
|
||||
public async Task<IQueryExecutionResult<TDestination>> HandleGetAsync(
|
||||
[FromQuery] DynamicQuery<TSource, TDestination, TParams> query,
|
||||
[FromServices] IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>> queryHandler
|
||||
)
|
||||
{
|
||||
var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
public class DynamicQueryControllerConvention : IControllerModelConvention
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DynamicQueryControllerConvention(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Apply(ControllerModel controller)
|
||||
{
|
||||
if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("DynamicQueryController") && controller.ControllerType.Assembly == typeof(DynamicQueryControllerConvention).Assembly)
|
||||
{
|
||||
var genericType = controller.ControllerType.GenericTypeArguments[0];
|
||||
var queryDiscovery = _serviceProvider.GetRequiredService<IQueryDiscovery>();
|
||||
var query = queryDiscovery.FindQuery(genericType);
|
||||
controller.ControllerName = query.LowerCamelCaseName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
using Svrnty.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
using Svrnty.CQRS.DynamicQuery.Discover;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
public class DynamicQueryControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
: IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
|
||||
/**
|
||||
* public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
|
||||
{
|
||||
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
|
||||
foreach (var f in queryDiscovery.GetQueries())
|
||||
{
|
||||
var ignoreAttribute = f.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
if (f.Category != "DynamicQuery")
|
||||
continue;
|
||||
|
||||
if (f is DynamicQueryMeta dynamicQueryMeta)
|
||||
{
|
||||
if (dynamicQueryMeta.ParamsType == null)
|
||||
{
|
||||
var controllerType = typeof(DynamicQueryController<,,>).MakeGenericType(f.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var controllerType = typeof(DynamicQueryController<,,,>).MakeGenericType(f.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType, dynamicQueryMeta.ParamsType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
|
||||
{
|
||||
var queryDiscovery = serviceProvider.GetRequiredService<IQueryDiscovery>();
|
||||
foreach (var queryMeta in queryDiscovery.GetQueries())
|
||||
{
|
||||
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
if (queryMeta.Category != "DynamicQuery")
|
||||
continue;
|
||||
|
||||
if (queryMeta is DynamicQueryMeta dynamicQueryMeta)
|
||||
{
|
||||
// todo: add better error output for the user
|
||||
|
||||
if (dynamicQueryMeta.ParamsType == null)
|
||||
{
|
||||
// todo: not aot friendly
|
||||
var controllerType = typeof(DynamicQueryController<,,>).MakeGenericType(queryMeta.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: not aot friendly
|
||||
var controllerType = typeof(DynamicQueryController<,,,>).MakeGenericType(queryMeta.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType, dynamicQueryMeta.ParamsType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
public class DynamicQueryControllerOptions
|
||||
{
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||
|
||||
public static class MvcBuilderExtensions
|
||||
{
|
||||
public static IMvcBuilder AddOpenHarborDynamicQueries(this IMvcBuilder builder, Action<DynamicQueryControllerOptions> configuration = null)
|
||||
{
|
||||
var options = new DynamicQueryControllerOptions();
|
||||
configuration?.Invoke(options);
|
||||
var services = builder.Services;
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
builder.AddMvcOptions(o => o.Conventions.Add(new DynamicQueryControllerConvention(serviceProvider)));
|
||||
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new DynamicQueryControllerFeatureProvider(serviceProvider)));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Company>Svrnty</Company>
|
||||
<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.AspNetCore.Abstractions\Svrnty.CQRS.AspNetCore.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery.Abstractions\Svrnty.CQRS.DynamicQuery.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.DynamicQuery\Svrnty.CQRS.DynamicQuery.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -9,11 +9,11 @@ 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.AspNetCore.Abstractions.Attributes;
|
||||
using Svrnty.CQRS.DynamicQuery;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||
using Svrnty.CQRS.DynamicQuery.Discover;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
@ -32,7 +32,7 @@ public static class EndpointRouteBuilderExtensions
|
||||
if (queryMeta.Category != "DynamicQuery")
|
||||
continue;
|
||||
|
||||
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
|
||||
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<IgnoreQueryAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
|
||||
@ -32,6 +32,5 @@
|
||||
<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>
|
||||
|
||||
@ -4,13 +4,9 @@ using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.Discover;
|
||||
|
||||
public class DynamicQueryMeta : QueryMeta
|
||||
public class DynamicQueryMeta(Type queryType, Type serviceType, Type queryResultType)
|
||||
: QueryMeta(queryType, serviceType, queryResultType)
|
||||
{
|
||||
public DynamicQueryMeta(Type queryType, Type serviceType, Type queryResultType) : base(queryType, serviceType, queryResultType)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Type SourceType => QueryType.GetGenericArguments()[0];
|
||||
public Type DestinationType => QueryType.GetGenericArguments()[1];
|
||||
public override string Category => "DynamicQuery";
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
public class DynamicQuery<TSource, TDestination> : DynamicQuery, IDynamicQuery<TSource, TDestination>
|
||||
where TSource : class
|
||||
@ -1,8 +1,8 @@
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
public class DynamicQueryAggregate
|
||||
{
|
||||
@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.AspNetCore;
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
public class DynamicQueryFilter
|
||||
{
|
||||
@ -17,7 +16,6 @@ public class DynamicQueryFilter
|
||||
public string Path { get; set; }
|
||||
public object Value { get; set; }
|
||||
|
||||
[FromQuery(Name ="value")]
|
||||
public string QueryValue
|
||||
{
|
||||
get
|
||||
69
Svrnty.CQRS.FluentValidation/CqrsBuilderExtensions.cs
Normal file
69
Svrnty.CQRS.FluentValidation/CqrsBuilderExtensions.cs
Normal file
@ -0,0 +1,69 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
|
||||
namespace Svrnty.CQRS.FluentValidation;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for CqrsBuilder to support FluentValidation
|
||||
/// </summary>
|
||||
public static class CqrsBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a command handler with FluentValidation validator to the CQRS pipeline
|
||||
/// </summary>
|
||||
public static CqrsBuilder AddCommand<TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
|
||||
this CqrsBuilder builder)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
where TValidator : class, IValidator<TCommand>
|
||||
{
|
||||
// Add the command handler
|
||||
builder.AddCommand<TCommand, TCommandHandler>();
|
||||
|
||||
// Add the validator
|
||||
builder.Services.AddTransient<IValidator<TCommand>, TValidator>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command handler with result and FluentValidation validator to the CQRS pipeline
|
||||
/// </summary>
|
||||
public static CqrsBuilder AddCommand<TCommand, TResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
|
||||
this CqrsBuilder builder)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TResult>
|
||||
where TValidator : class, IValidator<TCommand>
|
||||
{
|
||||
// Add the command handler
|
||||
builder.AddCommand<TCommand, TResult, TCommandHandler>();
|
||||
|
||||
// Add the validator
|
||||
builder.Services.AddTransient<IValidator<TCommand>, TValidator>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a query handler with FluentValidation validator to the CQRS pipeline
|
||||
/// </summary>
|
||||
public static CqrsBuilder AddQuery<TQuery, TResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TQueryHandler, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
|
||||
this CqrsBuilder builder)
|
||||
where TQuery : class
|
||||
where TQueryHandler : class, IQueryHandler<TQuery, TResult>
|
||||
where TValidator : class, IValidator<TQuery>
|
||||
{
|
||||
// Add the query handler
|
||||
builder.AddQuery<TQuery, TResult, TQueryHandler>();
|
||||
|
||||
// Add the validator
|
||||
builder.Services.AddTransient<IValidator<TQuery>, TValidator>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@ -785,6 +785,72 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" endpoints.MapGrpcService<DynamicQueryServiceImpl>();");
|
||||
sb.AppendLine(" return endpoints;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
|
||||
// Add configuration-based methods
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Registers gRPC services based on configuration");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static IServiceCollection AddGrpcFromConfiguration(this IServiceCollection services)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var config = services.BuildServiceProvider().GetService<Svrnty.CQRS.Configuration.CqrsConfiguration>();");
|
||||
sb.AppendLine(" var grpcOptions = config?.GetConfiguration<Svrnty.CQRS.Grpc.GrpcCqrsOptions>();");
|
||||
sb.AppendLine(" if (grpcOptions != null)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" services.AddGrpc();");
|
||||
sb.AppendLine(" if (grpcOptions.ShouldEnableReflection)");
|
||||
sb.AppendLine(" services.AddGrpcReflection();");
|
||||
sb.AppendLine();
|
||||
if (hasCommands)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapCommands())");
|
||||
sb.AppendLine(" services.AddSingleton<CommandServiceImpl>();");
|
||||
}
|
||||
if (hasQueries)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapQueries())");
|
||||
sb.AppendLine(" services.AddSingleton<QueryServiceImpl>();");
|
||||
}
|
||||
if (hasDynamicQueries)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapQueries())");
|
||||
sb.AppendLine(" services.AddSingleton<DynamicQueryServiceImpl>();");
|
||||
}
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" return services;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Maps gRPC service endpoints based on configuration");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static IEndpointRouteBuilder MapGrpcFromConfiguration(this IEndpointRouteBuilder endpoints)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var config = endpoints.ServiceProvider.GetService<Svrnty.CQRS.Configuration.CqrsConfiguration>();");
|
||||
sb.AppendLine(" var grpcOptions = config?.GetConfiguration<Svrnty.CQRS.Grpc.GrpcCqrsOptions>();");
|
||||
sb.AppendLine(" if (grpcOptions != null)");
|
||||
sb.AppendLine(" {");
|
||||
if (hasCommands)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapCommands())");
|
||||
sb.AppendLine(" endpoints.MapGrpcService<CommandServiceImpl>();");
|
||||
}
|
||||
if (hasQueries)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapQueries())");
|
||||
sb.AppendLine(" endpoints.MapGrpcService<QueryServiceImpl>();");
|
||||
}
|
||||
if (hasDynamicQueries)
|
||||
{
|
||||
sb.AppendLine(" if (grpcOptions.GetShouldMapQueries())");
|
||||
sb.AppendLine(" endpoints.MapGrpcService<DynamicQueryServiceImpl>();");
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" if (grpcOptions.ShouldEnableReflection)");
|
||||
sb.AppendLine(" endpoints.MapGrpcReflectionService();");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" return endpoints;");
|
||||
sb.AppendLine(" }");
|
||||
}
|
||||
|
||||
sb.AppendLine(" }");
|
||||
@ -1318,11 +1384,11 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
// Build the dynamic query object
|
||||
if (dynamicQuery.HasParams)
|
||||
{
|
||||
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}, {dynamicQuery.ParamsTypeFullyQualified}>");
|
||||
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}, {dynamicQuery.ParamsTypeFullyQualified}>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}>");
|
||||
sb.AppendLine($" var query = new Svrnty.CQRS.DynamicQuery.DynamicQuery<{dynamicQuery.SourceTypeFullyQualified}, {dynamicQuery.DestinationTypeFullyQualified}>");
|
||||
}
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Page = request.Page > 0 ? request.Page : null,");
|
||||
@ -1362,15 +1428,15 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
}
|
||||
|
||||
// Add helper methods for converting proto messages to AspNetCore types
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>? ConvertFilters(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.DynamicQueryFilter>? ConvertFilters(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (protoFilters == null || protoFilters.Count == 0)");
|
||||
sb.AppendLine(" return null;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" var filters = new List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>();");
|
||||
sb.AppendLine(" var filters = new List<Svrnty.CQRS.DynamicQuery.DynamicQueryFilter>();");
|
||||
sb.AppendLine(" foreach (var protoFilter in protoFilters)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter");
|
||||
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.DynamicQueryFilter");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Path = protoFilter.Path,");
|
||||
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)protoFilter.Type).ToString(),");
|
||||
@ -1395,12 +1461,12 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" return filters;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter> ConvertProtoFiltersToList(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.DynamicQueryFilter> ConvertProtoFiltersToList(Google.Protobuf.Collections.RepeatedField<DynamicQueryFilter> protoFilters)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var result = new List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter>();");
|
||||
sb.AppendLine(" var result = new List<Svrnty.CQRS.DynamicQuery.DynamicQueryFilter>();");
|
||||
sb.AppendLine(" foreach (var pf in protoFilters)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryFilter");
|
||||
sb.AppendLine(" var filter = new Svrnty.CQRS.DynamicQuery.DynamicQueryFilter");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Path = pf.Path,");
|
||||
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)pf.Type).ToString(),");
|
||||
@ -1447,12 +1513,12 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryAggregate>? ConvertAggregates(Google.Protobuf.Collections.RepeatedField<DynamicQueryAggregate> protoAggregates)");
|
||||
sb.AppendLine(" private static List<Svrnty.CQRS.DynamicQuery.DynamicQueryAggregate>? ConvertAggregates(Google.Protobuf.Collections.RepeatedField<DynamicQueryAggregate> protoAggregates)");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" if (protoAggregates == null || protoAggregates.Count == 0)");
|
||||
sb.AppendLine(" return null;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" return protoAggregates.Select(a => new Svrnty.CQRS.DynamicQuery.AspNetCore.DynamicQueryAggregate");
|
||||
sb.AppendLine(" return protoAggregates.Select(a => new Svrnty.CQRS.DynamicQuery.DynamicQueryAggregate");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Path = a.Path,");
|
||||
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.AggregateType)a.Type).ToString()");
|
||||
|
||||
72
Svrnty.CQRS.Grpc/CqrsBuilderExtensions.cs
Normal file
72
Svrnty.CQRS.Grpc/CqrsBuilderExtensions.cs
Normal file
@ -0,0 +1,72 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for CqrsBuilder to add gRPC support
|
||||
/// </summary>
|
||||
public static class CqrsBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds gRPC support to the CQRS pipeline
|
||||
/// </summary>
|
||||
/// <param name="builder">The CQRS builder</param>
|
||||
/// <param name="configure">Optional configuration for gRPC endpoints</param>
|
||||
/// <returns>The CQRS builder for method chaining</returns>
|
||||
public static CqrsBuilder AddGrpc(this CqrsBuilder builder, Action<GrpcCqrsOptions>? configure = null)
|
||||
{
|
||||
var options = new GrpcCqrsOptions();
|
||||
configure?.Invoke(options);
|
||||
builder.Configuration.SetConfiguration(options);
|
||||
|
||||
// Try to find and call the generated AddGrpcFromConfiguration method
|
||||
var addGrpcMethod = FindExtensionMethod("AddGrpcFromConfiguration");
|
||||
if (addGrpcMethod != null)
|
||||
{
|
||||
addGrpcMethod.Invoke(null, new object[] { builder.Services });
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Warning: AddGrpcFromConfiguration not found. gRPC services were not registered.");
|
||||
Console.WriteLine("Make sure your project has source generators enabled and references Svrnty.CQRS.Grpc.Generators.");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static MethodInfo? FindExtensionMethod(string methodName)
|
||||
{
|
||||
// Search through all loaded assemblies for the extension method
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes()
|
||||
.Where(t => t.IsClass && t.IsSealed && !t.IsGenericType && t.IsPublic);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var method = type.GetMethod(methodName,
|
||||
BindingFlags.Static | BindingFlags.Public,
|
||||
null,
|
||||
new[] { typeof(IServiceCollection) },
|
||||
null);
|
||||
|
||||
if (method != null)
|
||||
return method;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip assemblies that can't be inspected
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
68
Svrnty.CQRS.Grpc/GrpcCqrsOptions.cs
Normal file
68
Svrnty.CQRS.Grpc/GrpcCqrsOptions.cs
Normal file
@ -0,0 +1,68 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Svrnty.CQRS.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for gRPC CQRS endpoints
|
||||
/// </summary>
|
||||
public class GrpcCqrsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether reflection should be enabled
|
||||
/// </summary>
|
||||
public bool ShouldEnableReflection { get; private set; }
|
||||
|
||||
private bool ShouldMapCommands { get; set; }
|
||||
private bool ShouldMapQueries { get; set; }
|
||||
private bool WasMappingMethodCalled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables gRPC reflection for the service
|
||||
/// </summary>
|
||||
public GrpcCqrsOptions EnableReflection()
|
||||
{
|
||||
ShouldEnableReflection = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps command endpoints
|
||||
/// </summary>
|
||||
public GrpcCqrsOptions MapCommands()
|
||||
{
|
||||
WasMappingMethodCalled = true;
|
||||
ShouldMapCommands = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps query endpoints (includes dynamic queries)
|
||||
/// </summary>
|
||||
public GrpcCqrsOptions MapQueries()
|
||||
{
|
||||
WasMappingMethodCalled = true;
|
||||
ShouldMapQueries = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps both command and query endpoints
|
||||
/// </summary>
|
||||
public GrpcCqrsOptions MapCommandsAndQueries()
|
||||
{
|
||||
WasMappingMethodCalled = true;
|
||||
ShouldMapCommands = true;
|
||||
ShouldMapQueries = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether commands should be mapped (defaults to true if no mapping methods were called)
|
||||
/// </summary>
|
||||
public bool GetShouldMapCommands() => WasMappingMethodCalled ? ShouldMapCommands : true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether queries should be mapped (defaults to true if no mapping methods were called)
|
||||
/// </summary>
|
||||
public bool GetShouldMapQueries() => WasMappingMethodCalled ? ShouldMapQueries : true;
|
||||
}
|
||||
@ -2,10 +2,10 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsAotCompatible>false</IsAotCompatible>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Company>Svrnty</Company>
|
||||
<Nullable>enable</Nullable>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<Company>Svrnty</Company>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/svrnty/dotnet-cqrs</RepositoryUrl>
|
||||
@ -23,21 +23,13 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\icon.png" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||
<None Include="build\Svrnty.CQRS.Grpc.targets" Pack="true" PackagePath="build\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.76.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.68.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Grpc.Abstractions\Svrnty.CQRS.Grpc.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Grpc.Generators\Svrnty.CQRS.Grpc.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS\Svrnty.CQRS.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
25
Svrnty.CQRS.MinimalApi/CqrsBuilderExtensions.cs
Normal file
25
Svrnty.CQRS.MinimalApi/CqrsBuilderExtensions.cs
Normal file
@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
|
||||
namespace Svrnty.CQRS.MinimalApi;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for CqrsBuilder to add MinimalApi support
|
||||
/// </summary>
|
||||
public static class CqrsBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds MinimalApi support to the CQRS pipeline
|
||||
/// </summary>
|
||||
/// <param name="builder">The CQRS builder</param>
|
||||
/// <param name="configure">Optional configuration for MinimalApi endpoints</param>
|
||||
/// <returns>The CQRS builder for method chaining</returns>
|
||||
public static CqrsBuilder AddMinimalApi(this CqrsBuilder builder, Action<MinimalApiCqrsOptions>? configure = null)
|
||||
{
|
||||
var options = new MinimalApiCqrsOptions();
|
||||
configure?.Invoke(options);
|
||||
builder.Configuration.SetConfiguration(options);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@ -8,9 +8,9 @@ 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.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
namespace Svrnty.CQRS.MinimalApi;
|
||||
|
||||
@ -23,7 +23,7 @@ public static class EndpointRouteBuilderExtensions
|
||||
|
||||
foreach (var queryMeta in queryDiscovery.GetQueries())
|
||||
{
|
||||
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<QueryControllerIgnoreAttribute>();
|
||||
var ignoreAttribute = queryMeta.QueryType.GetCustomAttribute<IgnoreQueryAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
@ -157,7 +157,7 @@ public static class EndpointRouteBuilderExtensions
|
||||
|
||||
foreach (var commandMeta in commandDiscovery.GetCommands())
|
||||
{
|
||||
var ignoreAttribute = commandMeta.CommandType.GetCustomAttribute<CommandControllerIgnoreAttribute>();
|
||||
var ignoreAttribute = commandMeta.CommandType.GetCustomAttribute<IgnoreCommandAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
|
||||
39
Svrnty.CQRS.MinimalApi/MinimalApiCqrsOptions.cs
Normal file
39
Svrnty.CQRS.MinimalApi/MinimalApiCqrsOptions.cs
Normal file
@ -0,0 +1,39 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Svrnty.CQRS.MinimalApi;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for MinimalApi CQRS endpoints
|
||||
/// </summary>
|
||||
public class MinimalApiCqrsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to map command endpoints (default: true)
|
||||
/// </summary>
|
||||
public bool MapCommands { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to map query endpoints (default: true)
|
||||
/// </summary>
|
||||
public bool MapQueries { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to map dynamic query endpoints (default: true)
|
||||
/// </summary>
|
||||
public bool MapDynamicQueries { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Route prefix for command endpoints (default: "api/command")
|
||||
/// </summary>
|
||||
public string CommandRoutePrefix { get; set; } = "api/command";
|
||||
|
||||
/// <summary>
|
||||
/// Route prefix for query endpoints (default: "api/query")
|
||||
/// </summary>
|
||||
public string QueryRoutePrefix { get; set; } = "api/query";
|
||||
|
||||
/// <summary>
|
||||
/// Route prefix for dynamic query endpoints (default: "api/query")
|
||||
/// </summary>
|
||||
public string DynamicQueryRoutePrefix { get; set; } = "api/query";
|
||||
}
|
||||
@ -35,6 +35,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.AspNetCore.Abstractions\Svrnty.CQRS.AspNetCore.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS\Svrnty.CQRS.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
101
Svrnty.CQRS.MinimalApi/WebApplicationExtensions.cs
Normal file
101
Svrnty.CQRS.MinimalApi/WebApplicationExtensions.cs
Normal file
@ -0,0 +1,101 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Svrnty.CQRS.MinimalApi;
|
||||
|
||||
public static class WebApplicationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps Svrnty CQRS endpoints based on configuration (supports both gRPC and MinimalApi)
|
||||
/// </summary>
|
||||
public static WebApplication UseSvrntyCqrs(this WebApplication app)
|
||||
{
|
||||
var config = app.Services.GetService<CqrsConfiguration>();
|
||||
|
||||
// Handle gRPC configuration if available
|
||||
// Note: GrpcCqrsOptions type is from Svrnty.CQRS.Grpc package
|
||||
var grpcOptionsType = Type.GetType("Svrnty.CQRS.Grpc.GrpcCqrsOptions, Svrnty.CQRS.Grpc");
|
||||
if (grpcOptionsType != null && config != null)
|
||||
{
|
||||
var getConfigMethod = typeof(CqrsConfiguration).GetMethod("GetConfiguration")!.MakeGenericMethod(grpcOptionsType);
|
||||
var grpcOptions = getConfigMethod.Invoke(config, null);
|
||||
|
||||
if (grpcOptions != null)
|
||||
{
|
||||
// Try to find and call MapGrpcFromConfiguration extension method via reflection
|
||||
// This is generated by the source generator in consumer projects
|
||||
var grpcMethod = FindExtensionMethod("MapGrpcFromConfiguration");
|
||||
if (grpcMethod != null)
|
||||
{
|
||||
grpcMethod.Invoke(null, new object[] { app });
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Warning: MapGrpcFromConfiguration not found. gRPC endpoints were not mapped.");
|
||||
Console.WriteLine("Make sure your project references Svrnty.CQRS.Grpc and has source generators enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle MinimalApi configuration if available
|
||||
var minimalApiOptions = config?.GetConfiguration<MinimalApiCqrsOptions>();
|
||||
if (minimalApiOptions != null)
|
||||
{
|
||||
if (minimalApiOptions.MapCommands)
|
||||
{
|
||||
app.MapSvrntyCommands(minimalApiOptions.CommandRoutePrefix);
|
||||
}
|
||||
|
||||
if (minimalApiOptions.MapQueries)
|
||||
{
|
||||
app.MapSvrntyQueries(minimalApiOptions.QueryRoutePrefix);
|
||||
}
|
||||
|
||||
// TODO: Add dynamic query mapping when available
|
||||
// if (minimalApiOptions.MapDynamicQueries)
|
||||
// {
|
||||
// app.MapSvrntyDynamicQueries(minimalApiOptions.DynamicQueryRoutePrefix);
|
||||
// }
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static MethodInfo? FindExtensionMethod(string methodName)
|
||||
{
|
||||
// Search through all loaded assemblies for the extension method
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes()
|
||||
.Where(t => t.IsClass && t.IsSealed && !t.IsGenericType && t.IsPublic);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var method = type.GetMethod(methodName,
|
||||
BindingFlags.Static | BindingFlags.Public,
|
||||
null,
|
||||
new[] { typeof(IEndpointRouteBuilder) },
|
||||
null);
|
||||
|
||||
if (method != null)
|
||||
return method;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip assemblies that can't be inspected
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS.Abstractions",
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS", "Svrnty.CQRS\Svrnty.CQRS.csproj", "{7069B98F-8736-4114-8AF5-1ACE094E6238}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS.AspNetCore.Abstractions", "Svrnty.CQRS.AspNetCore.Abstractions\Svrnty.CQRS.AspNetCore.Abstractions.csproj", "{4C466827-31D3-4081-A751-C2FC7C381D7E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{617BA357-1A1F-40C5-B19A-A65A960E6142}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
LICENSE = LICENSE
|
||||
@ -17,8 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS.DynamicQuery", "Svrnty.CQRS.DynamicQuery\Svrnty.CQRS.DynamicQuery.csproj", "{A38CE930-191F-417C-B5BE-8CC62DB47513}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS.DynamicQuery.AspNetCore", "Svrnty.CQRS.DynamicQuery.AspNetCore\Svrnty.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svrnty.CQRS.FluentValidation", "Svrnty.CQRS.FluentValidation\Svrnty.CQRS.FluentValidation.csproj", "{70BD37C4-7497-474D-9A40-A701203971D8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svrnty.CQRS.DynamicQuery.Abstractions", "Svrnty.CQRS.DynamicQuery.Abstractions\Svrnty.CQRS.DynamicQuery.Abstractions.csproj", "{8B9F8ACE-10EA-4215-9776-DE29EC93B020}"
|
||||
@ -69,18 +65,6 @@ Global
|
||||
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7069B98F-8736-4114-8AF5-1ACE094E6238}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4C466827-31D3-4081-A751-C2FC7C381D7E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@ -93,18 +77,6 @@ Global
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Debug|x86.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.Build.0 = Release|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
||||
77
Svrnty.CQRS/Configuration/CqrsBuilder.cs
Normal file
77
Svrnty.CQRS/Configuration/CqrsBuilder.cs
Normal file
@ -0,0 +1,77 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.Discovery;
|
||||
|
||||
namespace Svrnty.CQRS.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Builder for configuring CQRS services
|
||||
/// </summary>
|
||||
public class CqrsBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the service collection for manual service registration
|
||||
/// </summary>
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CQRS configuration object (used by extension packages)
|
||||
/// </summary>
|
||||
public CqrsConfiguration Configuration { get; }
|
||||
|
||||
internal CqrsBuilder(IServiceCollection services)
|
||||
{
|
||||
Services = services;
|
||||
Configuration = new CqrsConfiguration();
|
||||
|
||||
// Add discovery services by default
|
||||
services.AddDefaultCommandDiscovery();
|
||||
services.AddDefaultQueryDiscovery();
|
||||
|
||||
// Register configuration as singleton so it can be accessed later
|
||||
services.AddSingleton(Configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the builder configuration
|
||||
/// </summary>
|
||||
internal void Build()
|
||||
{
|
||||
// Configuration is now handled by extension methods in respective packages
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command handler to the CQRS pipeline
|
||||
/// </summary>
|
||||
public CqrsBuilder AddCommand<TCommand, TCommandHandler>()
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
{
|
||||
Services.AddCommand<TCommand, TCommandHandler>();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command handler with result to the CQRS pipeline
|
||||
/// </summary>
|
||||
public CqrsBuilder AddCommand<TCommand, TResult, TCommandHandler>()
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TResult>
|
||||
{
|
||||
Services.AddCommand<TCommand, TResult, TCommandHandler>();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a query handler to the CQRS pipeline
|
||||
/// </summary>
|
||||
public CqrsBuilder AddQuery<TQuery, TResult, TQueryHandler>()
|
||||
where TQuery : class
|
||||
where TQueryHandler : class, IQueryHandler<TQuery, TResult>
|
||||
{
|
||||
Services.AddQuery<TQuery, TResult, TQueryHandler>();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
38
Svrnty.CQRS/Configuration/CqrsConfiguration.cs
Normal file
38
Svrnty.CQRS/Configuration/CqrsConfiguration.cs
Normal file
@ -0,0 +1,38 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for CQRS services and endpoints.
|
||||
/// Supports extension by third-party packages through generic configuration storage.
|
||||
/// </summary>
|
||||
public class CqrsConfiguration
|
||||
{
|
||||
private readonly Dictionary<Type, object> _configurations = new();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a configuration object for a specific type
|
||||
/// </summary>
|
||||
public void SetConfiguration<T>(T config) where T : class
|
||||
{
|
||||
_configurations[typeof(T)] = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a configuration object for a specific type
|
||||
/// </summary>
|
||||
public T? GetConfiguration<T>() where T : class
|
||||
{
|
||||
return _configurations.TryGetValue(typeof(T), out var config) ? config as T : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a configuration exists for a specific type
|
||||
/// </summary>
|
||||
public bool HasConfiguration<T>() where T : class
|
||||
{
|
||||
return _configurations.ContainsKey(typeof(T));
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,27 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
using Svrnty.CQRS.Discovery;
|
||||
|
||||
namespace Svrnty.CQRS;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Svrnty CQRS services with fluent configuration
|
||||
/// </summary>
|
||||
public static IServiceCollection AddSvrntyCqrs(this IServiceCollection services, Action<CqrsBuilder>? configure = null)
|
||||
{
|
||||
var builder = new CqrsBuilder(services);
|
||||
configure?.Invoke(builder);
|
||||
builder.Build(); // Execute deferred registrations
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDefaultQueryDiscovery(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddTransient<IQueryDiscovery, QueryDiscovery>();
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Svrnty.CQRS;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.FluentValidation;
|
||||
using Svrnty.CQRS.Grpc;
|
||||
using Svrnty.Sample;
|
||||
using Svrnty.Sample.Grpc.Extensions;
|
||||
using Svrnty.CQRS.MinimalApi;
|
||||
using Svrnty.CQRS.DynamicQuery;
|
||||
using Svrnty.CQRS.DynamicQuery.MinimalApi;
|
||||
@ -19,37 +18,44 @@ builder.WebHost.ConfigureKestrel(options =>
|
||||
options.ListenLocalhost(6001, o => o.Protocols = HttpProtocols.Http1);
|
||||
});
|
||||
|
||||
builder.Services.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
|
||||
builder.Services.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
|
||||
|
||||
builder.Services.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
||||
|
||||
// IMPORTANT: Register dynamic query dependencies FIRST
|
||||
// (before AddSvrntyCqrs, so gRPC services can find the handlers)
|
||||
builder.Services.AddTransient<PoweredSoft.Data.Core.IAsyncQueryableService, SimpleAsyncQueryableService>();
|
||||
builder.Services.AddTransient<PoweredSoft.DynamicQuery.Core.IQueryHandlerAsync, PoweredSoft.DynamicQuery.QueryHandlerAsync>();
|
||||
|
||||
builder.Services.AddDynamicQueryWithProvider<User, UserQueryableProvider>();
|
||||
|
||||
builder.Services.AddDefaultCommandDiscovery();
|
||||
builder.Services.AddDefaultQueryDiscovery();
|
||||
// Configure CQRS with fluent API
|
||||
builder.Services.AddSvrntyCqrs(cqrs =>
|
||||
{
|
||||
// Register commands and queries with validators
|
||||
cqrs.AddCommand<AddUserCommand, int, AddUserCommandHandler, AddUserCommandValidator>();
|
||||
cqrs.AddCommand<RemoveUserCommand, RemoveUserCommandHandler>();
|
||||
cqrs.AddQuery<FetchUserQuery, User, FetchUserQueryHandler>();
|
||||
|
||||
// Enable gRPC endpoints with reflection
|
||||
cqrs.AddGrpc(grpc =>
|
||||
{
|
||||
grpc.EnableReflection();
|
||||
});
|
||||
|
||||
// Enable MinimalApi endpoints
|
||||
cqrs.AddMinimalApi();
|
||||
});
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
builder.Services.AddGrpcCommandsAndQueries();
|
||||
app.MapGrpcCommandsAndQueries();
|
||||
app.MapGrpcReflectionService();
|
||||
// Map all configured CQRS endpoints (gRPC and MinimalApi)
|
||||
app.UseSvrntyCqrs();
|
||||
|
||||
// Map dynamic queries manually for now
|
||||
app.MapSvrntyDynamicQueries();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
app.MapSvrntyCommands();
|
||||
app.MapSvrntyQueries();
|
||||
app.MapSvrntyDynamicQueries();
|
||||
|
||||
|
||||
Console.WriteLine("Auto-Generated gRPC Server with Reflection, Validation, MinimalApi and Swagger");
|
||||
Console.WriteLine("gRPC (HTTP/2): http://localhost:6000");
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
<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" />
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Grpc.Abstractions\Svrnty.CQRS.Grpc.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Import the proto generation targets for testing (in production this would come from the NuGet package) -->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user