From a1a5a57383914e546a570d018b14b203420a545c Mon Sep 17 00:00:00 2001 From: David Lebee Date: Sun, 18 Nov 2018 05:33:37 -0600 Subject: [PATCH] added some extension and made available the code to bind easily to aspnetcore trough interface. --- DynamicQuery.sln | 8 +- .../Json/DynamicQueryJsonConverter.cs | 80 +++++++++++++++ .../MvcBuilderExtensions.cs | 20 ++++ ...PoweredSoft.DynamicQuery.AspNetCore.csproj | 26 +++++ .../ServiceCollectionExtensions.cs | 22 +++++ PoweredSoft.DynamicQuery.Core/IFilter.cs | 4 +- .../FilterInterceptorTests.cs | 64 ++++++++++++ .../Extensions/FilterExtensions.cs | 97 +++++++++++++++++++ .../PoweredSoft.DynamicQuery.csproj | 2 +- 9 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 PoweredSoft.DynamicQuery.AspNetCore/Json/DynamicQueryJsonConverter.cs create mode 100644 PoweredSoft.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs create mode 100644 PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj create mode 100644 PoweredSoft.DynamicQuery.AspNetCore/ServiceCollectionExtensions.cs create mode 100644 PoweredSoft.DynamicQuery/Extensions/FilterExtensions.cs diff --git a/DynamicQuery.sln b/DynamicQuery.sln index 361e0b1..f47d344 100644 --- a/DynamicQuery.sln +++ b/DynamicQuery.sln @@ -17,7 +17,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicQuery.Test", "PoweredSoft.DynamicQuery.Test\PoweredSoft.DynamicQuery.Test.csproj", "{3EAD8217-8E10-4261-9055-50444905922C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.Test", "PoweredSoft.DynamicQuery.Test\PoweredSoft.DynamicQuery.Test.csproj", "{3EAD8217-8E10-4261-9055-50444905922C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicQuery.AspNetCore", "PoweredSoft.DynamicQuery.AspNetCore\PoweredSoft.DynamicQuery.AspNetCore.csproj", "{DF58BD18-AB47-4018-B1EA-D1118D93B408}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -45,6 +47,10 @@ Global {3EAD8217-8E10-4261-9055-50444905922C}.Debug|Any CPU.Build.0 = Debug|Any CPU {3EAD8217-8E10-4261-9055-50444905922C}.Release|Any CPU.ActiveCfg = Release|Any CPU {3EAD8217-8E10-4261-9055-50444905922C}.Release|Any CPU.Build.0 = Release|Any CPU + {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PoweredSoft.DynamicQuery.AspNetCore/Json/DynamicQueryJsonConverter.cs b/PoweredSoft.DynamicQuery.AspNetCore/Json/DynamicQueryJsonConverter.cs new file mode 100644 index 0000000..b549e55 --- /dev/null +++ b/PoweredSoft.DynamicQuery.AspNetCore/Json/DynamicQueryJsonConverter.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace PoweredSoft.DynamicQuery.AspNetCore.Json +{ + public class DynamicQueryJsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => false; + + private Type[] DynamicQueryTypes { get; } = new Type[] + { + typeof(IFilter), + typeof(ISimpleFilter), + typeof(ICompositeFilter), + typeof(IAggregate), + typeof(ISort), + typeof(IGroup), + typeof(IQueryCriteria), + typeof(IQueryHandler) + }; + + public IServiceProvider ServiceProvider { get; } + + public DynamicQueryJsonConverter(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public override bool CanConvert(Type objectType) => objectType.IsInterface && DynamicQueryTypes.Contains(objectType); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return (object)null; + + if (objectType == typeof(IFilter)) + { + var jo = JObject.Load(reader); + + bool isComposite = false; + if (jo.ContainsKey("type")) + { + isComposite = jo.GetValue("type").Value() + .Equals("composite", StringComparison.OrdinalIgnoreCase); + } + else if (jo.ContainsKey("Type")) + { + isComposite = jo.GetValue("Type").Value() + .Equals("composite", StringComparison.OrdinalIgnoreCase); + } + else + { + throw new Exception("IFilter should have a type property.."); + } + + var filterObj = ServiceProvider.GetService(isComposite ? typeof(ICompositeFilter) : typeof(ISimpleFilter)); + filterObj = jo.ToObject(filterObj.GetType()); + return filterObj; + } + + var obj = ServiceProvider.GetService(objectType); + if (obj == null) + throw new JsonSerializationException("No object created."); + + serializer.Populate(reader, obj); + return obj; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/PoweredSoft.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs b/PoweredSoft.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs new file mode 100644 index 0000000..adf725f --- /dev/null +++ b/PoweredSoft.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using PoweredSoft.DynamicQuery.AspNetCore.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.DynamicQuery.AspNetCore +{ + public static class MvcBuilderExtensions + { + public static IMvcBuilder AddDynamicQueryJsonConverter(this IMvcBuilder builder, IServiceProvider serviceProvider) + { + builder.AddJsonOptions(o => + { + o.SerializerSettings.Converters.Add(new DynamicQueryJsonConverter(serviceProvider)); + }); + return builder; + } + } +} diff --git a/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj new file mode 100644 index 0000000..635b81c --- /dev/null +++ b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.1 + true + Powered Softwares Inc. + MIT + https://github.com/PoweredSoft/DynamicQuery + https://github.com/PoweredSoft/DynamicQuery + github + powered,soft,dynamic,criteria,query,builder,asp,net,core + 1.0.0 + https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&r=g&d=retro + + + + + + + + + + + + + diff --git a/PoweredSoft.DynamicQuery.AspNetCore/ServiceCollectionExtensions.cs b/PoweredSoft.DynamicQuery.AspNetCore/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..fe84214 --- /dev/null +++ b/PoweredSoft.DynamicQuery.AspNetCore/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.DynamicQuery.AspNetCore +{ + public static class ServiceCollectionExtensions + { + public static void AddDynamicQueryDefaultMappings(IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + } + } +} diff --git a/PoweredSoft.DynamicQuery.Core/IFilter.cs b/PoweredSoft.DynamicQuery.Core/IFilter.cs index bdeaed6..bf7f9fa 100644 --- a/PoweredSoft.DynamicQuery.Core/IFilter.cs +++ b/PoweredSoft.DynamicQuery.Core/IFilter.cs @@ -1,4 +1,6 @@ -namespace PoweredSoft.DynamicQuery.Core +using System; + +namespace PoweredSoft.DynamicQuery.Core { public interface IFilter { diff --git a/PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs index 5b8c7ad..e49b5be 100644 --- a/PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs @@ -1,4 +1,5 @@ using PoweredSoft.DynamicQuery.Core; +using PoweredSoft.DynamicQuery.Extensions; using PoweredSoft.DynamicQuery.Test.Mock; using System; using System.Collections.Generic; @@ -21,6 +22,19 @@ namespace PoweredSoft.DynamicQuery.Test } } + private class MockFilterInterceptorAWithExtension : IFilterInterceptor + { + public IFilter InterceptFilter(IFilter filter) + { + if (filter.IsSimpleFilterOn("CustomerFirstName")) + return filter.ReplaceByOn(t => t.Customer.FirstName); + else if (filter.IsSimpleFilterOn("CustomerFullName")) + return filter.ReplaceByCompositeOn(t => t.Customer.FirstName, t => t.Customer.LastName); + + return filter; + } + } + private class MockFilterInterceptorB : IFilterInterceptor { public IFilter InterceptFilter(IFilter filter) @@ -54,6 +68,56 @@ namespace PoweredSoft.DynamicQuery.Test }); } + [Fact] + public void SimpleWithExtensions() + { + MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_SimpleWithExtensions", TestSeeders.SimpleSeedScenario, ctx => + { + var queryable = ctx.Orders.AsQueryable(); + + var criteria = new QueryCriteria() + { + Filters = new List + { + new SimpleFilter { Path = "CustomerFirstName", Value = "David", Type = FilterType.Contains } + } + }; + + var query = new QueryHandler(); + query.AddInterceptor(new MockFilterInterceptorAWithExtension()); + var result = query.Execute(queryable, criteria); + + var actual = result.Data; + var expected = queryable.Where(t => t.Customer.FirstName == "David").ToList(); + Assert.Equal(expected, actual); + }); + } + + [Fact] + public void SimpleWithExtensions2() + { + MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_SimpleWithExtensions2", TestSeeders.SimpleSeedScenario, ctx => + { + var queryable = ctx.Orders.AsQueryable(); + + var criteria = new QueryCriteria() + { + Filters = new List + { + new SimpleFilter { Path = "CustomerFullName", Value = "Da", Type = FilterType.Contains } + } + }; + + var query = new QueryHandler(); + query.AddInterceptor(new MockFilterInterceptorAWithExtension()); + var result = query.Execute(queryable, criteria); + + var actual = result.Data; + var expected = queryable.Where(t => t.Customer.FirstName.Contains("Da") || t.Customer.LastName.Contains("Da")).ToList(); + Assert.Equal(expected, actual); + }); + } + [Fact] public void Multi() { diff --git a/PoweredSoft.DynamicQuery/Extensions/FilterExtensions.cs b/PoweredSoft.DynamicQuery/Extensions/FilterExtensions.cs new file mode 100644 index 0000000..a48ad93 --- /dev/null +++ b/PoweredSoft.DynamicQuery/Extensions/FilterExtensions.cs @@ -0,0 +1,97 @@ +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace PoweredSoft.DynamicQuery.Extensions +{ + public static class FilterExtensions + { + public static bool IsSimpleFilter(this IFilter filter) => filter is ISimpleFilter; + public static bool IsCompositeFilter(this IFilter filter) => filter is ICompositeFilter; + + public static bool IsSimpleFilterOn(this IFilter filter, string path) + { + var simpleFilter = filter as ISimpleFilter; + if (simpleFilter == null) + return false; + + var result = simpleFilter.Path?.Equals(path, StringComparison.InvariantCultureIgnoreCase) == true; + return result; + } + + public static bool IsSimpleFilterOn(this IFilter filter, Expression> expr) + { + var resolved = GetPropertySymbol(expr); + return filter.IsSimpleFilterOn(resolved); + } + + public static ISimpleFilter ReplaceByOn(this IFilter filter, string path) + { + var simpleFilter = filter as ISimpleFilter; + if (simpleFilter == null) + throw new Exception("Must be a simple filter"); + + var ret = new SimpleFilter(); + ret.And = filter.And; + ret.Type = filter.Type; + ret.Value = simpleFilter.Value; + ret.Path = path; + return ret; + } + + public static ISimpleFilter ReplaceByOn(this IFilter filter, Expression> expr) + { + var resolved = GetPropertySymbol(expr); + return filter.ReplaceByOn(resolved); + } + + public static ICompositeFilter ReplaceByCompositeOn(this IFilter filter, params string[] paths) + { + var simpleFilter = filter as ISimpleFilter; + if (simpleFilter == null) + throw new Exception("Must be a simple filter"); + + var compositeFilter = new CompositeFilter(); + compositeFilter.And = filter.And; + compositeFilter.Type = FilterType.Composite; + compositeFilter.Filters = paths + .Select(t => new SimpleFilter + { + Type = filter.Type, + Path = t, + And = false, + Value = simpleFilter.Value + }) + .AsEnumerable() + .ToList(); + return compositeFilter; + } + + public static ICompositeFilter ReplaceByCompositeOn(this IFilter filter, params Expression>[] exprs) + { + var paths = exprs.Select(expr => GetPropertySymbol(expr)).ToArray(); + return ReplaceByCompositeOn(filter, paths); + } + + internal static string GetPropertySymbol(Expression> expression) + { + return string.Join(".", + GetMembersOnPath(expression.Body as MemberExpression) + .Select(m => m.Member.Name) + .Reverse()); + } + + internal static IEnumerable GetMembersOnPath(MemberExpression expression) + { + while (expression != null) + { + yield return expression; + expression = expression.Expression as MemberExpression; + } + } + } +} diff --git a/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj index b210a85..d046c91 100644 --- a/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj +++ b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj @@ -9,7 +9,7 @@ https://github.com/PoweredSoft/DynamicQuery github powered,soft,dynamic,criteria,query,builder - 1.0.4 + 1.0.5 https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&r=g&d=retro