Schick.FilterExpressionCreator 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Schick.FilterExpressionCreator --version 1.1.0
NuGet\Install-Package Schick.FilterExpressionCreator -Version 1.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Schick.FilterExpressionCreator" Version="1.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Schick.FilterExpressionCreator --version 1.1.0
#r "nuget: Schick.FilterExpressionCreator, 1.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Schick.FilterExpressionCreator as a Cake Addin
#addin nuget:?package=Schick.FilterExpressionCreator&version=1.1.0

// Install Schick.FilterExpressionCreator as a Cake Tool
#tool nuget:?package=Schick.FilterExpressionCreator&version=1.1.0

Filter Expression Creator

Library to dynamically create lambda expressions to filter lists and database queries.

Demo

Demo: https://filterexpressioncreator.schick-software.de/demo

API: https://filterexpressioncreator.schick-software.de/openapi/

Table of Content

Getting Started

  1. Install the standard NuGet package into your ASP.NET Core application
Package Manager : Install-Package Schick.FilterExpressionCreator
CLI : dotnet add package Schick.FilterExpressionCreator
  1. Create a new filter
using FS.FilterExpressionCreator.Models;
using FS.FilterExpressionCreator.Enums;
public class Order
{
    public int Number { get; set; }
    public string Customer { get; set; }
    
    public Address Address { get; set; }
    public List<OrderItem> Items { get; set; }
}

public record Address(string Street, string City) { }
public record OrderItem(int Position, string Article) { }
var filter = new EntityFilter<Order>()
    .Add(x => x.Customer, "Joe")
    .Add(x => x.Number, FilterOperator.GreaterThan, 200);

// Print
System.Console.WriteLine(filter);
// x => (((x.Customer != null) AndAlso x.Customer.ToUpper().Contains("JOE")) AndAlso (x.Number > 200))
  1. Start filtering
var orders = new[] {
    new Order { Customer = "Joe Miller", Number = 100 },
    new Order { Customer = "Joe Smith", Number = 200 },
    new Order { Customer = "Joe Smith", Number = 300 },
};

// Using IEnumerable<>
var filteredOrders = orders.Where(filter).ToList();
// filteredOrders equals
// new[] { new Order { Customer = "Joe Smith", Number = 300 } };

// Or using queryables (e.g. Entity Framework)
dbContext.Orders.Where(filter).ToList();

Creating Filters

Filters can be created using operator/value(s) pairs or via filter micro syntax

// Operator/Value(s): Customer contains 'Joe' or 'Doe'
filter.Add(x => x.Customer, FilterOperator.Contains, "Joe", "Doe");

// Filter micro syntax: Customer contains 'Joe' or 'Doe'
filter.Add(x => x.Customer, "~Joe,Doe");

Multiple Values

Multiple values are combined using conditional OR expect for operator NOT. For operator NOT given values are combined using conditional AND.

Nested Filters

Filtering nested objects/lists is supported.

Nested Objects

Nested objects are filtered directly

var addressFilter = new EntityFilter<Address>()
    .Add(x => x.City, "==Berlin");

var filter = new EntityFilter<Order>()
    .Add(x => x.Address, addressFilter);

// Print
System.Console.WriteLine(filter);
// x => ((x.Address != null) AndAlso (x.Address.City == "Berlin"))

Nested Lists

Nested lists are filtered using IEnumerable<T>.Any()

var positionFilter = new EntityFilter<OrderPosition>()
    .Add(x => x.Article, "==Laptop");

var filter = new EntityFilter<Order>()
    .Add(x => x.Positions, positionFilter);

// Print
System.Console.WriteLine(filter);
// x => ((x.Positions != null) AndAlso x.Positions.Any(x => (x.Article == "Laptop")))

Filter for null

To filter for == null / != null special filter operators exists

// Filtering for values are NULL
filter.Add(x => x.Name, FilterOperator.IsNull);

// Filtering for values are NOT NULL
filter.Add(x => x.Name, FilterOperator.NotNull, "values", "are", "ignored");

While filtering for == null / != null, given values are ignored.

Filter Operators

Operator Micro Syntax Description
Default Selects the operator according to the filtered type. When filtering string the default is Contains; otherwise EqualCaseInsensitive
Contains ~ Hits when the filtered property contains the filter value
EqualCaseInsensitive = Hits when the filtered property equals the filter value (case insensitive)
EqualCaseSensitive == Hits when the filtered property equals the filter value (case sensitive)
NotEqual ! Negates the Default operator. Operators other than Default can not be negated (currently)
LessThan < Hits when the filtered property is less than the filter value
LessThanOrEqual Hits when the filtered property is less than or equal to the filter value
GreaterThan > Hits when the filtered property is greater than the filter value
GreaterThanOrEqual >= Hits when the filtered property is greater than or equals the filter value
IsNull ISNULL Hits when the filtered property is null
NotNull NOTNULL Hits when the filtered property is not null

