Added case-insensitive operators and started unit tests project

This commit is contained in:
Biarity
2018-02-10 10:26:32 +10:00
parent aa6a836cfb
commit 2796197cc9
13 changed files with 356 additions and 88 deletions

View File

@@ -13,6 +13,6 @@ namespace Sieve.Models
GreaterThanOrEqualTo,
LessThanOrEqualTo,
Contains,
StartsWith
StartsWith,
}
}

View File

@@ -9,6 +9,9 @@ namespace Sieve.Models
{
private string _filter;
private string[] operators = new string[] {
"==*",
"@=*",
"_=*",
"==",
"!=",
">",
@@ -16,7 +19,8 @@ namespace Sieve.Models
">=",
"<=",
"@=",
"_=" };
"_="
};
public FilterTerm(string filter)
{
@@ -67,6 +71,7 @@ namespace Sieve.Models
switch (Operator.Trim().ToLower())
{
case "==":
case "==*":
return FilterOperator.Equals;
case "!=":
return FilterOperator.NotEquals;
@@ -79,8 +84,10 @@ namespace Sieve.Models
case "<=":
return FilterOperator.LessThanOrEqualTo;
case "@=":
case "@=*":
return FilterOperator.Contains;
case "_=":
case "_=*":
return FilterOperator.StartsWith;
default:
return FilterOperator.Equals;

View File

@@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Text;

View File

@@ -49,6 +49,14 @@ namespace Sieve.Services
_options = options;
}
/// <summary>
/// Apply filtering, sorting, and pagination parameters found in `model` to `source`
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="model">An instance of ISieveModel</param>
/// <param name="source">Data source</param>
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
/// <returns>Returns a transformed version of `source`</returns>
public IQueryable<TEntity> ApplyAll<TEntity>(ISieveModel model, IQueryable<TEntity> source, object[] dataForCustomMethods = null)
{
var result = source;
@@ -67,7 +75,112 @@ namespace Sieve.Services
return result;
}
/// <summary>
/// Apply filtering parameters found in `model` to `source`
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="model">An instance of ISieveModel</param>
/// <param name="source">Data source</param>
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
/// <returns>Returns a transformed version of `source`</returns>
public IQueryable<TEntity> ApplyFiltering<TEntity>(ISieveModel model, IQueryable<TEntity> result, object[] dataForCustomMethods = null)
{
if (model?.FiltersParsed == null)
return result;
foreach (var filterTerm in model.FiltersParsed)
{
var property = GetSieveProperty<TEntity>(false, true, filterTerm.Name);
if (property != null)
{
var converter = TypeDescriptor.GetConverter(property.PropertyType);
var parameter = Expression.Parameter(typeof(TEntity), "e");
dynamic filterValue = Expression.Constant(
converter.CanConvertFrom(typeof(string)) ?
converter.ConvertFrom(filterTerm.Value) :
Convert.ChangeType(filterTerm.Value, property.PropertyType));
dynamic propertyValue = Expression.PropertyOrField(parameter, property.Name);
if (filterTerm.Operator.Contains("*"))
{
propertyValue = Expression.Call(propertyValue,
typeof(string).GetMethods()
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
filterValue = Expression.Call(filterValue,
typeof(string).GetMethods()
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
}
Expression comparison;
switch (filterTerm.OperatorParsed)
{
case FilterOperator.Equals:
comparison = Expression.Equal(propertyValue, filterValue);
break;
case FilterOperator.NotEquals:
comparison = Expression.NotEqual(propertyValue, filterValue);
break;
case FilterOperator.GreaterThan:
comparison = Expression.GreaterThan(propertyValue, filterValue);
break;
case FilterOperator.LessThan:
comparison = Expression.LessThan(propertyValue, filterValue);
break;
case FilterOperator.GreaterThanOrEqualTo:
comparison = Expression.GreaterThanOrEqual(propertyValue, filterValue);
break;
case FilterOperator.LessThanOrEqualTo:
comparison = Expression.LessThanOrEqual(propertyValue, filterValue);
break;
case FilterOperator.Contains:
comparison = Expression.Call(propertyValue,
typeof(string).GetMethods()
.First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
filterValue);
break;
case FilterOperator.StartsWith:
comparison = Expression.Call(propertyValue,
typeof(string).GetMethods()
.First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
filterValue); break;
default:
comparison = Expression.Equal(propertyValue, filterValue);
break;
}
result = result.Where(Expression.Lambda<Func<TEntity, bool>>(
comparison,
parameter));
}
else
{
result = ApplyCustomMethod(result, filterTerm.Name, _customFilterMethods,
new object[] {
result,
filterTerm.Operator,
filterTerm.Value
}, dataForCustomMethods);
}
}
return result;
}
/// <summary>
/// Apply sorting parameters found in `model` to `source`
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="model">An instance of ISieveModel</param>
/// <param name="source">Data source</param>
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
/// <returns>Returns a transformed version of `source`</returns>
public IQueryable<TEntity> ApplySorting<TEntity>(ISieveModel model, IQueryable<TEntity> result, object[] dataForCustomMethods = null)
{
if (model?.SortsParsed == null)
@@ -97,84 +210,15 @@ namespace Sieve.Services
return result;
}
public IQueryable<TEntity> ApplyFiltering<TEntity>(ISieveModel model, IQueryable<TEntity> result, object[] dataForCustomMethods = null)
{
if (model?.FiltersParsed == null)
return result;
foreach (var filterTerm in model.FiltersParsed)
{
var property = GetSieveProperty<TEntity>(false, true, filterTerm.Name);
if (property != null)
{
var converter = TypeDescriptor.GetConverter(property.PropertyType);
var parameter = Expression.Parameter(typeof(TEntity), "e");
var filterValue = Expression.Constant(
converter.CanConvertFrom(typeof(string)) ?
converter.ConvertFrom(filterTerm.Value) :
Convert.ChangeType(filterTerm.Value, property.PropertyType));
var propertyValue = Expression.PropertyOrField(parameter, property.Name);
Expression comparison;
switch (filterTerm.OperatorParsed)
{
case FilterOperator.Equals:
comparison = Expression.Equal(propertyValue, filterValue);
break;
case FilterOperator.NotEquals:
comparison = Expression.NotEqual(propertyValue, filterValue);
break;
case FilterOperator.GreaterThan:
comparison = Expression.GreaterThan(propertyValue, filterValue);
break;
case FilterOperator.LessThan:
comparison = Expression.LessThan(propertyValue, filterValue);
break;
case FilterOperator.GreaterThanOrEqualTo:
comparison = Expression.GreaterThanOrEqual(propertyValue, filterValue);
break;
case FilterOperator.LessThanOrEqualTo:
comparison = Expression.LessThanOrEqual(propertyValue, filterValue);
break;
case FilterOperator.Contains:
comparison = Expression.Call(propertyValue,
typeof(string).GetMethods()
.First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
filterValue);
break;
case FilterOperator.StartsWith:
comparison = Expression.Call(propertyValue,
typeof(string).GetMethods()
.First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
filterValue); break;
default:
comparison = Expression.Equal(propertyValue, filterValue);
break;
}
result = result.Where(Expression.Lambda<Func<TEntity, bool>>(
comparison,
parameter));
}
else
{
result = ApplyCustomMethod(result, filterTerm.Name, _customFilterMethods,
new object[] {
result,
filterTerm.Operator,
filterTerm.Value
}, dataForCustomMethods);
}
}
return result;
}
/// <summary>
/// Apply pagination parameters found in `model` to `source`
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="model">An instance of ISieveModel</param>
/// <param name="source">Data source</param>
/// <param name="dataForCustomMethods">Additional data that will be passed down to custom methods</param>
/// <returns>Returns a transformed version of `source`</returns>
public IQueryable<TEntity> ApplyPagination<TEntity>(ISieveModel model, IQueryable<TEntity> result)
{
var page = model?.Page ?? 1;
@@ -188,7 +232,7 @@ namespace Sieve.Services
return result;
}
private PropertyInfo GetSieveProperty<TEntity>(bool canSortRequired, bool canFilterRequired, string name)
private PropertyInfo GetSieveProperty<TEntity>(bool canSortRequired, bool canFilterRequired, string name)
{
return typeof(TEntity).GetProperties().FirstOrDefault(p =>
{

View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Sieve</id>
<version>1.3.7</version>
<version>1.3.8</version>
<title>Sieve</title>
<authors>Biarity</authors>
<owners>Biarity</owners>
@@ -13,7 +13,7 @@
<description>
Sieve is a simple, clean, and extensible framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box. Most common use case would be for serving ASP.NET Core GET queries. Documentation available on GitHub: https://github.com/Biarity/Sieve/
</description>
<releaseNotes>Custom operators no longer supported for simplicity</releaseNotes>
<releaseNotes>Filter before sort</releaseNotes>
<copyright>Copyright 2018</copyright>
<tags>aspnetcore filter sort page paginate sieve search</tags>
<dependencies>