From e1bb069253b662f22e1df835ff1518ed65d9aa94 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Sun, 24 Mar 2019 19:45:23 +0100 Subject: [PATCH] Added support for generic filter and sort methods --- README.md | 15 +++++++++++ Sieve/Services/SieveProcessor.cs | 22 +++++++++++++++ SieveUnitTests/Entities/BaseEntity.cs | 13 +++++++++ SieveUnitTests/Entities/Comment.cs | 7 +---- SieveUnitTests/Entities/Post.cs | 6 +---- SieveUnitTests/General.cs | 27 +++++++++++++++++++ .../Services/SieveCustomFilterMethods.cs | 6 +++++ .../Services/SieveCustomSortMethods.cs | 9 +++++++ 8 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 SieveUnitTests/Entities/BaseEntity.cs diff --git a/README.md b/README.md index fd0b677..384571e 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,15 @@ public class SieveCustomSortMethods : ISieveCustomSortMethods return result; // Must return modified IQueryable } + + public IQueryable Oldest(IQueryable source, bool useThenBy, bool desc) where T : BaseEntity // Generic functions are allowed too + { + var result = useThenBy ? + ((IOrderedQueryable)source).ThenByDescending(p => p.DateCreated) : + source.OrderByDescending(p => p.DateCreated); + + return result; + } } ``` And `SieveCustomFilterMethods`: @@ -97,6 +106,12 @@ public class SieveCustomFilterMethods : ISieveCustomFilterMethods return result; // Must return modified IQueryable } + + public IQueryable Latest(IQueryable source, string op, string[] values) where T : BaseEntity // Generic functions are allowed too + { + var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14)); + return result; + } } ``` diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index cf54d31..555e888 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -387,6 +387,28 @@ namespace Sieve.Services _options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, typeof(IQueryable)); + + if (customMethod == null) + { + // Find generic methods `public IQueryable Filter(IQueryable source, ...)` + var genericCustomMethod = parent?.GetType() + .GetMethodExt(name, + _options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance, + typeof(IQueryable<>)); + + if (genericCustomMethod != null && + genericCustomMethod.ReturnType.IsGenericType && + genericCustomMethod.ReturnType.GetGenericTypeDefinition() == typeof(IQueryable<>)) + { + var genericBaseType = genericCustomMethod.ReturnType.GenericTypeArguments[0]; + var constraints = genericBaseType.GetGenericParameterConstraints(); + if (constraints == null || constraints.Length == 0 || constraints.All((t) => t.IsAssignableFrom(typeof(TEntity)))) + { + customMethod = genericCustomMethod.MakeGenericMethod(typeof(TEntity)); + } + } + } + if (customMethod != null) { try diff --git a/SieveUnitTests/Entities/BaseEntity.cs b/SieveUnitTests/Entities/BaseEntity.cs new file mode 100644 index 0000000..1ea6697 --- /dev/null +++ b/SieveUnitTests/Entities/BaseEntity.cs @@ -0,0 +1,13 @@ +using System; +using Sieve.Attributes; + +namespace SieveUnitTests.Entities +{ + public class BaseEntity + { + public int Id { get; set; } + + [Sieve(CanFilter = true, CanSort = true)] + public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; + } +} diff --git a/SieveUnitTests/Entities/Comment.cs b/SieveUnitTests/Entities/Comment.cs index e3418be..0e67563 100644 --- a/SieveUnitTests/Entities/Comment.cs +++ b/SieveUnitTests/Entities/Comment.cs @@ -3,13 +3,8 @@ using Sieve.Attributes; namespace SieveUnitTests.Entities { - public class Comment + public class Comment : BaseEntity { - public int Id { get; set; } - - [Sieve(CanFilter = true, CanSort = true)] - public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; - [Sieve(CanFilter = true)] public string Text { get; set; } } diff --git a/SieveUnitTests/Entities/Post.cs b/SieveUnitTests/Entities/Post.cs index 20ecdfc..1503486 100644 --- a/SieveUnitTests/Entities/Post.cs +++ b/SieveUnitTests/Entities/Post.cs @@ -3,9 +3,8 @@ using Sieve.Attributes; namespace SieveUnitTests.Entities { - public class Post + public class Post : BaseEntity { - public int Id { get; set; } [Sieve(CanFilter = true, CanSort = true)] public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 8); @@ -16,9 +15,6 @@ namespace SieveUnitTests.Entities [Sieve(CanFilter = true, CanSort = true)] public int CommentCount { get; set; } = new Random().Next(0, 1000); - [Sieve(CanFilter = true, CanSort = true)] - public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; - [Sieve(CanFilter = true, CanSort = true)] public int? CategoryId { get; set; } = new Random().Next(0, 4); diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 471b216..093077c 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -193,6 +193,20 @@ namespace SieveUnitTests Assert.IsTrue(result.Count() == 3); } + [TestMethod] + public void CustomGenericFiltersWork() + { + var model = new SieveModel() + { + Filters = "Latest", + }; + + var result = _processor.Apply(model, _comments); + + Assert.IsFalse(result.Any(p => p.Id == 0)); + Assert.IsTrue(result.Count() == 2); + } + [TestMethod] public void CustomFiltersWithOperatorsWork() { @@ -272,6 +286,19 @@ namespace SieveUnitTests Assert.IsFalse(result.First().Id == 0); } + [TestMethod] + public void CustomGenericSortsWork() + { + var model = new SieveModel() + { + Sorts = "Oldest", + }; + + var result = _processor.Apply(model, _posts); + + Assert.IsTrue(result.Last().Id == 0); + } + [TestMethod] public void MethodNotFoundExceptionWork() { diff --git a/SieveUnitTests/Services/SieveCustomFilterMethods.cs b/SieveUnitTests/Services/SieveCustomFilterMethods.cs index 68a6132..5b6eecc 100644 --- a/SieveUnitTests/Services/SieveCustomFilterMethods.cs +++ b/SieveUnitTests/Services/SieveCustomFilterMethods.cs @@ -32,5 +32,11 @@ namespace SieveUnitTests.Services { return source; } + + public IQueryable Latest(IQueryable source, string op, string[] values) where T : BaseEntity + { + var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14)); + return result; + } } } diff --git a/SieveUnitTests/Services/SieveCustomSortMethods.cs b/SieveUnitTests/Services/SieveCustomSortMethods.cs index 8fd3f17..12bdb7f 100644 --- a/SieveUnitTests/Services/SieveCustomSortMethods.cs +++ b/SieveUnitTests/Services/SieveCustomSortMethods.cs @@ -16,5 +16,14 @@ namespace SieveUnitTests.Services return result; } + + public IQueryable Oldest(IQueryable source, bool useThenBy, bool desc) where T : BaseEntity + { + var result = useThenBy ? + ((IOrderedQueryable)source).ThenByDescending(p => p.DateCreated) : + source.OrderByDescending(p => p.DateCreated); + + return result; + } } }