Configuration

Creation of filter expression can be configured via FilterConfiguration. While implicit conversions to Func<TEntity, bool> and Expression<Func<TEntity, bool>> exists, explicit filter conversion is required to apply a configuration

// Parse filter values using german locale (e.g. "5,5" => 5.5f).
var configuration = new FilterConfiguration { CultureInfo = new CultureInfo("de-DE") };
// Explicit filter conversion
var filterExpression = filter.CreateFilterExpression(configuration);
// Filter IEnumerable<T> by compiling filter expression
var filteredOrders = orders.Where(filterExpression.Compile()).ToList();

Filter Micro Syntax

The filter syntax consists of an operator shortcut (see filter operators above) and a list of comma separated values. When a value contains a comma itself, it must be escaped by a backslash. The backslash itself is escaped by another backslash.

Examples

Syntax Description
Joe For string filtered value contains 'Joe', for Enum filtered value is 'Joe'
~Joe Filtered value contains 'Joe', even for Enum
=1\,2 Filtered value equals '1,2'
ISNULL Filtered value is null
>one-week-ago For DateTime filtered value is greater than one week ago, for others types see above
2020 For DateTime filtered value is between 01/01/2020 and 12/31/2020, for others types see above
2020-01 For DateTime filtered value is between 01/01/2020 and 1/31/2020, for others types see above

Date/Time

Date/Time values can be given as a representation in the form of a fault-tolerant round-trip date/time pattern

2020/01/01 works as well as 2020-01-01-12-30

or thanks to nChronic.Core as natural language date string. More details can be found here: https://github.com/mojombo/chronic

tomorrow works as well as 3-months-ago-saturday-at-5-pm

Partial date strings are supported too

2020-01 filters date between 2020-01-01 and 2020-01-31.

Enumerations

Enum values can be filtered by it's numeric representation as well as by it's name.

When filtering using Contains the filter values are expanded ("~male" filters for male as well as female).

Using With MVC Controllers

Model Binding

Model binding for MVC controllers is supported

[HttpGet]
public Task<List<Order>> GetOrders([FromQuery] EntityFilter<Order> order, ...)

The above usage maps the query parameters OrderNumber and OrderCustomer to the filter parameter order.

Register Model Binders

Package Manager : Install-Package Schick.FilterExpressionCreator.Mvc
CLI : dotnet add package Schick.FilterExpressionCreator.Mvc
using FS.FilterExpressionCreator.Mvc.Extensions;
// Register required stuff by calling 'AddFilterExpressionsSupport()' on IMvcBuilder instance
services.AddControllers().AddFilterExpressionsSupport();

Configure Model Binding

By default parameters for properties of filtered entity are named <Entity><Property>.

By default all public non-complex properties (string, int, DateTime, ...) are recognized.

Parameters can be renamed or hidden using FilterAttribute and FilterEntityAttribute. For the code below OrderNumber is not mapped anymore and OrderCustomer becomes CustomerName

[FilterEntity(Prefix = "")]
public class Order
{
    [Filter(Visible = false)]
    public int Number { get; set; }

    [Filter(Name = "CustomerName")]
    public string Customer { get; set; }
}

Nested Objects/Lists

Nested objects and lists are not implicit recognized as parameters. But you can simply combine them

[HttpGet]
public Task<List<Order>> GetOrders(
    [FromQuery] EntityFilter<Order> order, 
    [FromQuery] EntityFilter<Address> address
)
{
    var filter = order.Add(x => x.Address, address);
    // ...
}

Support for OpenAPI / Swashbuckle.AspNetCore

Support for OpenAPI (formerly swagger) provided by Swashbuckle.AspNetCore is available.

Register OpenAPI Support

Package Manager : Install-Package Schick.FilterExpressionCreator.Swashbuckle
CLI : dotnet add package Schick.FilterExpressionCreator.Swashbuckle
using FS.FilterExpressionCreator.Swashbuckle.Extensions;
services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("V1", new OpenApiInfo { Title = $"API", Version = "V1" });
    // Register required stuff by calling 'AddFilterExpressionsSupport()' on SwaggerGenOptions instance
    options.AddFilterExpressionsSupport();
});

