From de98b3b4729db45cccd4a36889572ca3a1cec841 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Wed, 3 Feb 2021 01:26:37 -0500 Subject: [PATCH] better way to inject interceptors into the dynamic query. --- .../ContactQueryableProvider.cs | 9 --- .../PersonConvertInterceptor.cs | 18 ++++++ Demo/DynamicQueries/PersonModel.cs | 10 +++ .../PersonOptimizationInterceptor.cs | 54 ++++++++++++++++ .../DynamicQueries/PersonQueryableProvider.cs | 33 ++++++++++ .../SearchContactParamsService.cs | 16 +++++ Demo/Queries/PersonQuery.cs | 36 +---------- Demo/Queries/PersonQueryHandler.cs | 29 +++++++++ Demo/Startup.cs | 6 ++ .../DynamicQueryInterceptorProvider.cs | 20 ++++++ .../IDynamicQueryInterceptorProvider.cs | 12 ++++ .../DynamicQueryHandler.cs | 8 ++- .../DynamicQueryHandlerBase.cs | 24 ++------ .../ServiceCollectionExtensions.cs | 61 ++++++++++++++++--- 14 files changed, 263 insertions(+), 73 deletions(-) create mode 100644 Demo/DynamicQueries/PersonConvertInterceptor.cs create mode 100644 Demo/DynamicQueries/PersonModel.cs create mode 100644 Demo/DynamicQueries/PersonOptimizationInterceptor.cs create mode 100644 Demo/DynamicQueries/PersonQueryableProvider.cs create mode 100644 Demo/DynamicQueries/SearchContactParamsService.cs create mode 100644 Demo/Queries/PersonQueryHandler.cs create mode 100644 PoweredSoft.CQRS.DynamicQuery.Abstractions/DynamicQueryInterceptorProvider.cs create mode 100644 PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryInterceptorProvider.cs diff --git a/Demo/DynamicQueries/ContactQueryableProvider.cs b/Demo/DynamicQueries/ContactQueryableProvider.cs index 7bb0875..1947870 100644 --- a/Demo/DynamicQueries/ContactQueryableProvider.cs +++ b/Demo/DynamicQueries/ContactQueryableProvider.cs @@ -6,15 +6,6 @@ using System.Threading.Tasks; namespace Demo.DynamicQueries { - public class SearchContactParamsService : IAlterQueryableService - { - public Task> AlterQueryableAsync(IQueryable query, IDynamicQueryParams dynamicQuery, CancellationToken cancellationToken = default) - { - var safe = dynamicQuery.GetParams()?.SearchDisplayName; - return Task.FromResult(query.Where(t => t.DisplayName.Contains(safe))); - } - } - public class ContactQueryableProvider : IQueryableProvider { public Task> GetQueryableAsync(object query, CancellationToken cancelllationToken = default) diff --git a/Demo/DynamicQueries/PersonConvertInterceptor.cs b/Demo/DynamicQueries/PersonConvertInterceptor.cs new file mode 100644 index 0000000..161235b --- /dev/null +++ b/Demo/DynamicQueries/PersonConvertInterceptor.cs @@ -0,0 +1,18 @@ +using Demo.Queries; +using PoweredSoft.DynamicQuery.Core; +using System.Linq; + +namespace Demo.DynamicQueries +{ + public class PersonConvertInterceptor : IQueryConvertInterceptor + { + public PersonModel InterceptResultTo(Person entity) + { + return new PersonModel + { + Id = entity.Id, + FullName = entity.FirstName + " " + entity.LastName + }; + } + } +} diff --git a/Demo/DynamicQueries/PersonModel.cs b/Demo/DynamicQueries/PersonModel.cs new file mode 100644 index 0000000..b51f7dc --- /dev/null +++ b/Demo/DynamicQueries/PersonModel.cs @@ -0,0 +1,10 @@ +using System; + +namespace Demo.DynamicQueries +{ + public class PersonModel + { + public long Id { get; set; } + public string FullName { get; set; } + } +} diff --git a/Demo/DynamicQueries/PersonOptimizationInterceptor.cs b/Demo/DynamicQueries/PersonOptimizationInterceptor.cs new file mode 100644 index 0000000..3163bf0 --- /dev/null +++ b/Demo/DynamicQueries/PersonOptimizationInterceptor.cs @@ -0,0 +1,54 @@ +using Demo.Queries; +using PoweredSoft.DynamicQuery; +using PoweredSoft.DynamicQuery.Core; +using System.Collections.Generic; + +namespace Demo.DynamicQueries +{ + public class PersonOptimizationInterceptor : IFilterInterceptor, ISortInterceptor + { + public IFilter InterceptFilter(IFilter filter) + { + if (filter is ISimpleFilter simpleFilter) + { + if (simpleFilter.Path.Equals(nameof(PersonModel.FullName), System.StringComparison.InvariantCultureIgnoreCase)) + return new CompositeFilter + { + Type = filter.Type, + And = filter.And, + Filters = new List { + new SimpleFilter + { + Not = simpleFilter.Not, + And = false, + Type = simpleFilter.Type, + Value = simpleFilter.Value, + Path = nameof(Person.FirstName) + }, + new SimpleFilter + { + Not = simpleFilter.Not, + And = false, + Type = simpleFilter.Type, + Value = simpleFilter.Value, + Path = nameof(Person.LastName) + } + } + }; + } + + return filter; + } + + public IEnumerable InterceptSort(IEnumerable sort) + { + foreach(var s in sort) + { + if (s.Path.Equals(nameof(PersonModel.FullName), System.StringComparison.InvariantCultureIgnoreCase)) + yield return new Sort(nameof(Person.LastName), s.Ascending); + else + yield return s; + } + } + } +} diff --git a/Demo/DynamicQueries/PersonQueryableProvider.cs b/Demo/DynamicQueries/PersonQueryableProvider.cs new file mode 100644 index 0000000..f2a9d64 --- /dev/null +++ b/Demo/DynamicQueries/PersonQueryableProvider.cs @@ -0,0 +1,33 @@ +using Demo.Queries; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Demo.DynamicQueries +{ + public class PersonQueryableProvider : IQueryableProvider + { + private readonly IEnumerable _persons = new List() + { + new Person + { + Id = 1, + FirstName = "David", + LastName = "Lebee" + }, + new Person + { + Id = 2, + FirstName = "John", + LastName = "Doe" + } + }; + + public Task> GetQueryableAsync(object query, CancellationToken cancelllationToken = default) + { + return Task.FromResult(_persons.AsQueryable()); + } + } +} diff --git a/Demo/DynamicQueries/SearchContactParamsService.cs b/Demo/DynamicQueries/SearchContactParamsService.cs new file mode 100644 index 0000000..3620880 --- /dev/null +++ b/Demo/DynamicQueries/SearchContactParamsService.cs @@ -0,0 +1,16 @@ +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Demo.DynamicQueries +{ + public class SearchContactParamsService : IAlterQueryableService + { + public Task> AlterQueryableAsync(IQueryable query, IDynamicQueryParams dynamicQuery, CancellationToken cancellationToken = default) + { + var safe = dynamicQuery.GetParams()?.SearchDisplayName; + return Task.FromResult(query.Where(t => t.DisplayName.Contains(safe))); + } + } +} diff --git a/Demo/Queries/PersonQuery.cs b/Demo/Queries/PersonQuery.cs index bdbb1f3..71153d8 100644 --- a/Demo/Queries/PersonQuery.cs +++ b/Demo/Queries/PersonQuery.cs @@ -1,9 +1,4 @@ -using PoweredSoft.CQRS.Abstractions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Demo.Queries { @@ -18,33 +13,4 @@ namespace Demo.Queries { public string Search { get; set; } } - - public class PersonQueryHandler : IQueryHandler> - { - private readonly IEnumerable _persons = new List() - { - new Person - { - Id = 1, - FirstName = "David", - LastName = "Lebee" - }, - new Person - { - Id = 2, - FirstName = "John", - LastName = "Doe" - } - }; - - public Task> HandleAsync(PersonQuery query, CancellationToken cancellationToken = default) - { - var ret = _persons.AsQueryable(); - - if (query != null && !string.IsNullOrEmpty(query.Search)) - ret = ret.Where(t => t.FirstName.Contains(query.Search) || t.LastName.Contains(query.Search)); - - return Task.FromResult(ret); - } - } } diff --git a/Demo/Queries/PersonQueryHandler.cs b/Demo/Queries/PersonQueryHandler.cs new file mode 100644 index 0000000..692311a --- /dev/null +++ b/Demo/Queries/PersonQueryHandler.cs @@ -0,0 +1,29 @@ +using PoweredSoft.CQRS.Abstractions; +using PoweredSoft.CQRS.DynamicQuery.Abstractions; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Demo.Queries +{ + public class PersonQueryHandler : IQueryHandler> + { + private readonly IQueryableProvider queryableProvider; + + public PersonQueryHandler(IQueryableProvider queryableProvider) + { + this.queryableProvider = queryableProvider; + } + + public async Task> HandleAsync(PersonQuery query, CancellationToken cancellationToken = default) + { + var ret = await queryableProvider.GetQueryableAsync(query); + + if (query != null && !string.IsNullOrEmpty(query.Search)) + ret = ret.Where(t => t.FirstName.Contains(query.Search) || t.LastName.Contains(query.Search)); + + return ret; + } + } +} diff --git a/Demo/Startup.cs b/Demo/Startup.cs index 52f7d3f..bf53d9d 100644 --- a/Demo/Startup.cs +++ b/Demo/Startup.cs @@ -65,6 +65,12 @@ namespace Demo services.AddDynamicQuery(); services.AddDynamicQueryWithParams(name: "SearchContacts") .AddAlterQueryableWithParams(); + + services + .AddTransient, PersonQueryableProvider>() + .AddDynamicQuery("People") + .AddDynamicQueryInterceptor() + .AddDynamicQueryInterceptor(); } private void AddCommands(IServiceCollection services) diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/DynamicQueryInterceptorProvider.cs b/PoweredSoft.CQRS.DynamicQuery.Abstractions/DynamicQueryInterceptorProvider.cs new file mode 100644 index 0000000..94ba9b9 --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/DynamicQueryInterceptorProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace PoweredSoft.CQRS.DynamicQuery.Abstractions +{ + public class DynamicQueryInterceptorProvider : IDynamicQueryInterceptorProvider + { + private readonly Type[] types; + + public DynamicQueryInterceptorProvider(params Type[] types) + { + this.types = types; + } + + public IEnumerable GetInterceptorsTypes() + { + return types; + } + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryInterceptorProvider.cs b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryInterceptorProvider.cs new file mode 100644 index 0000000..28cb8ae --- /dev/null +++ b/PoweredSoft.CQRS.DynamicQuery.Abstractions/IDynamicQueryInterceptorProvider.cs @@ -0,0 +1,12 @@ +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.CQRS.DynamicQuery.Abstractions +{ + public interface IDynamicQueryInterceptorProvider + { + IEnumerable GetInterceptorsTypes(); + } +} diff --git a/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs index 09c1dde..9dddab8 100644 --- a/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs +++ b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandler.cs @@ -16,7 +16,9 @@ namespace PoweredSoft.CQRS.DynamicQuery { public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync, IEnumerable> queryableProviders, - IEnumerable> alterQueryableServices, IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, serviceProvider) + IEnumerable> alterQueryableServices, + IEnumerable> dynamicQueryInterceptorProviders, + IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider) { } @@ -38,7 +40,9 @@ namespace PoweredSoft.CQRS.DynamicQuery public DynamicQueryHandler(IQueryHandlerAsync queryHandlerAsync, IEnumerable> queryableProviders, IEnumerable> alterQueryableServices, - IEnumerable> alterQueryableServicesWithParams, IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, serviceProvider) + IEnumerable> alterQueryableServicesWithParams, + IEnumerable> dynamicQueryInterceptorProviders, + IServiceProvider serviceProvider) : base(queryHandlerAsync, queryableProviders, alterQueryableServices, dynamicQueryInterceptorProviders, serviceProvider) { this.alterQueryableServicesWithParams = alterQueryableServicesWithParams; } diff --git a/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs index 4184ed3..df02463 100644 --- a/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs +++ b/PoweredSoft.CQRS.DynamicQuery/DynamicQueryHandlerBase.cs @@ -16,16 +16,19 @@ namespace PoweredSoft.CQRS.DynamicQuery private readonly IQueryHandlerAsync queryHandlerAsync; private readonly IEnumerable> queryableProviders; private readonly IEnumerable> alterQueryableServices; + private readonly IEnumerable> dynamicQueryInterceptorProviders; private readonly IServiceProvider serviceProvider; public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync, IEnumerable> queryableProviders, IEnumerable> alterQueryableServices, + IEnumerable> dynamicQueryInterceptorProviders, IServiceProvider serviceProvider) { this.queryHandlerAsync = queryHandlerAsync; this.queryableProviders = queryableProviders; this.alterQueryableServices = alterQueryableServices; + this.dynamicQueryInterceptorProviders = dynamicQueryInterceptorProviders; this.serviceProvider = serviceProvider; } @@ -44,7 +47,9 @@ namespace PoweredSoft.CQRS.DynamicQuery public virtual IEnumerable GetInterceptors() { - return Enumerable.Empty(); + var types = dynamicQueryInterceptorProviders.SelectMany(t => t.GetInterceptorsTypes()).Distinct(); + foreach (var type in types) + yield return serviceProvider.GetService(type) as IQueryInterceptor; } protected async Task> ProcessQueryAsync(IDynamicQuery query, CancellationToken cancellationToken = default) @@ -54,23 +59,6 @@ namespace PoweredSoft.CQRS.DynamicQuery var options = GetQueryExecutionOptions(source, query); var interceptors = this.GetInterceptors(); - // basic after read service. - var afterReadService1 = this.serviceProvider.GetService(typeof(IAfterReadInterceptorAsync)); - if (afterReadService1 is IQueryInterceptor ars1) - queryHandlerAsync.AddInterceptor(ars1); - - // support of injecting a query convert interceptor. - if (typeof(TSource) != typeof(TDestination)) - { - var convertService = this.serviceProvider.GetService(typeof(IQueryConvertInterceptor)); - if (convertService is IQueryInterceptor cs) - queryHandlerAsync.AddInterceptor(cs); - - var afterReadService2 = this.serviceProvider.GetService(typeof(IAfterReadInterceptorAsync)); - if (afterReadService2 is IQueryInterceptor ars2) - queryHandlerAsync.AddInterceptor(ars2); - } - foreach (var interceptor in interceptors) queryHandlerAsync.AddInterceptor(interceptor); diff --git a/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs b/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs index 988f0a3..af0cfdf 100644 --- a/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs +++ b/PoweredSoft.CQRS.DynamicQuery/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using PoweredSoft.CQRS.Abstractions; using PoweredSoft.CQRS.Abstractions.Discovery; using PoweredSoft.CQRS.DynamicQuery.Abstractions; @@ -95,22 +96,64 @@ namespace PoweredSoft.CQRS.DynamicQuery return services.AddTransient, TService>(); } - public static IServiceCollection AddQueryConvertInterceptor(this IServiceCollection services) - where TService : class, IQueryConvertInterceptor + public static IServiceCollection AddDynamicQueryInterceptor(this IServiceCollection services) + where TInterceptor : class, IQueryInterceptor { - return services.AddTransient, TService>(); + services.TryAddTransient(); + return services.AddSingleton>( + new DynamicQueryInterceptorProvider(typeof(TInterceptor))); } - public static IServiceCollection AddAfterReadInterceptorAsync(this IServiceCollection services) - where TService : class, IAfterReadInterceptorAsync + public static IServiceCollection AddDynamicQueryInterceptors(this IServiceCollection services) + where T1 : class, IQueryInterceptor + where T2 : class, IQueryInterceptor { - return services.AddTransient, TService>(); + services.TryAddTransient(); + services.TryAddTransient(); + return services.AddSingleton>( + new DynamicQueryInterceptorProvider(typeof(T1), typeof(T2))); } - public static IServiceCollection AddAfterReadInterceptorAsync(this IServiceCollection services) - where TService : class, IAfterReadInterceptorAsync + public static IServiceCollection AddDynamicQueryInterceptors(this IServiceCollection services) + where T1 : class, IQueryInterceptor + where T2 : class, IQueryInterceptor + where T3 : class, IQueryInterceptor { - return services.AddTransient, TService>(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + return services.AddSingleton>( + new DynamicQueryInterceptorProvider(typeof(T1), typeof(T2), typeof(T3))); + } + + public static IServiceCollection AddDynamicQueryInterceptors(this IServiceCollection services) + where T1 : class, IQueryInterceptor + where T2 : class, IQueryInterceptor + where T3 : class, IQueryInterceptor + where T4 : class, IQueryInterceptor + { + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + return services.AddSingleton>( + new DynamicQueryInterceptorProvider(typeof(T1), typeof(T2), typeof(T3), typeof(T4))); + } + + public static IServiceCollection AddDynamicQueryInterceptors(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(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + return services.AddSingleton>( + new DynamicQueryInterceptorProvider(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5))); } } }