diff --git a/Demo/AsyncProvider/InMemoryQueryableHandler.cs b/Demo/AsyncProvider/InMemoryQueryableHandler.cs new file mode 100644 index 0000000..0382f50 --- /dev/null +++ b/Demo/AsyncProvider/InMemoryQueryableHandler.cs @@ -0,0 +1,54 @@ +using PoweredSoft.Data.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Demo.AsyncProvider +{ + public class InMemoryQueryableHandler : IAsyncQueryableHandlerService + { + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.Any(predicate)); + } + + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.Any()); + } + + public bool CanHandle(IQueryable queryable) + { + var result = queryable is EnumerableQuery; + return result; + } + + public Task CountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.Count()); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.FirstOrDefault()); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.FirstOrDefault(predicate)); + } + + public Task LongCountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.LongCount()); + } + + public Task> ToListAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return Task.FromResult(queryable.ToList()); + } + } +} diff --git a/Demo/Demo.csproj b/Demo/Demo.csproj index 3403430..b558cd1 100644 --- a/Demo/Demo.csproj +++ b/Demo/Demo.csproj @@ -6,6 +6,7 @@ + @@ -13,6 +14,9 @@ + + + diff --git a/Demo/DynamicQueries/Contact.cs b/Demo/DynamicQueries/Contact.cs new file mode 100644 index 0000000..0ef525b --- /dev/null +++ b/Demo/DynamicQueries/Contact.cs @@ -0,0 +1,29 @@ +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Demo.DynamicQueries +{ + public class Contact + { + public long Id { get; set; } + public string DisplayName { get; set; } + } + + public class ContactQueryableProvider : IQueryableProvider + { + public Task> GetQueryableAsync(object query, CancellationToken cancelllationToken = default) + { + var ret = new List + { + new Contact { Id = 1, DisplayName = "David L"}, + new Contact { Id = 2, DisplayName = "John Doe"} + }; + + return Task.FromResult(ret.AsQueryable()); + } + } +} diff --git a/Demo/Startup.cs b/Demo/Startup.cs index 2753298..8e49e83 100644 --- a/Demo/Startup.cs +++ b/Demo/Startup.cs @@ -1,4 +1,6 @@ +using Demo.AsyncProvider; using Demo.Commands; +using Demo.DynamicQueries; using Demo.Queries; using FluentValidation; using FluentValidation.AspNetCore; @@ -13,6 +15,12 @@ using Microsoft.Extensions.Logging; using PoweredSoft.CQRS; using PoweredSoft.CQRS.Abstractions; using PoweredSoft.CQRS.AspNetCore.Mvc; +using PoweredSoft.CQRS.DynamicQuery; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.CQRS.DynamicQuery.AspNetCore; +using PoweredSoft.Data; +using PoweredSoft.Data.Core; +using PoweredSoft.DynamicQuery; using System; using System.Collections.Generic; using System.Linq; @@ -33,18 +41,30 @@ namespace Demo public void ConfigureServices(IServiceCollection services) { AddQueries(services); + AddDynamicQueries(services); AddCommands(services); + services.AddTransient(); + services.AddPoweredSoftDataServices(); + services.AddPoweredSoftDynamicQuery(); + services.AddPoweredSoftCQRS(); services .AddControllers() .AddPoweredSoftQueries() .AddPoweredSoftCommands() + .AddPoweredSoftDynamicQueries() .AddFluentValidation(); services.AddSwaggerGen(); } + private void AddDynamicQueries(IServiceCollection services) + { + services.AddTransient, ContactQueryableProvider>(); + services.AddDynamicQuery(); + } + private void AddCommands(IServiceCollection services) { services.AddCommand(); diff --git a/PoweredSoft.CQRS.Abstractions/Discovery/IQueryMeta.cs b/PoweredSoft.CQRS.Abstractions/Discovery/IQueryMeta.cs index 8bd01a3..f200d55 100644 --- a/PoweredSoft.CQRS.Abstractions/Discovery/IQueryMeta.cs +++ b/PoweredSoft.CQRS.Abstractions/Discovery/IQueryMeta.cs @@ -11,5 +11,6 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery Type QueryType { get; } Type ServiceType { get; } Type QueryResultType { get; } + string Category { get; } } } diff --git a/PoweredSoft.CQRS.Abstractions/Discovery/QueryMeta.cs b/PoweredSoft.CQRS.Abstractions/Discovery/QueryMeta.cs index c3a347f..a8dd58f 100644 --- a/PoweredSoft.CQRS.Abstractions/Discovery/QueryMeta.cs +++ b/PoweredSoft.CQRS.Abstractions/Discovery/QueryMeta.cs @@ -27,5 +27,6 @@ namespace PoweredSoft.CQRS.Abstractions.Discovery public virtual Type QueryType { get; } public virtual Type ServiceType { get; } public virtual Type QueryResultType { get; } + public virtual string Category => "BasicQuery"; } } diff --git a/PoweredSoft.CQRS.AspNetCore/Mvc/CommandControllerConvention.cs b/PoweredSoft.CQRS.AspNetCore/Mvc/CommandControllerConvention.cs index b3b740c..4962115 100644 --- a/PoweredSoft.CQRS.AspNetCore/Mvc/CommandControllerConvention.cs +++ b/PoweredSoft.CQRS.AspNetCore/Mvc/CommandControllerConvention.cs @@ -5,23 +5,23 @@ using System; namespace PoweredSoft.CQRS.AspNetCore.Mvc { - public class QueryControllerConvention : IControllerModelConvention + public class CommandControllerConvention : IControllerModelConvention { private readonly IServiceProvider serviceProvider; - public QueryControllerConvention(IServiceProvider serviceProvider) + public CommandControllerConvention(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) + if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("CommandController") && controller.ControllerType.Assembly == typeof(CommandControllerConvention).Assembly) { var genericType = controller.ControllerType.GenericTypeArguments[0]; - var queryDiscovery = this.serviceProvider.GetRequiredService(); - var query = queryDiscovery.FindQuery(genericType); - controller.ControllerName = query.Name; + var commandDiscovery = this.serviceProvider.GetRequiredService(); + var command = commandDiscovery.FindCommand(genericType); + controller.ControllerName = command.Name; } } } diff --git a/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs b/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs index 4962115..b3b740c 100644 --- a/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs +++ b/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerConvention.cs @@ -5,23 +5,23 @@ using System; namespace PoweredSoft.CQRS.AspNetCore.Mvc { - public class CommandControllerConvention : IControllerModelConvention + public class QueryControllerConvention : IControllerModelConvention { private readonly IServiceProvider serviceProvider; - public CommandControllerConvention(IServiceProvider serviceProvider) + public QueryControllerConvention(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public void Apply(ControllerModel controller) { - if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("CommandController") && controller.ControllerType.Assembly == typeof(CommandControllerConvention).Assembly) + if (controller.ControllerType.IsGenericType && controller.ControllerType.Name.Contains("QueryController") && controller.ControllerType.Assembly == typeof(QueryControllerConvention).Assembly) { var genericType = controller.ControllerType.GenericTypeArguments[0]; - var commandDiscovery = this.serviceProvider.GetRequiredService(); - var command = commandDiscovery.FindCommand(genericType); - controller.ControllerName = command.Name; + var queryDiscovery = this.serviceProvider.GetRequiredService(); + var query = queryDiscovery.FindQuery(genericType); + controller.ControllerName = query.Name; } } } diff --git a/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerFeatureProvider.cs b/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerFeatureProvider.cs index d3f520a..b1d9f69 100644 --- a/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerFeatureProvider.cs +++ b/PoweredSoft.CQRS.AspNetCore/Mvc/QueryControllerFeatureProvider.cs @@ -28,6 +28,9 @@ namespace PoweredSoft.CQRS.AspNetCore.Mvc 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); diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQuery.cs b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQuery.cs new file mode 100644 index 0000000..f5ee4c3 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQuery.cs @@ -0,0 +1,25 @@ +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.Abstractions +{ + public interface IDynamicQuery : IDynamicQuery + where TSource : class + where TDestination : class + { + + } + + public interface IDynamicQuery + + { + List GetFilters(); + List GetGroups(); + List GetSorts(); + List GetAggregates(); + int? GetPage(); + int? GetPageSize(); + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryParams.cs b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryParams.cs new file mode 100644 index 0000000..099c2f8 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryParams.cs @@ -0,0 +1,8 @@ +namespace PoweredSoft.CQRS.DynamicQuery.Abstractions +{ + public interface IDynamicQueryParams + where TParams : class + { + TParams GetParams(); + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/IQueryableProvider.cs b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IQueryableProvider.cs new file mode 100644 index 0000000..e4e2d94 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IQueryableProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace PoweredSoft.CQRS.DynamicQuery.Abstractions +{ + public interface IQueryableProvider + { + Task> GetQueryableAsync(object query, CancellationToken cancelllationToken = default); + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj b/PoweredSoft.CQRS.DynamicQuery.Abstractions/PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj new file mode 100644 index 0000000..6dc5922 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs new file mode 100644 index 0000000..880f79e --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQuery.cs @@ -0,0 +1,71 @@ +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.DynamicQuery; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore +{ + public class DynamicQuery : DynamicQuery, IDynamicQuery + where TSource : class + where TDestination : class + { + + } + + public class DynamicQuery : DynamicQuery, IDynamicQuery, IDynamicQueryParams + 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 Sorts { get; set; } + public List Aggregates { get; set; } + public List Groups { get; set; } + public List Filters { get; set; } + + + public List GetAggregates() + { + return Aggregates?.AsEnumerable()?.ToList(); + } + + public List GetFilters() + { + return Filters?.Select(t => t.ToFilter())?.ToList(); + } + + public List GetGroups() + { + return this.Groups?.AsEnumerable()?.ToList(); + } + + public int? GetPage() + { + return this.Page; + } + + public int? GetPageSize() + { + return this.PageSize; + } + + public List GetSorts() + { + return this.Sorts?.AsEnumerable()?.ToList(); + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQueryFilter.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQueryFilter.cs new file mode 100644 index 0000000..0b8c618 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/DynamicQueryFilter.cs @@ -0,0 +1,59 @@ +using PoweredSoft.DynamicQuery; +using PoweredSoft.DynamicQuery.Core; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore +{ + public class DynamicQueryFilter + { + public List Filters { get; set; } + public bool? And { get; set; } + public FilterType Type { get; set; } + public bool? Not { get; set; } + public string Path { get; set; } + public object Value { get; set; } + + public IFilter ToFilter() + { + if (Type == FilterType.Composite) + { + var compositeFilter = new CompositeFilter + { + And = And, + Type = FilterType.Composite, + Filters = Filters?.Select(t => t.ToFilter())?.ToList() ?? new List() + }; + return compositeFilter; + } + + object value = Value; + if (Value is JsonElement jsonElement) + { + if (jsonElement.ValueKind == JsonValueKind.String) + value = jsonElement.ToString(); + else if (jsonElement.ValueKind == JsonValueKind.Number && jsonElement.TryGetInt64(out var l)) + value = l; + else if (jsonElement.ValueKind == JsonValueKind.True) + value = true; + else if (jsonElement.ValueKind == JsonValueKind.False) + value = false; + else if (jsonElement.ValueKind == JsonValueKind.Array) + throw new System.Exception("TODO"); + else + value = null; + } + + var simpleFilter = new SimpleFilter + { + And = And, + Type = Type, + Not = Not, + Path = Path, + Value = value + }; + return simpleFilter; + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryController.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryController.cs new file mode 100644 index 0000000..68e593c --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc +{ + [ApiController, Route("api/query/[controller]")] + public class DynamicQueryController : Controller + where TSource : class + where TDestination : class + { + [HttpPost] + public async Task> HandleAsync( + [FromBody] DynamicQuery query, + [FromServices]PoweredSoft.CQRS.Abstractions.IQueryHandler, IQueryExecutionResult> queryHandler + ) + { + var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted); + return result; + } + } + + [ApiController, Route("api/query/[controller]")] + public class DynamicQueryController : Controller + where TSource : class + where TDestination : class + where TParams : class + { + [HttpPost] + public async Task> HandleAsync( + [FromBody] DynamicQuery query, + [FromServices] PoweredSoft.CQRS.Abstractions.IQueryHandler, IQueryExecutionResult> queryHandler + ) + { + var result = await queryHandler.HandleAsync(query, HttpContext.RequestAborted); + return result; + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerConvention.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerConvention.cs new file mode 100644 index 0000000..cd37b0a --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerConvention.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.Extensions.DependencyInjection; +using PoweredSoft.CQRS.Abstractions.Discovery; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc +{ + public class DynamicQueryControllerConvention : IControllerModelConvention + { + private readonly IServiceProvider serviceProvider; + + public DynamicQueryControllerConvention(IServiceProvider serviceProvider) + { + this.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 = this.serviceProvider.GetRequiredService(); + var query = queryDiscovery.FindQuery(genericType); + controller.ControllerName = query.Name; + } + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerFeatureProvider.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerFeatureProvider.cs new file mode 100644 index 0000000..9e9fe94 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerFeatureProvider.cs @@ -0,0 +1,53 @@ +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; +using PoweredSoft.CQRS.DynamicQuery.Discover; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc +{ + public class DynamicQueryControllerFeatureProvider : IApplicationFeatureProvider + { + private readonly ServiceProvider serviceProvider; + + public DynamicQueryControllerFeatureProvider(ServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + var queryDiscovery = this.serviceProvider.GetRequiredService(); + foreach (var f in queryDiscovery.GetQueries()) + { + var ignoreAttribute = f.QueryType.GetCustomAttribute(); + if (ignoreAttribute != null) + continue; + + if (f.Category != "DynamicQuery") + continue; + + if (f is DynamicQueryMeta dynamicQueryMeta) + { + if (dynamicQueryMeta.ParamsType == null) + { + var controllerType = typeof(DynamicQueryController<,,>).MakeGenericType(f.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType); + var controllerTypeInfo = controllerType.GetTypeInfo(); + feature.Controllers.Add(controllerTypeInfo); + } + else + { + var controllerType = typeof(DynamicQueryController<,,,>).MakeGenericType(f.QueryType, dynamicQueryMeta.SourceType, dynamicQueryMeta.DestinationType, dynamicQueryMeta.ParamsType); + var controllerTypeInfo = controllerType.GetTypeInfo(); + feature.Controllers.Add(controllerTypeInfo); + } + } + } + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerOptions.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerOptions.cs new file mode 100644 index 0000000..30840b5 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/Mvc/DynamicQueryControllerOptions.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc +{ + public class DynamicQueryControllerOptions + { + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs new file mode 100644 index 0000000..4600ca8 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.CQRS.DynamicQuery.AspNetCore.Mvc; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace PoweredSoft.CQRS.DynamicQuery.AspNetCore +{ + public static class MvcBuilderExtensions + { + public static IMvcBuilder AddPoweredSoftDynamicQueries(this IMvcBuilder builder, Action 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; + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.AspNetCore/PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj new file mode 100644 index 0000000..1dffb01 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.AspNetCore/PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + diff --git a/PoweredSoft.CQRS.DynamicQuery/Discover/DynamicQueryMeta.cs b/PoweredSoft.CQRS.DynamicQuery/Discover/DynamicQueryMeta.cs new file mode 100644 index 0000000..794d6cc --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery/Discover/DynamicQueryMeta.cs @@ -0,0 +1,33 @@ +using PoweredSoft.CQRS.Abstractions.Discovery; +using PoweredSoft.CQRS.Discovery; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.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 (NameAttribute != null) + return NameAttribute.Name; + + var pluralizer = new Pluralize.NET.Pluralizer(); + return pluralizer.Pluralize(DestinationType.Name); + } + } + + public Type ParamsType { get; internal set; } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs new file mode 100644 index 0000000..ba24d31 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs @@ -0,0 +1,85 @@ +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.DynamicQuery; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace PoweredSoft.CQRS.DynamicQuery +{ + public class DynamicQueryHandler : + PoweredSoft.CQRS.Abstractions.IQueryHandler, IQueryExecutionResult> + where TSource : class + where TDestination : class + + { + private readonly IQueryHandlerAsync queryHandlerAsync; + private readonly IEnumerable> queryableProviders; + + public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync, + IEnumerable> queryableProviders) + { + this.queryHandlerAsync = queryHandlerAsync; + this.queryableProviders = queryableProviders; + } + + protected virtual Task> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default) + { + if (this.queryableProviders.Any()) + return queryableProviders.ElementAt(0).GetQueryableAsync(query, cancellationToken); + + throw new Exception($"You must provide a QueryableProvider for {typeof(TSource).Name}"); + } + + public virtual IQueryExecutionOptions GetQueryExecutionOptions(IQueryable source, IDynamicQuery query) + { + return new QueryExecutionOptions(); + } + + public virtual IEnumerable GetInterceptors() + { + return Enumerable.Empty(); + } + + protected async Task> 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 = this.GetInterceptors(); + + foreach (var interceptor in interceptors) + queryHandlerAsync.AddInterceptor(interceptor); + + var criteria = CreateCriteriaFromQuery(query); + var result = await queryHandlerAsync.ExecuteAsync(source, criteria, options, cancellationToken); + return result; + } + + protected virtual Task> AlterSourceAsync(IQueryable source, IDynamicQuery query, CancellationToken cancellationToken) + { + return Task.FromResult(source); + } + + protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query) + { + var criteria = new QueryCriteria + { + Page = query?.GetPage(), + PageSize = query?.GetPageSize(), + Filters = query?.GetFilters() ?? new List(), + Sorts = query?.GetSorts() ?? new List(), + Groups = query.GetGroups() ?? new List(), + Aggregates = query.GetAggregates() ?? new List() + }; + return criteria; + } + + public async Task> HandleAsync(IDynamicQuery query, CancellationToken cancellationToken = default) + { + return await ProcessQueryAsync(query, cancellationToken); + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery/PoweredSoft.CQRS.DynamicQuery.csproj b/PoweredSoft.CQRS.DynamicQuery/PoweredSoft.CQRS.DynamicQuery.csproj new file mode 100644 index 0000000..ca38f73 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery/PoweredSoft.CQRS.DynamicQuery.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs b/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..382bf70 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.DependencyInjection; +using PoweredSoft.CQRS.Abstractions; +using PoweredSoft.CQRS.Abstractions.Discovery; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using PoweredSoft.CQRS.DynamicQuery.Discover; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddDynamicQuery(this IServiceCollection services) + where TSource : class + where TDestination : class + { + // add query handler. + services.AddTransient, IQueryExecutionResult>, DynamicQueryHandler>(); + + // add for discovery purposes. + var queryType = typeof(IDynamicQuery); + var resultType = typeof(IQueryExecutionResult); + var serviceType = typeof(DynamicQueryHandler); + var queryMeta = new DynamicQueryMeta(queryType, serviceType, resultType); + + services.AddSingleton(queryMeta); + + return services; + } + + public static IServiceCollection AddDynamicQueryWithParams(this IServiceCollection services) + where TSource : class + where TDestination : class + { + // add query handler. + services.AddTransient, IQueryExecutionResult>, DynamicQueryHandler>(); + + // add for discovery purposes. + var queryType = typeof(IDynamicQuery); + var resultType = typeof(IQueryExecutionResult); + var serviceType = typeof(DynamicQueryHandler); + var queryMeta = new DynamicQueryMeta(queryType, serviceType, resultType); + + // params type. + queryMeta.ParamsType = typeof(TParams); + + services.AddSingleton(queryMeta); + + return services; + } + } +} diff --git a/PoweredSoft.CQRS.sln b/PoweredSoft.CQRS.sln index 0271202..221bb14 100644 --- a/PoweredSoft.CQRS.sln +++ b/PoweredSoft.CQRS.sln @@ -19,6 +19,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQuery", "PoweredSoft.CQRS.DynamicQuery\PoweredSoft.CQRS.DynamicQuery.csproj", "{A38CE930-191F-417C-B5BE-8CC62DB47513}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQuery.Abstractions", "PoweredSoft.CQRS.DynamicQuery.Abstractions\PoweredSoft.CQRS.DynamicQuery.Abstractions.csproj", "{60B5E255-77B8-48E0-AE8F-04E8332970F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.CQRS.DynamicQuery.AspNetCore", "PoweredSoft.CQRS.DynamicQuery.AspNetCore\PoweredSoft.CQRS.DynamicQuery.AspNetCore.csproj", "{0829B99A-0A20-4CAC-A91E-FB67E18444DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +51,18 @@ Global {F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Debug|Any CPU.Build.0 = Debug|Any CPU {F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Release|Any CPU.ActiveCfg = Release|Any CPU {F15B1E11-8D4C-489E-AFF7-AA144105FE46}.Release|Any CPU.Build.0 = Release|Any CPU + {A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A38CE930-191F-417C-B5BE-8CC62DB47513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A38CE930-191F-417C-B5BE-8CC62DB47513}.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 + {0829B99A-0A20-4CAC-A91E-FB67E18444DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE