update namespaces
refactor the name of the organisation
This commit is contained in:
parent
88c86513e9
commit
1c81288895
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CommandNameAttribute : Attribute
|
||||
{
|
||||
public CommandNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class QueryNameAttribute : Attribute
|
||||
{
|
||||
public QueryNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
|
51
OpenHarbor.CQRS.Abstractions/Discovery/CommandMeta.cs
Normal file
51
OpenHarbor.CQRS.Abstractions/Discovery/CommandMeta.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using OpenHarbor.CQRS.Abstractions.Attributes;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
public sealed class CommandMeta : ICommandMeta
|
||||
{
|
||||
public CommandMeta(Type commandType, Type serviceType, Type commandResultType)
|
||||
{
|
||||
CommandType = commandType;
|
||||
ServiceType = serviceType;
|
||||
CommandResultType = commandResultType;
|
||||
}
|
||||
|
||||
public CommandMeta(Type commandType, Type serviceType)
|
||||
{
|
||||
CommandType = commandType;
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
private CommandNameAttribute NameAttribute => CommandType.GetCustomAttribute<CommandNameAttribute>();
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
var name = NameAttribute?.Name ?? CommandType.Name.Replace("Command", string.Empty);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public Type CommandType { get; }
|
||||
public Type ServiceType { get; }
|
||||
public Type CommandResultType { get; }
|
||||
|
||||
public string LowerCamelCaseName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return Name;
|
||||
|
||||
var name = Name;
|
||||
var firstLetter = Char.ToLowerInvariant(name[0]);
|
||||
var ret = $"{firstLetter}{name.Substring(1)}";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
OpenHarbor.CQRS.Abstractions/Discovery/ICommandMeta.cs
Normal file
13
OpenHarbor.CQRS.Abstractions/Discovery/ICommandMeta.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
public interface ICommandMeta
|
||||
{
|
||||
string Name { get; }
|
||||
Type CommandType { get; }
|
||||
Type ServiceType { get; }
|
||||
Type CommandResultType { get; }
|
||||
string LowerCamelCaseName { get; }
|
||||
}
|
||||
|
23
OpenHarbor.CQRS.Abstractions/Discovery/IQueryDiscovery.cs
Normal file
23
OpenHarbor.CQRS.Abstractions/Discovery/IQueryDiscovery.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
public interface IQueryDiscovery
|
||||
{
|
||||
IQueryMeta FindQuery(string name);
|
||||
IQueryMeta FindQuery(Type queryType);
|
||||
IEnumerable<IQueryMeta> GetQueries();
|
||||
bool QueryExists(string name);
|
||||
bool QueryExists(Type queryType);
|
||||
}
|
||||
|
||||
public interface ICommandDiscovery
|
||||
{
|
||||
bool CommandExists(string name);
|
||||
bool CommandExists(Type commandType);
|
||||
ICommandMeta FindCommand(string name);
|
||||
ICommandMeta FindCommand(Type commandType);
|
||||
IEnumerable<ICommandMeta> GetCommands();
|
||||
}
|
||||
|
13
OpenHarbor.CQRS.Abstractions/Discovery/IQueryMeta.cs
Normal file
13
OpenHarbor.CQRS.Abstractions/Discovery/IQueryMeta.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
public interface IQueryMeta
|
||||
{
|
||||
string Name { get; }
|
||||
Type QueryType { get; }
|
||||
Type ServiceType { get; }
|
||||
Type QueryResultType { get; }
|
||||
string Category { get; }
|
||||
string LowerCamelCaseName { get; }
|
||||
}
|
46
OpenHarbor.CQRS.Abstractions/Discovery/QueryMeta.cs
Normal file
46
OpenHarbor.CQRS.Abstractions/Discovery/QueryMeta.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using OpenHarbor.CQRS.Abstractions.Attributes;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
public class QueryMeta : IQueryMeta
|
||||
{
|
||||
public QueryMeta(Type queryType, Type serviceType, Type queryResultType)
|
||||
{
|
||||
QueryType = queryType;
|
||||
ServiceType = serviceType;
|
||||
QueryResultType = queryResultType;
|
||||
}
|
||||
|
||||
protected virtual QueryNameAttribute NameAttribute => QueryType.GetCustomAttribute<QueryNameAttribute>();
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
var name = NameAttribute?.Name ?? QueryType.Name.Replace("Query", string.Empty);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Type QueryType { get; }
|
||||
public virtual Type ServiceType { get; }
|
||||
public virtual Type QueryResultType { get; }
|
||||
public virtual string Category => "BasicQuery";
|
||||
|
||||
public string LowerCamelCaseName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return Name;
|
||||
|
||||
var name = Name;
|
||||
var firstLetter = char.ToLowerInvariant(name[0]);
|
||||
var ret = $"{firstLetter}{name[1..]}";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
OpenHarbor.CQRS.Abstractions/ICommandHandler.cs
Normal file
16
OpenHarbor.CQRS.Abstractions/ICommandHandler.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
public interface ICommandHandler<in TCommand>
|
||||
where TCommand : class
|
||||
{
|
||||
Task HandleAsync(TCommand command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ICommandHandler<in TCommand, TCommandResult>
|
||||
where TCommand : class
|
||||
{
|
||||
Task<TCommandResult> HandleAsync(TCommand command, CancellationToken cancellationToken = default);
|
||||
}
|
10
OpenHarbor.CQRS.Abstractions/IQueryHandler.cs
Normal file
10
OpenHarbor.CQRS.Abstractions/IQueryHandler.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
public interface IQueryHandler<in TQuery, TQueryResult>
|
||||
where TQuery : class
|
||||
{
|
||||
Task<TQueryResult> HandleAsync(TQuery query, CancellationToken cancellationToken = default);
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Copyright>Powered Softwares Inc.</Copyright>
|
||||
<PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl>
|
||||
<Company>PoweredSoft</Company>
|
||||
<Authors>PoweredSoft Team</Authors>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
@ -0,0 +1,8 @@
|
||||
namespace OpenHarbor.CQRS.Abstractions.Security;
|
||||
|
||||
public enum AuthorizationResult
|
||||
{
|
||||
Unauthorized,
|
||||
Forbidden,
|
||||
Allowed
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Security;
|
||||
|
||||
public interface ICommandAuthorizationService
|
||||
{
|
||||
Task<AuthorizationResult> IsAllowedAsync(Type commandType, CancellationToken cancellationToken = default);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions.Security;
|
||||
|
||||
public interface IQueryAuthorizationService
|
||||
{
|
||||
Task<AuthorizationResult> IsAllowedAsync(Type queryType, CancellationToken cancellationToken = default);
|
||||
}
|
49
OpenHarbor.CQRS.Abstractions/ServiceCollectionExtensions.cs
Normal file
49
OpenHarbor.CQRS.Abstractions/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddQuery<TQuery, TQueryResult, TQueryHandler>(this IServiceCollection services)
|
||||
where TQuery : class
|
||||
where TQueryHandler : class, IQueryHandler<TQuery, TQueryResult>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<IQueryHandler<TQuery, TQueryResult>, TQueryHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var queryMeta = new QueryMeta(typeof(TQuery), typeof(IQueryHandler<TQuery, TQueryResult>), typeof(TQueryResult));
|
||||
services.AddSingleton<IQueryMeta>(queryMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandResult, TCommandHandler>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TCommandResult>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<ICommandHandler<TCommand, TCommandResult>, TCommandHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var commandMeta = new CommandMeta(typeof(TCommand), typeof(ICommandHandler<TCommand>), typeof(TCommandResult));
|
||||
services.AddSingleton<ICommandMeta>(commandMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandHandler>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<ICommandHandler<TCommand>, TCommandHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var commandMeta = new CommandMeta(typeof(TCommand), typeof(ICommandHandler<TCommand>));
|
||||
services.AddSingleton<ICommandMeta>(commandMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CommandControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class QueryControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
38
OpenHarbor.CQRS.AspNetCore/Mvc/CommandController.cs
Normal file
38
OpenHarbor.CQRS.AspNetCore/Mvc/CommandController.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/command/[controller]")]
|
||||
public class CommandController<TCommand> : Controller
|
||||
where TCommand : class
|
||||
{
|
||||
[HttpPost, CommandControllerAuthorization]
|
||||
public async Task<IActionResult> Handle([FromServices] ICommandHandler<TCommand> handler,
|
||||
[FromBody] TCommand command)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
await handler.HandleAsync(command, this.Request.HttpContext.RequestAborted);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/command/[controller]")]
|
||||
public class CommandController<TCommand, TTCommandResult> : Controller
|
||||
where TCommand : class
|
||||
{
|
||||
[HttpPost, CommandControllerAuthorization]
|
||||
public async Task<ActionResult<TTCommandResult>> Handle([FromServices] ICommandHandler<TCommand, TTCommandResult> handler,
|
||||
[FromBody] TCommand command)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
return Ok(await handler.HandleAsync(command, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
using OpenHarbor.CQRS.Abstractions.Security;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class CommandControllerAsyncAuthorizationFilter : IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly ICommandAuthorizationService _authorizationService;
|
||||
|
||||
public CommandControllerAsyncAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
_authorizationService = serviceProvider.GetService<ICommandAuthorizationService>();
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (_authorizationService == null)
|
||||
return;
|
||||
|
||||
var action = context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;
|
||||
if (action == null)
|
||||
throw new Exception("Only Supports controller action descriptor");
|
||||
|
||||
var attribute = action.MethodInfo.GetCustomAttribute<CommandControllerAuthorizationAttribute>();
|
||||
Type commandType;
|
||||
if (attribute?.CommandType != null)
|
||||
commandType = attribute.CommandType;
|
||||
else
|
||||
commandType = action.ControllerTypeInfo.GenericTypeArguments.First();
|
||||
|
||||
var ar = await _authorizationService.IsAllowedAsync(commandType);
|
||||
if (ar == AuthorizationResult.Forbidden)
|
||||
context.Result = new StatusCodeResult(403);
|
||||
else if(ar == AuthorizationResult.Unauthorized)
|
||||
context.Result = new StatusCodeResult(401);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class CommandControllerAuthorizationAttribute : TypeFilterAttribute
|
||||
{
|
||||
public CommandControllerAuthorizationAttribute() : base(typeof(CommandControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CommandControllerAuthorizationAttribute(Type commandType) : base(typeof(CommandControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
CommandType = commandType;
|
||||
}
|
||||
|
||||
public Type CommandType { get; } = null;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class CommandControllerConvention : IControllerModelConvention
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public CommandControllerConvention(IServiceProvider serviceProvider)
|
||||
{
|
||||
this._serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Apply(ControllerModel controller)
|
||||
{
|
||||
if (!controller.ControllerType.IsGenericType)
|
||||
return;
|
||||
|
||||
if (!controller.ControllerType.Name.Contains("CommandController"))
|
||||
return;
|
||||
|
||||
if (controller.ControllerType.Assembly != typeof(CommandControllerConvention).Assembly)
|
||||
return;
|
||||
|
||||
var genericType = controller.ControllerType.GenericTypeArguments[0];
|
||||
var commandDiscovery = this._serviceProvider.GetRequiredService<ICommandDiscovery>();
|
||||
var command = commandDiscovery.FindCommand(genericType);
|
||||
controller.ControllerName = command.LowerCamelCaseName;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using OpenHarbor.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class CommandControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
private readonly ServiceProvider _serviceProvider;
|
||||
|
||||
public CommandControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
{
|
||||
this._serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
|
||||
{
|
||||
var commandDiscovery = this._serviceProvider.GetRequiredService<ICommandDiscovery>();
|
||||
foreach (var f in commandDiscovery.GetCommands())
|
||||
{
|
||||
var ignoreAttribute = f.CommandType.GetCustomAttribute<CommandControllerIgnoreAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
if (f.CommandResultType == null)
|
||||
{
|
||||
var controllerType = typeof(CommandController<>).MakeGenericType(f.CommandType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var controllerType = typeof(CommandController<,>).MakeGenericType(f.CommandType, f.CommandResultType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class CommandControllerOptions
|
||||
{
|
||||
|
||||
}
|
27
OpenHarbor.CQRS.AspNetCore/Mvc/MvcBuilderExensions.cs
Normal file
27
OpenHarbor.CQRS.AspNetCore/Mvc/MvcBuilderExensions.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public static class MvcBuilderExtensions
|
||||
{
|
||||
public static IMvcBuilder AddPoweredSoftQueries(this IMvcBuilder builder, Action<QueryControllerOptions> configuration = null)
|
||||
{
|
||||
var options = new QueryControllerOptions();
|
||||
configuration?.Invoke(options);
|
||||
var services = builder.Services;
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
builder.AddMvcOptions(o => o.Conventions.Add(new QueryControllerConvention(serviceProvider)));
|
||||
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new QueryControllerFeatureProvider(serviceProvider)));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IMvcBuilder AddPoweredSoftCommands(this IMvcBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
builder.AddMvcOptions(o => o.Conventions.Add(new CommandControllerConvention(serviceProvider)));
|
||||
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new CommandControllerFeatureProvider(serviceProvider)));
|
||||
return builder;
|
||||
}
|
||||
}
|
33
OpenHarbor.CQRS.AspNetCore/Mvc/QueryController.cs
Normal file
33
OpenHarbor.CQRS.AspNetCore/Mvc/QueryController.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class QueryController<TQuery, TQueryResult> : Controller
|
||||
where TQuery : class
|
||||
{
|
||||
[HttpPost, QueryControllerAuthorization]
|
||||
public async Task<ActionResult<TQueryResult>> Handle([FromServices] IQueryHandler<TQuery, TQueryResult> handler,
|
||||
[FromBody] TQuery query)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
|
||||
return Ok(await handler.HandleAsync(query, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
|
||||
[HttpGet, QueryControllerAuthorization]
|
||||
public async Task<ActionResult<TQueryResult>> HandleGet([FromServices] IQueryHandler<TQuery, TQueryResult> handler,
|
||||
[FromQuery] TQuery query)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
|
||||
return Ok(await handler.HandleAsync(query, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
using OpenHarbor.CQRS.Abstractions.Security;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class QueryControllerAsyncAuthorizationFilter : IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly IQueryAuthorizationService _authorizationService;
|
||||
|
||||
public QueryControllerAsyncAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
_authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (_authorizationService == null)
|
||||
return;
|
||||
|
||||
var action = context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;
|
||||
if (action == null)
|
||||
throw new Exception("Only Supports controller action descriptor");
|
||||
|
||||
var attribute = action.MethodInfo.GetCustomAttribute<QueryControllerAuthorizationAttribute>();
|
||||
Type queryType;
|
||||
if (attribute?.QueryType != null)
|
||||
queryType = attribute.QueryType;
|
||||
else
|
||||
queryType = action.ControllerTypeInfo.GenericTypeArguments.First();
|
||||
|
||||
var ar = await _authorizationService.IsAllowedAsync(queryType);
|
||||
if (ar == AuthorizationResult.Forbidden)
|
||||
context.Result = new StatusCodeResult(403);
|
||||
else if (ar == AuthorizationResult.Unauthorized)
|
||||
context.Result = new StatusCodeResult(401);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class QueryControllerAuthorizationAttribute : TypeFilterAttribute
|
||||
{
|
||||
public QueryControllerAuthorizationAttribute() : base(typeof(QueryControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public QueryControllerAuthorizationAttribute(Type queryType) : base(typeof(QueryControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
QueryType = queryType;
|
||||
}
|
||||
|
||||
public Type QueryType { get; }
|
||||
}
|
27
OpenHarbor.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs
Normal file
27
OpenHarbor.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class QueryControllerConvention : IControllerModelConvention
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public QueryControllerConvention(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Apply(ControllerModel controller)
|
||||
{
|
||||
if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("QueryController") && controller.ControllerType.Assembly == typeof(QueryControllerConvention).Assembly)
|
||||
{
|
||||
var genericType = controller.ControllerType.GenericTypeArguments[0];
|
||||
var queryDiscovery = _serviceProvider.GetRequiredService<IQueryDiscovery>();
|
||||
var query = queryDiscovery.FindQuery(genericType);
|
||||
controller.ControllerName = query.LowerCamelCaseName;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using OpenHarbor.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class QueryControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
private readonly ServiceProvider _serviceProvider;
|
||||
|
||||
public QueryControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
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 != "BasicQuery")
|
||||
continue;
|
||||
|
||||
var controllerType = typeof(QueryController<,>).MakeGenericType(f.QueryType, f.QueryResultType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
6
OpenHarbor.CQRS.AspNetCore/Mvc/QueryControllerOptions.cs
Normal file
6
OpenHarbor.CQRS.AspNetCore/Mvc/QueryControllerOptions.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
|
||||
public class QueryControllerOptions
|
||||
{
|
||||
|
||||
}
|
19
OpenHarbor.CQRS.AspNetCore/OpenHarbor.CQRS.AspNetCore.csproj
Normal file
19
OpenHarbor.CQRS.AspNetCore/OpenHarbor.CQRS.AspNetCore.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.Abstractions\OpenHarbor.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.AspNetCore.Abstractions\OpenHarbor.CQRS.AspNetCore.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public class DynamicQueryInterceptorProvider<TSource, TDestination> : IDynamicQueryInterceptorProvider<TSource, TDestination>
|
||||
{
|
||||
private readonly Type[] _types;
|
||||
|
||||
public DynamicQueryInterceptorProvider(params Type[] types)
|
||||
{
|
||||
_types = types;
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetInterceptorsTypes()
|
||||
{
|
||||
return _types;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IAlterQueryableService<TSource, TDestination>
|
||||
{
|
||||
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQuery dynamicQuery, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IAlterQueryableService<TSource, TDestination, in TParams>
|
||||
where TParams : class
|
||||
{
|
||||
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQueryParams<TParams> dynamicQuery, CancellationToken cancellationToken = default);
|
||||
}
|
29
OpenHarbor.CQRS.DynamicQuery.Abstractions/IDynamicQuery.cs
Normal file
29
OpenHarbor.CQRS.DynamicQuery.Abstractions/IDynamicQuery.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IDynamicQuery<TSource, TDestination> : IDynamicQuery
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IDynamicQuery<TSource, TDestination, out TParams> : IDynamicQuery<TSource, TDestination>, IDynamicQueryParams<TParams>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IDynamicQuery
|
||||
{
|
||||
List<IFilter> GetFilters();
|
||||
List<IGroup> GetGroups();
|
||||
List<ISort> GetSorts();
|
||||
List<IAggregate> GetAggregates();
|
||||
int? GetPage();
|
||||
int? GetPageSize();
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IDynamicQueryInterceptorProvider<TSource, TDestination>
|
||||
{
|
||||
IEnumerable<Type> GetInterceptorsTypes();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IDynamicQueryParams<out TParams>
|
||||
where TParams : class
|
||||
{
|
||||
TParams GetParams();
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IQueryableProvider<TSource>
|
||||
{
|
||||
Task<IQueryable<TSource>> GetQueryableAsync(object query, CancellationToken cancellationToken = default);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PoweredSoft.DynamicQuery.Core" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
68
OpenHarbor.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs
Normal file
68
OpenHarbor.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore;
|
||||
|
||||
public class DynamicQuery<TSource, TDestination> : DynamicQuery, IDynamicQuery<TSource, TDestination>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class DynamicQuery<TSource, TDestination, TParams> : DynamicQuery, IDynamicQuery<TSource, TDestination, TParams>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
public TParams Params { get; set; }
|
||||
|
||||
public TParams GetParams()
|
||||
{
|
||||
return Params;
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicQuery : IDynamicQuery
|
||||
{
|
||||
public int? Page { get; set; }
|
||||
public int? PageSize { get; set; }
|
||||
public List<Sort> Sorts { get; set; }
|
||||
public List<DynamicQueryAggregate> Aggregates { get; set; }
|
||||
public List<Group> Groups { get; set; }
|
||||
public List<DynamicQueryFilter> Filters { get; set; }
|
||||
|
||||
|
||||
public List<IAggregate> GetAggregates()
|
||||
{
|
||||
return Aggregates?.Select(t => t.ToAggregate())?.ToList();//.AsEnumerable<IAggregate>()?.ToList();
|
||||
}
|
||||
|
||||
public List<IFilter> GetFilters()
|
||||
{
|
||||
return Filters?.Select(t => t.ToFilter())?.ToList();
|
||||
}
|
||||
|
||||
public List<IGroup> GetGroups()
|
||||
{
|
||||
return this.Groups?.AsEnumerable<IGroup>()?.ToList();
|
||||
}
|
||||
|
||||
public int? GetPage()
|
||||
{
|
||||
return this.Page;
|
||||
}
|
||||
|
||||
public int? GetPageSize()
|
||||
{
|
||||
return this.PageSize;
|
||||
}
|
||||
|
||||
public List<ISort> GetSorts()
|
||||
{
|
||||
return this.Sorts?.AsEnumerable<ISort>()?.ToList();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore;
|
||||
|
||||
public class DynamicQueryAggregate
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public IAggregate ToAggregate()
|
||||
{
|
||||
return new Aggregate
|
||||
{
|
||||
Type = Enum.Parse<AggregateType>(Type),
|
||||
Path = Path
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
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 OpenHarbor.CQRS.DynamicQuery.AspNetCore;
|
||||
|
||||
public class DynamicQueryFilter
|
||||
{
|
||||
public List<DynamicQueryFilter> Filters { get; set; }
|
||||
public bool? And { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool? Not { get; set; }
|
||||
public string Path { get; set; }
|
||||
public object Value { get; set; }
|
||||
|
||||
[FromQuery(Name ="value")]
|
||||
public string QueryValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool? CaseInsensitive { get; set; }
|
||||
|
||||
public IFilter ToFilter()
|
||||
{
|
||||
var type = Enum.Parse<FilterType>(Type);
|
||||
if (type == FilterType.Composite)
|
||||
{
|
||||
var compositeFilter = new CompositeFilter
|
||||
{
|
||||
And = And,
|
||||
Type = FilterType.Composite,
|
||||
Filters = Filters?.Select(t => t.ToFilter())?.ToList() ?? new List<IFilter>()
|
||||
};
|
||||
return compositeFilter;
|
||||
}
|
||||
|
||||
object value = Value;
|
||||
if (Value is JsonElement jsonElement)
|
||||
{
|
||||
switch (jsonElement.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
value = jsonElement.ToString();
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
if (jsonElement.ToString().Contains('.'))
|
||||
value = jsonElement.GetDecimal();
|
||||
else if (jsonElement.TryGetInt64(out var convertedValue))
|
||||
value = convertedValue;
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
value = true;
|
||||
break;
|
||||
case JsonValueKind.False:
|
||||
value = false;
|
||||
break;
|
||||
// TODO: Array support
|
||||
default:
|
||||
value = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var simpleFilter = new SimpleFilter
|
||||
{
|
||||
And = And,
|
||||
Type = type,
|
||||
Not = Not,
|
||||
Path = Path,
|
||||
Value = value,
|
||||
CaseInsensitive = CaseInsensitive,
|
||||
};
|
||||
return simpleFilter;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using OpenHarbor.CQRS.AspNetCore.Mvc;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class DynamicQueryController<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<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;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace OpenHarbor.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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using OpenHarbor.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Discover;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
public class DynamicQueryControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
private readonly ServiceProvider _serviceProvider;
|
||||
|
||||
public DynamicQueryControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (dynamicQueryMeta.ParamsType == null)
|
||||
{
|
||||
var controllerType = typeof(DynamicQueryController<,>).MakeGenericType(queryMeta.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var controllerType = typeof(DynamicQueryController<,,>).MakeGenericType(queryMeta.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType, dynamicQueryMeta.ParamsType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
public class DynamicQueryControllerOptions
|
||||
{
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.DynamicQuery.AspNetCore.Mvc;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.AspNetCore;
|
||||
|
||||
public static class MvcBuilderExtensions
|
||||
{
|
||||
public static IMvcBuilder AddPoweredSoftDynamicQueries(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;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.Abstractions\OpenHarbor.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.AspNetCore.Abstractions\OpenHarbor.CQRS.AspNetCore.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.AspNetCore\OpenHarbor.CQRS.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.DynamicQuery.Abstractions\OpenHarbor.CQRS.DynamicQuery.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.DynamicQuery\OpenHarbor.CQRS.DynamicQuery.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
32
OpenHarbor.CQRS.DynamicQuery/Discover/DynamicQueryMeta.cs
Normal file
32
OpenHarbor.CQRS.DynamicQuery/Discover/DynamicQueryMeta.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Pluralize.NET;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery.Discover;
|
||||
|
||||
public class DynamicQueryMeta : QueryMeta
|
||||
{
|
||||
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";
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OverridableName != null)
|
||||
return OverridableName;
|
||||
|
||||
var pluralizer = new Pluralizer();
|
||||
return pluralizer.Pluralize(DestinationType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public Type ParamsType { get; internal set; }
|
||||
public string OverridableName { get; internal set; }
|
||||
}
|
||||
|
67
OpenHarbor.CQRS.DynamicQuery/DynamicQueryHandler.cs
Normal file
67
OpenHarbor.CQRS.DynamicQuery/DynamicQueryHandler.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery;
|
||||
|
||||
public class DynamicQueryHandler<TSource, TDestination>
|
||||
: DynamicQueryHandlerBase<TSource, TDestination>,
|
||||
OpenHarbor.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync,
|
||||
IEnumerable<IQueryableProvider<TSource>> queryableProviders,
|
||||
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
|
||||
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
|
||||
IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<IQueryExecutionResult<TDestination>> HandleAsync(IDynamicQuery<TSource, TDestination> query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ProcessQueryAsync(query, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicQueryHandler<TSource, TDestination, TParams>
|
||||
: DynamicQueryHandlerBase<TSource, TDestination>,
|
||||
OpenHarbor.CQRS.Abstractions.IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams;
|
||||
|
||||
public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync,
|
||||
IEnumerable<IQueryableProvider<TSource>> queryableProviders,
|
||||
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
|
||||
IEnumerable<IAlterQueryableService<TSource, TDestination, TParams>> alterQueryableServicesWithParams,
|
||||
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
|
||||
IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider)
|
||||
{
|
||||
this.alterQueryableServicesWithParams = alterQueryableServicesWithParams;
|
||||
}
|
||||
|
||||
protected override async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
source = await base.AlterSourceAsync(source, query, cancellationToken);
|
||||
|
||||
if (query is IDynamicQueryParams<TParams> withParams)
|
||||
{
|
||||
foreach (var it in alterQueryableServicesWithParams)
|
||||
source = await it.AlterQueryableAsync(source, withParams, cancellationToken);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public Task<IQueryExecutionResult<TDestination>> HandleAsync(IDynamicQuery<TSource, TDestination, TParams> query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.ProcessQueryAsync(query, cancellationToken);
|
||||
}
|
||||
}
|
92
OpenHarbor.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs
Normal file
92
OpenHarbor.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery;
|
||||
|
||||
public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
private readonly IQueryHandlerAsync _queryHandlerAsync;
|
||||
private readonly IEnumerable<IQueryableProvider<TSource>> _queryableProviders;
|
||||
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination>> _alterQueryableServices;
|
||||
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> _dynamicQueryInterceptorProviders;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync,
|
||||
IEnumerable<IQueryableProvider<TSource>> queryableProviders,
|
||||
IEnumerable<IAlterQueryableService<TSource, TDestination>> alterQueryableServices,
|
||||
IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> dynamicQueryInterceptorProviders,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_queryHandlerAsync = queryHandlerAsync;
|
||||
_queryableProviders = queryableProviders;
|
||||
_alterQueryableServices = alterQueryableServices;
|
||||
_dynamicQueryInterceptorProviders = dynamicQueryInterceptorProviders;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_queryableProviders.Any())
|
||||
return _queryableProviders.ElementAt(0).GetQueryableAsync(query, cancellationToken);
|
||||
|
||||
throw new Exception($"You must provide a QueryableProvider<TSource> for {typeof(TSource).Name}");
|
||||
}
|
||||
|
||||
public virtual IQueryExecutionOptions GetQueryExecutionOptions(IQueryable<TSource> source, IDynamicQuery query)
|
||||
{
|
||||
return new QueryExecutionOptions();
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IQueryInterceptor> GetInterceptors()
|
||||
{
|
||||
var types = _dynamicQueryInterceptorProviders.SelectMany(t => t.GetInterceptorsTypes()).Distinct();
|
||||
foreach (var type in types)
|
||||
yield return _serviceProvider.GetService(type) as IQueryInterceptor;
|
||||
}
|
||||
|
||||
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var source = await GetQueryableAsync(query, cancellationToken);
|
||||
source = await AlterSourceAsync(source, query, cancellationToken);
|
||||
var options = GetQueryExecutionOptions(source, query);
|
||||
var interceptors = GetInterceptors();
|
||||
|
||||
foreach (var interceptor in interceptors)
|
||||
_queryHandlerAsync.AddInterceptor(interceptor);
|
||||
|
||||
var criteria = CreateCriteriaFromQuery(query);
|
||||
var result = await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var t in _alterQueryableServices)
|
||||
source = await t.AlterQueryableAsync(source, query, cancellationToken);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query)
|
||||
{
|
||||
var criteria = new QueryCriteria
|
||||
{
|
||||
Page = query?.GetPage(),
|
||||
PageSize = query?.GetPageSize(),
|
||||
Filters = query?.GetFilters() ?? new List<IFilter>(),
|
||||
Sorts = query?.GetSorts() ?? new List<ISort>(),
|
||||
Groups = query?.GetGroups() ?? new List<IGroup>(),
|
||||
Aggregates = query?.GetAggregates() ?? new List<IAggregate>()
|
||||
};
|
||||
return criteria;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Pluralize.NET" Version="1.0.2" />
|
||||
<PackageReference Include="PoweredSoft.DynamicQuery" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.DynamicQuery.Abstractions\OpenHarbor.CQRS.DynamicQuery.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS\OpenHarbor.CQRS.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
175
OpenHarbor.CQRS.DynamicQuery/ServiceCollectionExtensions.cs
Normal file
175
OpenHarbor.CQRS.DynamicQuery/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
||||
using OpenHarbor.CQRS.DynamicQuery.Discover;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace OpenHarbor.CQRS.DynamicQuery;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddDynamicQuery<TSourceAndDestination>(this IServiceCollection services, string name = null)
|
||||
where TSourceAndDestination : class
|
||||
=> AddDynamicQuery<TSourceAndDestination, TSourceAndDestination>(services, name: name);
|
||||
|
||||
public static IServiceCollection AddDynamicQuery<TSource, TDestination>(this IServiceCollection services, string name = null)
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
// add query handler.
|
||||
services.AddTransient<IQueryHandler<IDynamicQuery<TSource, TDestination>, IQueryExecutionResult<TDestination>>, DynamicQueryHandler<TSource, TDestination>>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var queryType = typeof(IDynamicQuery<TSource, TDestination>);
|
||||
var resultType = typeof(IQueryExecutionResult<TDestination>);
|
||||
var serviceType = typeof(DynamicQueryHandler<TSource, TDestination>);
|
||||
var queryMeta = new DynamicQueryMeta(queryType, serviceType, resultType)
|
||||
{
|
||||
OverridableName = name
|
||||
};
|
||||
|
||||
services.AddSingleton<IQueryMeta>(queryMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryWithProvider<TSource, TQueryableProvider>(this IServiceCollection services, string name = null)
|
||||
where TQueryableProvider : class, IQueryableProvider<TSource>
|
||||
where TSource : class
|
||||
{
|
||||
services.AddTransient<IQueryableProvider<TSource>, TQueryableProvider>()
|
||||
.AddDynamicQuery<TSource>(name: name);
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryWithParamsAndProvider<TSource, TParams, TQueryableProvider>(this IServiceCollection services, string name = null)
|
||||
where TQueryableProvider : class, IQueryableProvider<TSource>
|
||||
where TParams : class
|
||||
where TSource : class
|
||||
{
|
||||
services.AddTransient<IQueryableProvider<TSource>, TQueryableProvider>()
|
||||
.AddDynamicQueryWithParams<TSource, TParams>(name: name);
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryWithParams<TSourceAndDestination, TParams>(this IServiceCollection services, string name = null)
|
||||
where TSourceAndDestination : class
|
||||
where TParams : class
|
||||
=> AddDynamicQueryWithParams<TSourceAndDestination, TSourceAndDestination, TParams>(services, name: name);
|
||||
|
||||
public static IServiceCollection AddDynamicQueryWithParams<TSource, TDestination, TParams>(this IServiceCollection services, string name = null)
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
// add query handler.
|
||||
services.AddTransient<IQueryHandler<IDynamicQuery<TSource, TDestination, TParams>, IQueryExecutionResult<TDestination>>, DynamicQueryHandler<TSource, TDestination, TParams>>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var queryType = typeof(IDynamicQuery<TSource, TDestination, TParams>);
|
||||
var resultType = typeof(IQueryExecutionResult<TDestination>);
|
||||
var serviceType = typeof(DynamicQueryHandler<TSource, TDestination>);
|
||||
var queryMeta = new DynamicQueryMeta(queryType, serviceType, resultType)
|
||||
{
|
||||
|
||||
// params type.
|
||||
ParamsType = typeof(TParams),
|
||||
OverridableName = name
|
||||
};
|
||||
|
||||
services.AddSingleton<IQueryMeta>(queryMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAlterQueryable<TSourceAndDestination, TService>(this IServiceCollection services)
|
||||
where TService : class, IAlterQueryableService<TSourceAndDestination, TSourceAndDestination>
|
||||
{
|
||||
return services.AddTransient<IAlterQueryableService<TSourceAndDestination, TSourceAndDestination>, TService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAlterQueryable<TSource, TDestination, TService>(this IServiceCollection services)
|
||||
where TService : class, IAlterQueryableService<TSource, TDestination>
|
||||
{
|
||||
return services.AddTransient<IAlterQueryableService<TSource, TDestination>, TService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAlterQueryableWithParams<TSourceAndTDestination, TParams, TService>
|
||||
(this IServiceCollection services)
|
||||
where TParams : class
|
||||
where TService : class, IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams>
|
||||
{
|
||||
return services.AddTransient<IAlterQueryableService< TSourceAndTDestination, TSourceAndTDestination, TParams>, TService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAlterQueryableWithParams<TSource, TDestination, TParams, TService>
|
||||
(this IServiceCollection services)
|
||||
where TParams : class
|
||||
where TService : class, IAlterQueryableService<TSource, TDestination, TParams>
|
||||
{
|
||||
return services.AddTransient<IAlterQueryableService<TSource, TDestination, TParams>, TService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryInterceptor<TSource, TDestination, TInterceptor>(this IServiceCollection services)
|
||||
where TInterceptor : class, IQueryInterceptor
|
||||
{
|
||||
services.TryAddTransient<TInterceptor>();
|
||||
return services.AddSingleton<IDynamicQueryInterceptorProvider<TSource, TDestination>>(
|
||||
new DynamicQueryInterceptorProvider<TSource, TDestination>(typeof(TInterceptor)));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryInterceptors<TSource, TDestination, T1, T2>(this IServiceCollection services)
|
||||
where T1 : class, IQueryInterceptor
|
||||
where T2 : class, IQueryInterceptor
|
||||
{
|
||||
services.TryAddTransient<T1>();
|
||||
services.TryAddTransient<T2>();
|
||||
return services.AddSingleton<IDynamicQueryInterceptorProvider<TSource, TDestination>>(
|
||||
new DynamicQueryInterceptorProvider<TSource, TDestination>(typeof(T1), typeof(T2)));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryInterceptors<TSource, TDestination, T1, T2, T3>(this IServiceCollection services)
|
||||
where T1 : class, IQueryInterceptor
|
||||
where T2 : class, IQueryInterceptor
|
||||
where T3 : class, IQueryInterceptor
|
||||
{
|
||||
services.TryAddTransient<T1>();
|
||||
services.TryAddTransient<T2>();
|
||||
services.TryAddTransient<T3>();
|
||||
return services.AddSingleton<IDynamicQueryInterceptorProvider<TSource, TDestination>>(
|
||||
new DynamicQueryInterceptorProvider<TSource, TDestination>(typeof(T1), typeof(T2), typeof(T3)));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryInterceptors<TSource, TDestination, T1, T2, T3, T4>(this IServiceCollection services)
|
||||
where T1 : class, IQueryInterceptor
|
||||
where T2 : class, IQueryInterceptor
|
||||
where T3 : class, IQueryInterceptor
|
||||
where T4 : class, IQueryInterceptor
|
||||
{
|
||||
services.TryAddTransient<T1>();
|
||||
services.TryAddTransient<T2>();
|
||||
services.TryAddTransient<T3>();
|
||||
services.TryAddTransient<T4>();
|
||||
return services.AddSingleton<IDynamicQueryInterceptorProvider<TSource, TDestination>>(
|
||||
new DynamicQueryInterceptorProvider<TSource, TDestination>(typeof(T1), typeof(T2), typeof(T3), typeof(T4)));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDynamicQueryInterceptors<TSource, TDestination, T1, T2, T3, T4, T5>(this IServiceCollection services)
|
||||
where T1 : class, IQueryInterceptor
|
||||
where T2 : class, IQueryInterceptor
|
||||
where T3 : class, IQueryInterceptor
|
||||
where T4 : class, IQueryInterceptor
|
||||
where T5 : class, IQueryInterceptor
|
||||
{
|
||||
services.TryAddTransient<T1>();
|
||||
services.TryAddTransient<T2>();
|
||||
services.TryAddTransient<T3>();
|
||||
services.TryAddTransient<T4>();
|
||||
services.TryAddTransient<T5>();
|
||||
return services.AddSingleton<IDynamicQueryInterceptorProvider<TSource, TDestination>>(
|
||||
new DynamicQueryInterceptorProvider<TSource, TDestination>(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="10.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS\OpenHarbor.CQRS.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,43 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenHarbor.CQRS.Abstractions;
|
||||
|
||||
namespace OpenHarbor.CQRS.FluentValidation;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
private static IServiceCollection AddFluentValidator<T, TValidator>(this IServiceCollection services)
|
||||
where TValidator : class, IValidator<T>
|
||||
{
|
||||
services.AddTransient<IValidator<T>, TValidator>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandHandler, TValidator>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
where TValidator : class, IValidator<TCommand>
|
||||
{
|
||||
return services.AddCommand<TCommand, TCommandHandler>()
|
||||
.AddFluentValidator<TCommand, TValidator>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandResult, TCommandHandler, TValidator>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TCommandResult>
|
||||
where TValidator : class, IValidator<TCommand>
|
||||
{
|
||||
return services.AddCommand<TCommand, TCommandResult, TCommandHandler>()
|
||||
.AddFluentValidator<TCommand, TValidator>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddQuery<TQuery, TQueryResult, TQueryHandler, TValidator>(this IServiceCollection services)
|
||||
where TQuery : class
|
||||
where TQueryHandler : class, IQueryHandler<TQuery, TQueryResult>
|
||||
where TValidator : class, IValidator<TQuery>
|
||||
{
|
||||
services.AddQuery<TQuery, TQueryResult, TQueryHandler>()
|
||||
.AddFluentValidator<TQuery, TValidator>();
|
||||
return services;
|
||||
}
|
||||
}
|
@ -3,13 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30907.101
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.Abstractions", "PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj", "{ED78E19D-31D4-4783-AE9E-2844A8541277}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.Abstractions", "OpenHarbor.CQRS.Abstractions\OpenHarbor.CQRS.Abstractions.csproj", "{ED78E19D-31D4-4783-AE9E-2844A8541277}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS", "PoweredSoft.CQRS\PoweredSoft.CQRS.csproj", "{7069B98F-8736-4114-8AF5-1ACE094E6238}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS", "OpenHarbor.CQRS\OpenHarbor.CQRS.csproj", "{7069B98F-8736-4114-8AF5-1ACE094E6238}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.AspNetCore", "PoweredSoft.CQRS.AspNetCore\PoweredSoft.CQRS.AspNetCore.csproj", "{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.AspNetCore", "OpenHarbor.CQRS.AspNetCore\OpenHarbor.CQRS.AspNetCore.csproj", "{A1D577E5-61BD-4E25-B2C8-1005C1D7665B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.AspNetCore.Abstractions", "PoweredSoft.CQRS.AspNetCore.Abstractions\PoweredSoft.CQRS.AspNetCore.Abstractions.csproj", "{4C466827-31D3-4081-A751-C2FC7C381D7E}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.AspNetCore.Abstractions", "OpenHarbor.CQRS.AspNetCore.Abstractions\OpenHarbor.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
|
||||
@ -17,13 +17,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.DynamicQuery", "PoweredSoft.CQRS.DynamicQuery\PoweredSoft.CQRS.DynamicQuery.csproj", "{A38CE930-191F-417C-B5BE-8CC62DB47513}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.DynamicQuery", "OpenHarbor.CQRS.DynamicQuery\OpenHarbor.CQRS.DynamicQuery.csproj", "{A38CE930-191F-417C-B5BE-8CC62DB47513}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.DynamicQuery.Abstractions", "PoweredSoft.CQRS.DynamicQuery.Abstractions\PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj", "{60B5E255-77B8-48E0-AE8F-04E8332970F9}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.DynamicQuery.AspNetCore", "OpenHarbor.CQRS.DynamicQuery.AspNetCore\OpenHarbor.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.DynamicQuery.AspNetCore", "PoweredSoft.CQRS.DynamicQuery.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenHarbor.CQRS.FluentValidation", "OpenHarbor.CQRS.FluentValidation\OpenHarbor.CQRS.FluentValidation.csproj", "{70BD37C4-7497-474D-9A40-A701203971D8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.CQRS.FluentValidation", "PoweredSoft.CQRS.FluentValidation\PoweredSoft.CQRS.FluentValidation.csproj", "{70BD37C4-7497-474D-9A40-A701203971D8}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenHarbor.CQRS.DynamicQuery.Abstractions", "OpenHarbor.CQRS.DynamicQuery.Abstractions\OpenHarbor.CQRS.DynamicQuery.Abstractions.csproj", "{8B9F8ACE-10EA-4215-9776-DE29EC93B020}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -51,10 +51,6 @@ Global
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A38CE930-191F-417C-B5BE-8CC62DB47513}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{60B5E255-77B8-48E0-AE8F-04E8332970F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{60B5E255-77B8-48E0-AE8F-04E8332970F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{60B5E255-77B8-48E0-AE8F-04E8332970F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{60B5E255-77B8-48E0-AE8F-04E8332970F9}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -63,6 +59,10 @@ Global
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{70BD37C4-7497-474D-9A40-A701203971D8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8B9F8ACE-10EA-4215-9776-DE29EC93B020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B9F8ACE-10EA-4215-9776-DE29EC93B020}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B9F8ACE-10EA-4215-9776-DE29EC93B020}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B9F8ACE-10EA-4215-9776-DE29EC93B020}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
22
OpenHarbor.CQRS/Discovery/CommandDiscovery.cs
Normal file
22
OpenHarbor.CQRS/Discovery/CommandDiscovery.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace OpenHarbor.CQRS.Discovery;
|
||||
|
||||
public sealed class CommandDiscovery : ICommandDiscovery
|
||||
{
|
||||
private readonly IEnumerable<ICommandMeta> _commandMetas;
|
||||
|
||||
public CommandDiscovery(IEnumerable<ICommandMeta> commandMetas)
|
||||
{
|
||||
_commandMetas = commandMetas;
|
||||
}
|
||||
|
||||
public IEnumerable<ICommandMeta> GetCommands() => _commandMetas;
|
||||
public ICommandMeta FindCommand(string name) => _commandMetas.FirstOrDefault(t => t.Name == name);
|
||||
public ICommandMeta FindCommand(Type commandType) => _commandMetas.FirstOrDefault(t => t.CommandType == commandType);
|
||||
public bool CommandExists(string name) => _commandMetas.Any(t => t.Name == name);
|
||||
public bool CommandExists(Type commandType) => _commandMetas.Any(t => t.CommandType == commandType);
|
||||
}
|
23
OpenHarbor.CQRS/Discovery/QueryDiscovery.cs
Normal file
23
OpenHarbor.CQRS/Discovery/QueryDiscovery.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace OpenHarbor.CQRS.Discovery;
|
||||
|
||||
public sealed class QueryDiscovery : IQueryDiscovery
|
||||
{
|
||||
private readonly IEnumerable<IQueryMeta> _queryMetas;
|
||||
|
||||
public QueryDiscovery(IEnumerable<IQueryMeta> queryMetas)
|
||||
{
|
||||
_queryMetas = queryMetas;
|
||||
}
|
||||
|
||||
public IEnumerable<IQueryMeta> GetQueries() => _queryMetas;
|
||||
public IQueryMeta FindQuery(string name) => _queryMetas.FirstOrDefault(t => t.Name == name);
|
||||
public IQueryMeta FindQuery(Type queryType) => _queryMetas.FirstOrDefault(t => t.QueryType == queryType);
|
||||
public bool QueryExists(string name) => _queryMetas.Any(t => t.Name == name);
|
||||
public bool QueryExists(Type queryType) => _queryMetas.Any(t => t.QueryType == queryType);
|
||||
}
|
||||
|
12
OpenHarbor.CQRS/OpenHarbor.CQRS.csproj
Normal file
12
OpenHarbor.CQRS/OpenHarbor.CQRS.csproj
Normal file
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<PackageIconUrl>https://avatars.githubusercontent.com/u/52874619?v=4</PackageIconUrl>
|
||||
<Authors>David Lebee, Mathias Beaulieu-Duncan</Authors>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenHarbor.CQRS.Abstractions\OpenHarbor.CQRS.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
21
OpenHarbor.CQRS/ServiceCollectionExtensions.cs
Normal file
21
OpenHarbor.CQRS/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using OpenHarbor.CQRS.Abstractions.Discovery;
|
||||
using OpenHarbor.CQRS.Discovery;
|
||||
|
||||
namespace OpenHarbor.CQRS;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddDefaultQueryDiscovery(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddTransient<IQueryDiscovery, QueryDiscovery>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDefaultCommandDiscovery(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddTransient<ICommandDiscovery, CommandDiscovery>();
|
||||
return services;
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CommandNameAttribute : Attribute
|
||||
{
|
||||
public CommandNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class QueryNameAttribute : Attribute
|
||||
{
|
||||
public QueryNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
using PoweredSoft.CQRS.Abstractions.Attributes;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Discovery
|
||||
{
|
||||
public class CommandMeta : ICommandMeta
|
||||
{
|
||||
public CommandMeta(Type commandType, Type serviceType, Type commandResultType)
|
||||
{
|
||||
CommandType = commandType;
|
||||
ServiceType = serviceType;
|
||||
CommandResultType = commandResultType;
|
||||
}
|
||||
|
||||
public CommandMeta(Type commandType, Type serviceType)
|
||||
{
|
||||
CommandType = commandType;
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
protected virtual CommandNameAttribute NameAttribute => CommandType.GetCustomAttribute<CommandNameAttribute>();
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
var name = NameAttribute?.Name ?? CommandType.Name.Replace("Command", string.Empty);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Type CommandType { get; }
|
||||
public virtual Type ServiceType { get; }
|
||||
public virtual Type CommandResultType { get; }
|
||||
|
||||
public string LowerCamelCaseName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return Name;
|
||||
|
||||
var name = Name;
|
||||
var firstLetter = Char.ToLowerInvariant(name[0]);
|
||||
var ret = $"{firstLetter}{name.Substring(1)}";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Discovery
|
||||
{
|
||||
public interface ICommandMeta
|
||||
{
|
||||
string Name { get; }
|
||||
Type CommandType { get; }
|
||||
Type ServiceType { get; }
|
||||
Type CommandResultType { get; }
|
||||
string LowerCamelCaseName { get; }
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Discovery
|
||||
{
|
||||
public interface IQueryDiscovery
|
||||
{
|
||||
IQueryMeta FindQuery(string name);
|
||||
IQueryMeta FindQuery(Type queryType);
|
||||
IEnumerable<IQueryMeta> GetQueries();
|
||||
bool QueryExists(string name);
|
||||
bool QueryExists(Type queryType);
|
||||
}
|
||||
|
||||
public interface ICommandDiscovery
|
||||
{
|
||||
bool CommandExists(string name);
|
||||
bool CommandExists(Type commandType);
|
||||
ICommandMeta FindCommand(string name);
|
||||
ICommandMeta FindCommand(Type commandType);
|
||||
IEnumerable<ICommandMeta> GetCommands();
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Discovery
|
||||
{
|
||||
public interface IQueryMeta
|
||||
{
|
||||
string Name { get; }
|
||||
Type QueryType { get; }
|
||||
Type ServiceType { get; }
|
||||
Type QueryResultType { get; }
|
||||
string Category { get; }
|
||||
string LowerCamelCaseName { get; }
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
using PoweredSoft.CQRS.Abstractions.Attributes;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Discovery
|
||||
{
|
||||
public class QueryMeta : IQueryMeta
|
||||
{
|
||||
public QueryMeta(Type queryType, Type serviceType, Type queryResultType)
|
||||
{
|
||||
QueryType = queryType;
|
||||
ServiceType = serviceType;
|
||||
QueryResultType = queryResultType;
|
||||
}
|
||||
|
||||
protected virtual QueryNameAttribute NameAttribute => QueryType.GetCustomAttribute<QueryNameAttribute>();
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
var name = NameAttribute?.Name ?? QueryType.Name.Replace("Query", string.Empty);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Type QueryType { get; }
|
||||
public virtual Type ServiceType { get; }
|
||||
public virtual Type QueryResultType { get; }
|
||||
public virtual string Category => "BasicQuery";
|
||||
|
||||
public string LowerCamelCaseName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return Name;
|
||||
|
||||
var name = Name;
|
||||
var firstLetter = Char.ToLowerInvariant(name[0]);
|
||||
var ret = $"{firstLetter}{name.Substring(1)}";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions
|
||||
{
|
||||
public interface ICommandHandler<in TCommand>
|
||||
where TCommand : class
|
||||
{
|
||||
Task HandleAsync(TCommand command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ICommandHandler<in TCommand, TCommandResult>
|
||||
where TCommand : class
|
||||
{
|
||||
Task<TCommandResult> HandleAsync(TCommand command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions
|
||||
{
|
||||
public interface IQueryHandler<in TQuery, TQueryResult>
|
||||
where TQuery : class
|
||||
{
|
||||
Task<TQueryResult> HandleAsync(TQuery query, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace PoweredSoft.CQRS.Abstractions.Security
|
||||
{
|
||||
public enum AuthorizationResult
|
||||
{
|
||||
Unauthorized,
|
||||
Forbidden,
|
||||
Allowed
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Security
|
||||
{
|
||||
public interface ICommandAuthorizationService
|
||||
{
|
||||
Task<AuthorizationResult> IsAllowedAsync(Type commandType, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions.Security
|
||||
{
|
||||
|
||||
public interface IQueryAuthorizationService
|
||||
{
|
||||
Task<AuthorizationResult> IsAllowedAsync(Type queryType, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||
|
||||
namespace PoweredSoft.CQRS.Abstractions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddQuery<TQuery, TQueryResult, TQueryHandler>(this IServiceCollection services)
|
||||
where TQuery : class
|
||||
where TQueryHandler : class, IQueryHandler<TQuery, TQueryResult>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<IQueryHandler<TQuery, TQueryResult>, TQueryHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var queryMeta = new QueryMeta(typeof(TQuery), typeof(IQueryHandler<TQuery, TQueryResult>), typeof(TQueryResult));
|
||||
services.AddSingleton<IQueryMeta>(queryMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandResult, TCommandHandler>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TCommandResult>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<ICommandHandler<TCommand, TCommandResult>, TCommandHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var commandMeta = new CommandMeta(typeof(TCommand), typeof(ICommandHandler<TCommand>), typeof(TCommandResult));
|
||||
services.AddSingleton<ICommandMeta>(commandMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommand<TCommand, TCommandHandler>(this IServiceCollection services)
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
{
|
||||
// add handler to DI.
|
||||
services.AddTransient<ICommandHandler<TCommand>, TCommandHandler>();
|
||||
|
||||
// add for discovery purposes.
|
||||
var commandMeta = new CommandMeta(typeof(TCommand), typeof(ICommandHandler<TCommand>));
|
||||
services.AddSingleton<ICommandMeta>(commandMeta);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CommandControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class QueryControllerIgnoreAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Copyright>Powered Softwares Inc.</Copyright>
|
||||
<PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl>
|
||||
<Company>PoweredSoft</Company>
|
||||
<Authors>PoweredSoft Team</Authors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,39 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PoweredSoft.CQRS.Abstractions;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/command/[controller]")]
|
||||
public class CommandController<TCommand> : Controller
|
||||
where TCommand : class
|
||||
{
|
||||
[HttpPost, CommandControllerAuthorization]
|
||||
public async Task<IActionResult> Handle([FromServices] ICommandHandler<TCommand> handler,
|
||||
[FromBody] TCommand command)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
await handler.HandleAsync(command, this.Request.HttpContext.RequestAborted);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/command/[controller]")]
|
||||
public class CommandController<TCommand, TTCommandResult> : Controller
|
||||
where TCommand : class
|
||||
{
|
||||
[HttpPost, CommandControllerAuthorization]
|
||||
public async Task<ActionResult<TTCommandResult>> Handle([FromServices] ICommandHandler<TCommand, TTCommandResult> handler,
|
||||
[FromBody] TCommand command)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
return Ok(await handler.HandleAsync(command, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
using PoweredSoft.CQRS.Abstractions.Security;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class CommandControllerAsyncAuthorizationFilter : IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly ICommandAuthorizationService authorizationService;
|
||||
|
||||
public CommandControllerAsyncAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
authorizationService = serviceProvider.GetService<ICommandAuthorizationService>();
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (authorizationService == null)
|
||||
return;
|
||||
|
||||
var action = context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;
|
||||
if (action == null)
|
||||
throw new Exception("Only Supports controller action descriptor");
|
||||
|
||||
var attribute = action.MethodInfo.GetCustomAttribute<CommandControllerAuthorizationAttribute>();
|
||||
Type commandType;
|
||||
if (attribute?.CommandType != null)
|
||||
commandType = attribute.CommandType;
|
||||
else
|
||||
commandType = action.ControllerTypeInfo.GenericTypeArguments.First();
|
||||
|
||||
var ar = await authorizationService.IsAllowedAsync(commandType);
|
||||
if (ar == AuthorizationResult.Forbidden)
|
||||
context.Result = new StatusCodeResult(403);
|
||||
else if(ar == AuthorizationResult.Unauthorized)
|
||||
context.Result = new StatusCodeResult(401);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class CommandControllerAuthorizationAttribute : TypeFilterAttribute
|
||||
{
|
||||
public CommandControllerAuthorizationAttribute() : base(typeof(CommandControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CommandControllerAuthorizationAttribute(Type commandType) : base(typeof(CommandControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
CommandType = commandType;
|
||||
}
|
||||
|
||||
public Type CommandType { get; } = null;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class CommandControllerConvention : IControllerModelConvention
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public CommandControllerConvention(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Apply(ControllerModel controller)
|
||||
{
|
||||
if (!controller.ControllerType.IsGenericType)
|
||||
return;
|
||||
|
||||
if (!controller.ControllerType.Name.Contains("CommandController"))
|
||||
return;
|
||||
|
||||
if (controller.ControllerType.Assembly != typeof(CommandControllerConvention).Assembly)
|
||||
return;
|
||||
|
||||
var genericType = controller.ControllerType.GenericTypeArguments[0];
|
||||
var commandDiscovery = this.serviceProvider.GetRequiredService<ICommandDiscovery>();
|
||||
var command = commandDiscovery.FindCommand(genericType);
|
||||
controller.ControllerName = command.LowerCamelCaseName;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||
using PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class CommandControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
private readonly ServiceProvider serviceProvider;
|
||||
|
||||
public CommandControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
|
||||
{
|
||||
var commandDiscovery = this.serviceProvider.GetRequiredService<ICommandDiscovery>();
|
||||
foreach (var f in commandDiscovery.GetCommands())
|
||||
{
|
||||
var ignoreAttribute = f.CommandType.GetCustomAttribute<CommandControllerIgnoreAttribute>();
|
||||
if (ignoreAttribute != null)
|
||||
continue;
|
||||
|
||||
if (f.CommandResultType == null)
|
||||
{
|
||||
var controllerType = typeof(CommandController<>).MakeGenericType(f.CommandType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var controllerType = typeof(CommandController<,>).MakeGenericType(f.CommandType, f.CommandResultType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class CommandControllerOptions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PoweredSoft.CQRS.Abstractions;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class QueryController<TQuery, TQueryResult> : Controller
|
||||
where TQuery : class
|
||||
{
|
||||
[HttpPost, QueryControllerAuthorization]
|
||||
public async Task<ActionResult<TQueryResult>> Handle([FromServices] IQueryHandler<TQuery, TQueryResult> handler,
|
||||
[FromBody] TQuery query)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
|
||||
return Ok(await handler.HandleAsync(query, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
|
||||
[HttpGet, QueryControllerAuthorization]
|
||||
public async Task<ActionResult<TQueryResult>> HandleGet([FromServices] IQueryHandler<TQuery, TQueryResult> handler,
|
||||
[FromQuery] TQuery query)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
|
||||
return Ok(await handler.HandleAsync(query, this.Request.HttpContext.RequestAborted));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
using PoweredSoft.CQRS.Abstractions.Security;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class QueryControllerAsyncAuthorizationFilter : IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly IQueryAuthorizationService authorizationService;
|
||||
|
||||
public QueryControllerAsyncAuthorizationFilter(IServiceProvider serviceProvider)
|
||||
{
|
||||
authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();
|
||||
}
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (authorizationService == null)
|
||||
return;
|
||||
|
||||
var action = context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;
|
||||
if (action == null)
|
||||
throw new Exception("Only Supports controller action descriptor");
|
||||
|
||||
var attribute = action.MethodInfo.GetCustomAttribute<QueryControllerAuthorizationAttribute>();
|
||||
Type queryType;
|
||||
if (attribute?.QueryType != null)
|
||||
queryType = attribute.QueryType;
|
||||
else
|
||||
queryType = action.ControllerTypeInfo.GenericTypeArguments.First();
|
||||
|
||||
var ar = await authorizationService.IsAllowedAsync(queryType);
|
||||
if (ar == AuthorizationResult.Forbidden)
|
||||
context.Result = new StatusCodeResult(403);
|
||||
else if (ar == AuthorizationResult.Unauthorized)
|
||||
context.Result = new StatusCodeResult(401);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class QueryControllerAuthorizationAttribute : TypeFilterAttribute
|
||||
{
|
||||
public QueryControllerAuthorizationAttribute() : base(typeof(QueryControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public QueryControllerAuthorizationAttribute(Type queryType) : base(typeof(QueryControllerAsyncAuthorizationFilter))
|
||||
{
|
||||
QueryType = queryType;
|
||||
}
|
||||
|
||||
public Type QueryType { get; } = null;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class QueryControllerConvention : IControllerModelConvention
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public QueryControllerConvention(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Apply(ControllerModel controller)
|
||||
{
|
||||
if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("QueryController") && controller.ControllerType.Assembly == typeof(QueryControllerConvention).Assembly)
|
||||
{
|
||||
var genericType = controller.ControllerType.GenericTypeArguments[0];
|
||||
var queryDiscovery = this.serviceProvider.GetRequiredService<IQueryDiscovery>();
|
||||
var query = queryDiscovery.FindQuery(genericType);
|
||||
controller.ControllerName = query.LowerCamelCaseName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||
using PoweredSoft.CQRS.AspNetCore.Abstractions.Attributes;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class QueryControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
|
||||
{
|
||||
private readonly ServiceProvider serviceProvider;
|
||||
|
||||
public QueryControllerFeatureProvider(ServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
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 != "BasicQuery")
|
||||
continue;
|
||||
|
||||
var controllerType = typeof(QueryController<,>).MakeGenericType(f.QueryType, f.QueryResultType);
|
||||
var controllerTypeInfo = controllerType.GetTypeInfo();
|
||||
feature.Controllers.Add(controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public class QueryControllerOptions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace PoweredSoft.CQRS.AspNetCore.Mvc
|
||||
{
|
||||
public static class MvcBuilderExtensions
|
||||
{
|
||||
public static IMvcBuilder AddPoweredSoftQueries(this IMvcBuilder builder, Action<QueryControllerOptions> configuration = null)
|
||||
{
|
||||
var options = new QueryControllerOptions();
|
||||
configuration?.Invoke(options);
|
||||
var services = builder.Services;
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
builder.AddMvcOptions(o => o.Conventions.Add(new QueryControllerConvention(serviceProvider)));
|
||||
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new QueryControllerFeatureProvider(serviceProvider)));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IMvcBuilder AddPoweredSoftCommands(this IMvcBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
builder.AddMvcOptions(o => o.Conventions.Add(new CommandControllerConvention(serviceProvider)));
|
||||
builder.ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new CommandControllerFeatureProvider(serviceProvider)));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Copyright>Powered Softwares Inc.</Copyright>
|
||||
<PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl>
|
||||
<Company>PoweredSoft</Company>
|
||||
<Authors>PoweredSoft Team</Authors>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.Abstractions\PoweredSoft.CQRS.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\PoweredSoft.CQRS.AspNetCore.Abstractions\PoweredSoft.CQRS.AspNetCore.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public class DynamicQueryInterceptorProvider<TSource, TDestination> : IDynamicQueryInterceptorProvider<TSource, TDestination>
|
||||
{
|
||||
private readonly Type[] types;
|
||||
|
||||
public DynamicQueryInterceptorProvider(params Type[] types)
|
||||
{
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetInterceptorsTypes()
|
||||
{
|
||||
return types;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public interface IAlterQueryableService<TSource, TDestination>
|
||||
{
|
||||
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQuery dynamicQuery, CancellationToken cancellationToken = default);
|
||||
}
|
||||
public interface IAlterQueryableService<TSource, TDestination, TParams>
|
||||
where TParams : class
|
||||
{
|
||||
Task<IQueryable<TSource>> AlterQueryableAsync(IQueryable<TSource> query, IDynamicQueryParams<TParams> dynamicQuery, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public interface IDynamicQuery<TSource, TDestination> : IDynamicQuery
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IDynamicQuery<TSource, TDestination, out TParams> : IDynamicQuery<TSource, TDestination>, IDynamicQueryParams<TParams>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IDynamicQuery
|
||||
{
|
||||
List<IFilter> GetFilters();
|
||||
List<IGroup> GetGroups();
|
||||
List<ISort> GetSorts();
|
||||
List<IAggregate> GetAggregates();
|
||||
int? GetPage();
|
||||
int? GetPageSize();
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public interface IDynamicQueryInterceptorProvider<TSource, TDestination>
|
||||
{
|
||||
IEnumerable<Type> GetInterceptorsTypes();
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public interface IDynamicQueryParams<out TParams>
|
||||
where TParams : class
|
||||
{
|
||||
TParams GetParams();
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.Abstractions
|
||||
{
|
||||
public interface IQueryableProvider<TSource>
|
||||
{
|
||||
Task<IQueryable<TSource>> GetQueryableAsync(object query, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Copyright>Powered Softwares Inc.</Copyright>
|
||||
<PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl>
|
||||
<Company>PoweredSoft</Company>
|
||||
<Authors>PoweredSoft Team</Authors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PoweredSoft.DynamicQuery.Core" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,69 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PoweredSoft.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore
|
||||
{
|
||||
public class DynamicQuery<TSource, TDestination> : DynamicQuery, IDynamicQuery<TSource, TDestination>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class DynamicQuery<TSource, TDestination, TParams> : DynamicQuery, IDynamicQuery<TSource, TDestination, TParams>
|
||||
where TSource : class
|
||||
where TDestination : class
|
||||
where TParams : class
|
||||
{
|
||||
public TParams Params { get; set; }
|
||||
|
||||
public TParams GetParams()
|
||||
{
|
||||
return Params;
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicQuery : IDynamicQuery
|
||||
{
|
||||
public int? Page { get; set; }
|
||||
public int? PageSize { get; set; }
|
||||
public List<Sort> Sorts { get; set; }
|
||||
public List<DynamicQueryAggregate> Aggregates { get; set; }
|
||||
public List<Group> Groups { get; set; }
|
||||
public List<DynamicQueryFilter> Filters { get; set; }
|
||||
|
||||
|
||||
public List<IAggregate> GetAggregates()
|
||||
{
|
||||
return Aggregates?.Select(t => t.ToAggregate())?.ToList();//.AsEnumerable<IAggregate>()?.ToList();
|
||||
}
|
||||
|
||||
public List<IFilter> GetFilters()
|
||||
{
|
||||
return Filters?.Select(t => t.ToFilter())?.ToList();
|
||||
}
|
||||
|
||||
public List<IGroup> GetGroups()
|
||||
{
|
||||
return this.Groups?.AsEnumerable<IGroup>()?.ToList();
|
||||
}
|
||||
|
||||
public int? GetPage()
|
||||
{
|
||||
return this.Page;
|
||||
}
|
||||
|
||||
public int? GetPageSize()
|
||||
{
|
||||
return this.PageSize;
|
||||
}
|
||||
|
||||
public List<ISort> GetSorts()
|
||||
{
|
||||
return this.Sorts?.AsEnumerable<ISort>()?.ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore
|
||||
{
|
||||
public class DynamicQueryAggregate
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public IAggregate ToAggregate()
|
||||
{
|
||||
return new Aggregate
|
||||
{
|
||||
Type = Enum.Parse<AggregateType>(Type),
|
||||
Path = Path
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
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 PoweredSoft.CQRS.DynamicQuery.AspNetCore
|
||||
{
|
||||
public class DynamicQueryFilter
|
||||
{
|
||||
public List<DynamicQueryFilter> Filters { get; set; }
|
||||
public bool? And { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool? Not { get; set; }
|
||||
public string Path { get; set; }
|
||||
public object Value { get; set; }
|
||||
|
||||
[FromQuery(Name ="value")]
|
||||
public string QueryValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool? CaseInsensitive { get; set; }
|
||||
|
||||
public IFilter ToFilter()
|
||||
{
|
||||
var type = Enum.Parse<FilterType>(Type);
|
||||
if (type == FilterType.Composite)
|
||||
{
|
||||
var compositeFilter = new CompositeFilter
|
||||
{
|
||||
And = And,
|
||||
Type = FilterType.Composite,
|
||||
Filters = Filters?.Select(t => t.ToFilter())?.ToList() ?? new List<IFilter>()
|
||||
};
|
||||
return compositeFilter;
|
||||
}
|
||||
|
||||
object value = Value;
|
||||
if (Value is JsonElement jsonElement)
|
||||
{
|
||||
switch (jsonElement.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
value = jsonElement.ToString();
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
if (jsonElement.ToString().Contains('.'))
|
||||
value = jsonElement.GetDecimal();
|
||||
else if (jsonElement.TryGetInt64(out var convertedValue))
|
||||
value = convertedValue;
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
value = true;
|
||||
break;
|
||||
case JsonValueKind.False:
|
||||
value = false;
|
||||
break;
|
||||
// TODO: Array support
|
||||
default:
|
||||
value = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var simpleFilter = new SimpleFilter
|
||||
{
|
||||
And = And,
|
||||
Type = type,
|
||||
Not = Not,
|
||||
Path = Path,
|
||||
Value = value,
|
||||
CaseInsensitive = CaseInsensitive,
|
||||
};
|
||||
return simpleFilter;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PoweredSoft.CQRS.Abstractions;
|
||||
using PoweredSoft.CQRS.AspNetCore.Mvc;
|
||||
using PoweredSoft.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc
|
||||
{
|
||||
[ApiController, Route("api/query/[controller]")]
|
||||
public class DynamicQueryController<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<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;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user