Register XML Documentation

To get descriptions for generated parameters from XML documentation, paths to documentation files can be provided

services
.AddSwaggerGen(options =>
{
    options.SwaggerDoc("V1", new OpenApiInfo { Title = $"API", Version = "V1" });
    // Register required stuff including XML documentatioin files
    var filterCreatorDoc = Path.Combine(AppContext.BaseDirectory, "FS.FilterExpressionCreator.xml");
    options.AddFilterExpressionsSupport(filterCreatorDoc);
    options.IncludeXmlComments(filterCreatorDoc);
});

Support for Newtonsoft.Json

By default System.Text.Json is used to serialize/convert Filter Expression Creator specific stuff. If you like to use Newtonsoft.Json instead you must register it

Package Manager : Install-Package Schick.FilterExpressionCreator.Mvc.Newtonsoft
CLI : dotnet add package Schick.FilterExpressionCreator.Mvc.Newtonsoft
using FS.FilterExpressionCreator.Mvc.Newtonsoft;
// Register support for Newtonsoft by calling 
// 'AddFilterExpressionsNewtonsoftSupport()' on IMvcBuilder instance
services.AddControllers().AddFilterExpressionsNewtonsoftSupport();

Advanced Scenarios

Deep Copy Filters

The EntityFilter<T> class supports deep cloning by calling the Clone() method

var copy = filter.Clone();

Cast Filters

Filters can be cast between entities, e.g. to convert them between DTOs and database models.

Properties are matched by type (check if assignable) and name (case sensitive)

var dtoFilter = new EntityFilter<OrderDto>().Add(...);
var orderFilter = dtoFilter.Cast<Order>();

Serialize Filters

Using System.Text.Json

Objects of type EntityFilter<T> can be serialized via System.Text.Json.JsonSerializer without further requirements

var json = JsonSerializer.Serialize(filter);
filter = JsonSerializer.Deserialize<EntityFilter<EntityFilter<Order>>>(json);

Using Newtonsoft.Json

When using Newtonsoft.Json additional converters are required

Package Manager : Install-Package Schick.FilterExpressionCreator.Newtonsoft
CLI : dotnet add package Schick.FilterExpressionCreator.Newtonsoft
using FS.FilterExpressionCreator.Newtonsoft.Extensions;
var json = JsonConvert.SerializeObject(filter, JsonConverterExtensions.NewtonsoftConverters);
filter = JsonConvert.DeserializeObject<EntityFilter<Order>>(json, JsonConverterExtensions.NewtonsoftConverters);

Combine Filter Expressions

To add custom checks to a filter either call .Where(...) again

var filteredOrders = orders
    .Where(filter)
    .Where(item => item.Items.Count > 2);

or where this isn't possible combine filters with CombineWithConditionalAnd

using FS.FilterExpressionCreator.Extensions;
var extendedFilter = new[]
    {
        filter.CreateFilterExpression(),
        item => item.Items.Count > 2
    }
    .CombineWithConditionalAnd();

var filteredOrders = orders.Where(extendedFilter.Compile());
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Schick.FilterExpressionCreator:

Package Downloads
Schick.FilterExpressionCreator.Newtonsoft

Library to dynamically create lambda expressions to filter lists and database queries

Schick.FilterExpressionCreator.Mvc

Library to dynamically create lambda expressions to filter lists and database queries

Schick.FilterExpressionCreator.Swashbuckle

Library to dynamically create lambda expressions to filter lists and database queries

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.6.1 153 2/10/2024
4.6.0 140 2/10/2024
4.5.0 503 6/13/2023
4.4.0 880 3/3/2023
4.3.0 581 2/17/2023
4.2.2 698 12/24/2022
4.2.1 718 11/24/2022
4.2.0 1,215 9/25/2022
4.1.1-beta1 240 9/3/2022
4.1.0 1,035 9/3/2022
4.0.1 1,603 3/22/2022
4.0.0 1,003 3/22/2022
3.1.0 1,141 12/31/2021
3.0.0 634 12/20/2021
2.3.1 629 12/5/2021
2.3.0 602 12/5/2021
2.2.1 861 11/21/2021
2.2.0 911 11/21/2021
2.1.1 764 10/16/2021
2.0.0 677 9/1/2021
1.2.0 699 8/30/2021
1.1.0 697 8/20/2021
1.0.3 678 8/18/2021
1.0.2 726 7/10/2021
1.0.1 686 6/4/2021
1.0.0 783 5/30/2021