zackattackz.FilterExpressionCreator
5.0.0-alpha.2
dotnet add package zackattackz.FilterExpressionCreator --version 5.0.0-alpha.2
NuGet\Install-Package zackattackz.FilterExpressionCreator -Version 5.0.0-alpha.2
<PackageReference Include="zackattackz.FilterExpressionCreator" Version="5.0.0-alpha.2" />
paket add zackattackz.FilterExpressionCreator --version 5.0.0-alpha.2
#r "nuget: zackattackz.FilterExpressionCreator, 5.0.0-alpha.2"
// Install zackattackz.FilterExpressionCreator as a Cake Addin #addin nuget:?package=zackattackz.FilterExpressionCreator&version=5.0.0-alpha.2&prerelease // Install zackattackz.FilterExpressionCreator as a Cake Tool #tool nuget:?package=zackattackz.FilterExpressionCreator&version=5.0.0-alpha.2&prerelease
C# / VB / .NET LINQ Filter Expression Creator
Dynamically creates lambda expressions to filter enumerable and database queries via System.Linq.Enumerable.Where(...)
.
Demo
Demo: https://filterexpressioncreator.schick-software.de/demo
API: https://filterexpressioncreator.schick-software.de/openapi/
Table of Content
- Getting Started
- Creating Filters
- Using With MVC Controllers
- Support for OpenAPI / Swashbuckle.AspNetCore
- Support for Newtonsoft.Json
- Advanced Scenarios
Getting Started
- Install the standard NuGet package into your ASP.NET Core application
Package Manager : Install-Package Schick.FilterExpressionCreator
CLI : dotnet add package Schick.FilterExpressionCreator
- Create a new filter
using FS.FilterExpressionCreator.Enums;
using FS.FilterExpressionCreator.Extensions;
using FS.FilterExpressionCreator.Filters;
public class Order
{
public int Number { get; set; }
public string Customer { get; set; }
}
var filter = new EntityFilter<Order>()
.Add(x => x.Number, FilterOperator.GreaterThan, 200)
.Add(x => x.Customer, "Joe");
// Show filter
System.Console.WriteLine(filter);
// x => (((x.Customer != null) AndAlso x.Customer.ToUpper().Contains("JOE")) AndAlso (x.Number > 200))
- Start filtering
using FS.FilterExpressionCreator.Enums;
using FS.FilterExpressionCreator.Extensions;
using FS.FilterExpressionCreator.Filters;
using System.Linq;
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)
var filteredOrders = dbContext.Orders.Where(filter).ToList();
Creating Filters
General
Filters can be added/replaced using operator/value(s) pairs
// Operator/Value(s): Customer contains 'Joe' or 'Doe'
filter.Add(x => x.Customer, FilterOperator.Contains, "Joe", "Doe");
// Operator/Value(s): Customer contains 'Joe' or equals 'Doe'
filter.Add(x => x.Customer, ValueFilter.Create(
FilterOperator.EqualCaseInsensitive, "Joe"),
ValueFilter.Create(FilterOperator.EqualCaseInsensitive, "Doe")
);
or via filter micro syntax
// Filter micro syntax: Customer contains 'Joe' or 'Doe'.
filter.Add(x => x.Customer, "~Joe,~Doe");
// Filter micro syntax: Customer contains 'Joe' or equals 'Doe'
filter.Add(x => x.Customer, "~Joe,=Doe");
Combine values with AND and OR
// OR: Customer contains 'Joe' or 'Doe'.
// Multiple values given to one call of Add/Replace are combined using conditional `OR`.
filter.Add(x => x.Customer, "~Joe,~Doe");
// AND: Customer contains 'Joe' and 'Doe'.
// Multiple calls to Add/Replace are combined using conditional `AND`.
filter.Add(x => x.Customer, "~Joe");
filter.Add(x => x.Customer, "~Doe");
Nested Filters
Filtering nested objects/lists is supported.
Nested Objects
Nested objects are filtered directly
using FS.FilterExpressionCreator.Enums;
using FS.FilterExpressionCreator.Extensions;
using FS.FilterExpressionCreator.Filters;
using System.Collections.Generic;
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 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 itemFilter = new EntityFilter<OrderItem>()
.Add(x => x.Article, "==Laptop");
var filter = new EntityFilter<Order>()
.Add(x => x.Items, itemFilter);
// Print
System.Console.WriteLine(filter);
// x => ((x.Items != null) AndAlso x.Items.Any(x => (x.Article == "Laptop")))
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 |
Filter to null
To filter to == null
/ != null
special filter operators exists
// Filtering for values are NULL
filter.Add(x => x.Customer, FilterOperator.IsNull);
// Filtering for values are NOT NULL
filter.Add(x => x.Customer, FilterOperator.NotNull, "values", "are", "ignored");
While filtering for == null
/ != null
, given values are ignored.
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 contains 1 or 2 |
=1\,2 | Filtered value equals '1,2' |
<4,>10 | Filtered value is less than 4 or greater than 10 |
~Joe<br />~Doe | Filtered value contains Joe and Doe |
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
).
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
using FS.FilterExpressionCreator.Models;
// 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.CreateFilter(configuration);
// Filter IEnumerable<T> by compiling filter expression
var filteredOrders = orders.Where(filterExpression.Compile()).ToList();
Interception
Creation of filter expression can be intercepted via IPropertyFilterInterceptor
. While implicit conversions to Func<TEntity, bool>
and Expression<Func<TEntity, bool>>
exists, explicit filter conversion is required to apply an interceptor.
An example can be found in the test code InterceptorTests
Default Configuration and Interception
The class EntityFilter
has static properties to provide a system-wide configuration and/or interceptors
/// <summary>
/// Gets or sets the default configuration. Can be used to set a system-wide configuration.
/// </summary>
public static FilterConfiguration DefaultConfiguration { get; set; } = new FilterConfiguration();
/// <summary>
/// Gets or sets the default interceptor. Can be used to set a system-wide interceptor.
/// </summary>
public static IPropertyFilterInterceptor DefaultInterceptor { get; set; }
Using With MVC Controllers
Model Binding
Model binding for MVC controllers is supported
using FS.FilterExpressionCreator.Filters;
[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
.
Filter Sets
Multiple entity filters can be combined to a set of filters using the EntityFilterSetAttribute
.
Instead of
using FS.FilterExpressionCreator.Filters;
[HttpGet]
public Task<List<Order>> GetOrders([FromQuery] EntityFilter<Order> order, EntityFilter<OrderItem> orderItem, ...) { }
you can write
using FS.FilterExpressionCreator.Filters;
using FS.FilterExpressionCreator.Abstractions.Attributes;
[HttpGet]
public Task<List<Order>> GetOrders([FromQuery] OrderFilter filter, ...)
{
var order = filter.Order;
var orderItem = filter.OrderItem;
}
[EntityFilterSet]
public class OrderFilter
{
public EntityFilter<Order> Order { get; set; }
public EntityFilter<OrderItem> OrderItem { get; set; }
}
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
using FS.FilterExpressionCreator.Abstractions.Attributes;
[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);
// ...
}
Retrieve HTTP query parameters from filter
Use the extension method ToQueryParams()
to convert an EntityFilter<TEntity>
back to it's HTTP query parameters
var filter = new EntityFilter<Order>()
.Add(x => x.Number, FilterOperator.GreaterThan, 200)
.Add(x => x.Customer, "Joe");
var queryParams = filter.ToQueryParams();
// Print query parameters
System.Console.WriteLine(filter);
// number=>200&customer=Joe
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.Abstractions.Extensions;
var extendedFilter = new[]
{
filter.CreateFilter(),
item => item.Items.Count > 2
}
.CombineWithConditionalAnd();
var filteredOrders = orders.Where(extendedFilter.Compile());
Product | Versions 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. |
-
.NETStandard 2.1
- Chronic.Core (>= 0.4.0)
- System.Text.Json (>= 6.0.7)
- zackattackz.FilterExpressionCreator.Abstractions (>= 5.0.0-alpha.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
5.0.0-alpha.2 | 136 | 2/23/2023 |