mirror of
https://github.com/Biarity/Sieve.git
synced 2025-07-28 05:03:22 +02:00
Base & test solution
This commit is contained in:
18
Sieve/Attributes/SieveAttribute.cs
Normal file
18
Sieve/Attributes/SieveAttribute.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
class SieveAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Override name used
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool CanSort { get; set; }
|
||||
public bool CanFilter { get; set; }
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Sieve
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
}
|
31
Sieve/Extensions/OrderByWithDirection.cs
Normal file
31
Sieve/Extensions/OrderByWithDirection.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Extensions
|
||||
{
|
||||
|
||||
public static class LinqExtensions
|
||||
{
|
||||
public static IOrderedEnumerable<TSource> OrderByWithDirection<TSource, TKey>
|
||||
(this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector,
|
||||
bool descending)
|
||||
{
|
||||
return descending ? source.OrderByDescending(keySelector)
|
||||
: source.OrderBy(keySelector);
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<TSource> OrderByWithDirection<TSource, TKey>
|
||||
(this IQueryable<TSource> source,
|
||||
Expression<Func<TSource, TKey>> keySelector,
|
||||
bool descending)
|
||||
{
|
||||
return descending ? source.OrderByDescending(keySelector)
|
||||
: source.OrderBy(keySelector);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
17
Sieve/Models/FilterOperator.cs
Normal file
17
Sieve/Models/FilterOperator.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public enum FilterOperator
|
||||
{
|
||||
Equals,
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
GreaterThanOrEqualTo,
|
||||
LessThanOrEqualTo,
|
||||
Contains,
|
||||
StartsWith
|
||||
}
|
||||
}
|
58
Sieve/Models/FilterTerm.cs
Normal file
58
Sieve/Models/FilterTerm.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public class FilterTerm
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Operator { get; set; }
|
||||
|
||||
[BindNever]
|
||||
public FilterOperator OperatorParsed {
|
||||
get {
|
||||
switch (Operator.Trim().ToLower())
|
||||
{
|
||||
case "equals":
|
||||
case "eq":
|
||||
case "==":
|
||||
return FilterOperator.Equals;
|
||||
case "lessthan":
|
||||
case "lt":
|
||||
case "<":
|
||||
return FilterOperator.LessThan;
|
||||
case "greaterthan":
|
||||
case "gt":
|
||||
case ">":
|
||||
return FilterOperator.GreaterThan;
|
||||
case "greaterthanorequalto":
|
||||
case "gte":
|
||||
case ">=":
|
||||
return FilterOperator.GreaterThanOrEqualTo;
|
||||
case "lessthanorequalto":
|
||||
case "lte":
|
||||
case "<=":
|
||||
return FilterOperator.LessThanOrEqualTo;
|
||||
case "contains":
|
||||
case "co":
|
||||
case "@=":
|
||||
return FilterOperator.Contains;
|
||||
case "startswith":
|
||||
case "sw":
|
||||
case "_=":
|
||||
return FilterOperator.StartsWith;
|
||||
default:
|
||||
return FilterOperator.Equals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public bool Descending { get; set; } = false;
|
||||
}
|
||||
}
|
20
Sieve/Models/SieveModel.cs
Normal file
20
Sieve/Models/SieveModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public class SieveModel
|
||||
{
|
||||
public IEnumerable<FilterTerm> Filter { get; set; }
|
||||
|
||||
public IEnumerable<SortTerm> Sort { get; set; }
|
||||
|
||||
[Range(1, Double.MaxValue)]
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
[Range(1, Double.MaxValue)]
|
||||
public int PageSize { get; set; } = 10;
|
||||
}
|
||||
}
|
10
Sieve/Models/SieveOptions.cs
Normal file
10
Sieve/Models/SieveOptions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public class SieveOptions
|
||||
{
|
||||
}
|
||||
}
|
14
Sieve/Models/SortTerm.cs
Normal file
14
Sieve/Models/SortTerm.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Models
|
||||
{
|
||||
public class SortTerm
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool Descending { get; set; } = false;
|
||||
}
|
||||
}
|
15
Sieve/Services/ISieveCustomFilterMethods.cs
Normal file
15
Sieve/Services/ISieveCustomFilterMethods.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Services
|
||||
{
|
||||
public interface ISieveCustomFilterMethods
|
||||
{
|
||||
}
|
||||
|
||||
public interface ISieveCustomFilterMethods<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
}
|
||||
}
|
15
Sieve/Services/ISieveCustomSortMethods.cs
Normal file
15
Sieve/Services/ISieveCustomSortMethods.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sieve.Services
|
||||
{
|
||||
public interface ISieveCustomSortMethods
|
||||
{
|
||||
}
|
||||
|
||||
public interface ISieveCustomSortMethods<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
}
|
||||
}
|
151
Sieve/Services/SieveProcessor.cs
Normal file
151
Sieve/Services/SieveProcessor.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Sieve.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Data.Entity;
|
||||
using System.Reflection;
|
||||
using Sieve.Attributes;
|
||||
using Sieve.Extensions;
|
||||
|
||||
namespace Sieve.Services
|
||||
{
|
||||
public class SieveProcessor<TEntity>
|
||||
where TEntity: class
|
||||
{
|
||||
private IOptions<SieveOptions> _options;
|
||||
private ISieveCustomSortMethods<TEntity> _customSortMethods;
|
||||
private ISieveCustomFilterMethods<TEntity> _customFilterMethods;
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomSortMethods<TEntity> customSortMethods,
|
||||
ISieveCustomFilterMethods<TEntity> customFilterMethods)
|
||||
{
|
||||
_options = options;
|
||||
_customSortMethods = customSortMethods;
|
||||
_customFilterMethods = customFilterMethods;
|
||||
}
|
||||
|
||||
public IEnumerable<TEntity> ApplyAll(SieveModel model, IQueryable<TEntity> source)
|
||||
{
|
||||
var result = source.AsNoTracking();
|
||||
|
||||
// Sort
|
||||
result = ApplySort(model, result);
|
||||
|
||||
// Filter
|
||||
result = ApplyFilter(model, result);
|
||||
|
||||
// Paginate
|
||||
result = ApplyPagination(model, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IQueryable<TEntity> ApplySort(SieveModel model, IQueryable<TEntity> result)
|
||||
{
|
||||
foreach (var sortTerm in model.Sort)
|
||||
{
|
||||
var property = GetSieveProperty(true, false, sortTerm.Name);
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
result = result.OrderByWithDirection(
|
||||
e => property.GetValue(e),
|
||||
sortTerm.Descending);
|
||||
}
|
||||
else
|
||||
{
|
||||
var customMethod = _customSortMethods.GetType()
|
||||
.GetMethod(sortTerm.Name);
|
||||
|
||||
if (customMethod != null)
|
||||
{
|
||||
result = result.OrderByWithDirection(
|
||||
e => customMethod.Invoke(_customSortMethods, new object[] { e }),
|
||||
sortTerm.Descending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IQueryable<TEntity> ApplyFilter(SieveModel model, IQueryable<TEntity> result)
|
||||
{
|
||||
foreach (var filterTerm in model.Filter)
|
||||
{
|
||||
var property = GetSieveProperty(false, true, filterTerm.Name);
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
var filterValue = Convert.ChangeType(filterTerm.Value, property.GetType());
|
||||
|
||||
switch (filterTerm.OperatorParsed)
|
||||
{
|
||||
case FilterOperator.Equals:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).Equals(filterValue));
|
||||
break;
|
||||
case FilterOperator.GreaterThan:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).CompareTo(filterValue) > 0);
|
||||
break;
|
||||
case FilterOperator.LessThan:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).CompareTo(filterValue) < 0);
|
||||
break;
|
||||
case FilterOperator.GreaterThanOrEqualTo:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).CompareTo(filterValue) >= 0);
|
||||
break;
|
||||
case FilterOperator.LessThanOrEqualTo:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).CompareTo(filterValue) <= 0);
|
||||
break;
|
||||
case FilterOperator.Contains:
|
||||
result = result.Where(e => ((string)property.GetValue(e)).Contains((string)filterValue));
|
||||
break;
|
||||
case FilterOperator.StartsWith:
|
||||
result = result.Where(e => ((string)property.GetValue(e)).StartsWith((string)filterValue));
|
||||
break;
|
||||
default:
|
||||
result = result.Where(e => ((IComparable)property.GetValue(e)).Equals(filterValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var customMethod = _customFilterMethods.GetType()
|
||||
.GetMethod(filterTerm.Name);
|
||||
|
||||
if (customMethod != null)
|
||||
{
|
||||
result = result.Where(
|
||||
e => (bool)customMethod.Invoke(_customFilterMethods, new object[] { e }));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IQueryable<TEntity> ApplyPagination(SieveModel model, IQueryable<TEntity> result)
|
||||
{
|
||||
result = result.Skip((model.Page - 1) * model.PageSize)
|
||||
.Take(model.PageSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
private PropertyInfo GetSieveProperty(bool canSortRequired, bool canFilterRequired, string name)
|
||||
{
|
||||
return typeof(TEntity).GetProperties().FirstOrDefault(p =>
|
||||
{
|
||||
if (p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute sieveAttribute)
|
||||
if ((canSortRequired ? sieveAttribute.CanSort : true) &&
|
||||
(canFilterRequired ? sieveAttribute.CanFilter : true) &&
|
||||
((sieveAttribute.Name ?? p.Name) == name))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,4 +4,10 @@
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EntityFramework" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user