FilterBuilder 1.2.0

dotnet add package FilterBuilder --version 1.2.0                
NuGet\Install-Package FilterBuilder -Version 1.2.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="FilterBuilder" Version="1.2.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FilterBuilder --version 1.2.0                
#r "nuget: FilterBuilder, 1.2.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 FilterBuilder as a Cake Addin
#addin nuget:?package=FilterBuilder&version=1.2.0

// Install FilterBuilder as a Cake Tool
#tool nuget:?package=FilterBuilder&version=1.2.0                

Filter Builder Documentation

Introduction

Filter Builder is an ASP.NET library designed to simplify the creation and management of dynamic query filters using LINQ. It is ideal for applications requiring complex search functionalities with dynamic user inputs, allowing seamless integration across various data sources.

Features

  • Dynamic Filter Creation: Enables the creation of LINQ expressions for dynamic query generation directly from user inputs.
  • Flexibility with Data Sources: Can be used with any data source compatible with LINQ, enhancing its versatility.
  • Easy Integration: Easily integrates into existing projects with minimal setup.

Getting Started

Prerequisites

  • .NET Core 3.1 or later
  • Visual Studio 2019 or later

Installation

Before you can use the Filter Builder, you need to install it along with its dependencies via NuGet. Here are the commands to install each package:

  1. Akinzekeel.ExpressionBuilder: This package helps in building complex LINQ expressions dynamically:

    Install-Package Akinzekeel.ExpressionBuilder
    
  2. Microsoft.EntityFrameworkCore.DynamicLinq: This package extends Entity Framework capabilities to support dynamic LINQ operations:

    Install-Package Microsoft.EntityFrameworkCore.DynamicLinq
    
  3. Newtonsoft.Json: A popular high-performance JSON framework for .NET:

    Install-Package Newtonsoft.Json
    
  4. Microsoft.AspNetCore.Mvc.NewtonsoftJson: Adds Newtonsoft.Json support to ASP.NET Core MVC:

    Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
    

Ensure all these packages are installed to make full use of the Filter Builder in your projects.

Configuration

After installing the necessary packages, you need to configure your ASP.NET Core application to use Newtonsoft.Json for handling JSON operations. This is done in the Program.cs file of your project. Add the following line of code in the Program.cs to set up Newtonsoft.Json:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers().AddNewtonsoftJson();

Creating Filters with Filter Builder

The Filter Builder library provides a powerful and flexible way to build dynamic query filters based on your model's properties. Understanding how to specify these filters is crucial for effective use.

Key Expression Syntax (KeyExp)

The library uses a specific convention for referencing properties in the filter criteria, known as KeyExp. Here's how to address different types of properties:

  • Direct Properties: Reference any direct value property by its name.
    • Example: Id, Name, Gender
  • Nested Properties: For properties within a reference type, use dot notation.
    • Example: Address.City, Employer.Name
  • List Properties: To filter based on properties of items within a list, use the property name followed by the item's property in brackets.
    • Example: Contacts[Type], Contacts[Value]

Supported Operations

The operations available are grouped by the type of data they are meant to work with, ensuring that you can apply relevant filters based on the property type:

Default Operations
  • Equal
  • NotEqual
Text Operations
  • Contains
  • DoesNotContain
  • EndsWith
  • IsEmpty
  • IsNotEmpty
  • IsNotNull
  • IsNotNullNorWhiteSpace
  • IsNull
  • IsNullOrWhiteSpace
  • NotEqual
  • StartsWith
Number Operations
  • Between
  • Equal
  • GreaterThan
  • GreaterThanOrEqual
  • LessThan
  • LessThanOrEqual
  • NotEqual
Boolean Operations
  • Equal
  • NotEqual
Date Operations
  • Between
  • Equal
  • GreaterThan
  • GreaterThanOrEqual
  • LessThan
  • LessThanOrEqual
  • NotEqual

Connectors

  • And : Used to combine multiple conditions where all conditions must be true for the combined result to be true. This is the primary logical connector used to ensure that all specified conditions are met.

  • Or : Used to combine multiple conditions where at least one of the conditions must be true for the combined result to be true. This connector allows for more flexibility by satisfying any one of the multiple specified conditions.

Specifying Values

  • Value : Specify the primary value for the operation.
  • Value2 : Used for operations like Between to specify the secondary value.

Filter Usage

Here is how you would build a filter using the Filter Builder in an ASP.NET application:

  • Using Filter Builder in Code with Defined Class Properties

    To effectively use the Filter Builder directly in your code, you should define a class with properties that match the names and types of the entity properties you want to filter. This ensures that the filter conditions you set up will correctly map to the corresponding fields in your database.

    Step 1: Defining the Model Class

    First, define a class that represents the model or database entity you are working with.

    // Entity Model
    public class User
    {
        public int Age { get; set; }
        public bool IsActive { get; set; }
        public string Name { get; set; }
        public BirthData Birth { get; set; }
        public List<Contact> Contacts { get; private set; }
    }
    
    public class BirthData
    {
        public DateTime Date { get; set; }
        public string Country { get; set; }
    }
    
    public class Contact
    {
        public string Value { get; set; }
        public string Comments { get; set; }
    }
    
    Step 2: Using the Filter Class to Create a Filter DTO

    Next, define a DTO (Data Transfer Object) that utilizes the Filter class for each property you might want to filter on within the User class, And ensure that property names and types in this class exactly match those in your database entity.

    public class UserFilterDTO
    {
        // Using default filter
        public int Age { get; set; }
        public bool IsActive { get; set; }
    
        // Using Advance filter
        public Filter<string> Name { get; set; }
        public Filter<string> Birth { get; set; }
        public Filter<string> Contact { get; set; }
    }
    
    Step 3: Filtering Entities Using the Filter DTO

    With the UserFilterDTO set up, you can now build filter expressions based on this DTO and apply them to filter data in your application.

    // Instantiate the filter DTO and set filter conditions
    var filterDto = new UserFilterDTO
    {
        //Equals Default logical operation if not set with all type except string and date
        Age = 19, 
        IsActive = true,
    
    
        Name = new Filter<string> {
            KeyExp = "Name", // Default key expression if not set
            Value = "Moh",
            Operation = "StartsWith",
            Connector="And" // Default logical connector if not set
        },
    
        BirthCountry = new Filter<string> {
            KeyExp = "Birth.Country", // This will filter by the country of the birth date 
            Value = "USA",
            Operation = "Equal"
        },
    
        ContactValue = new Filter<string> {
            KeyExp = "Contacts[Value]", // This will filter by each contact's value in the contacts list
            Value = "@example.com",
            Operation = "Contains" // Default logical connector if not set
        }
    };
    
    // Use the filter DTO to build a filter expression and fetch filtered results
    var filterExpression = FilterExpression.Build<User, UserFilterDTO>(filterDto);
    var filteredUsers = dbContext.Users.Where(filterExpression).ToList();
    return filteredUsers;
    
    
  • Implementing Filter Requests in API Controllers

    Integrating advanced filtering into an API involves receiving filter parameters through request models. Below is a guide on setting up your API controller to handle complex filters using the UserFilterDTO and how to manage JSON serialization issues by extending a base class.

    Step 1: Extend UserFilterDTO from FilterSearch

    Modify UserFilterDTO to inherit from FilterSearch to utilize advanced filtering capabilities and manage JSON serialization effectively.

    public class UserFilterDTO : FilterSearch
    {
        public int Age { get; set; }
        public bool IsActive { get; set; }
        public string Name { get; set; }
        public string Birth { get; set; }
        public string Contact { get; set; }
    }
    
    Step 2: API Controller Setup

    Set up an API controller that includes an action method to receive the UserFilterDTO. This method will use the model to filter data based on the provided criteria.

    [ApiController]
    [Route("[controller]")]
    public class UsersController : ControllerBase
    {
        private readonly DbContext dbContext;
    
        public UsersController(DbContext dbContext)
        {
            this.dbContext = dbContext;
        }
    
        [HttpPost("filter")]
        public ActionResult<List<User>> FilterUsers([FromBody] UserFilterDTO filterDto)
        {
            var filterExpression = FilterExpression.Build<User, UserFilterDTO>(filterDto);
            var filteredUsers = dbContext.Users.Where(filterExpression).ToList();
            return Ok(filteredUsers);
        }
    }
    
    
    Sending a Filter Request Using Body Form

    To send a request to the API with the filter contained in the body of the request, you can use tools like Postman, or write a client-side script using fetch API or similar libraries.

    Example Enter the JSON Body
    {
        "Age": 19,
        "IsActive": true,
        "Name": {"Value": "John", "Operation": "StartsWith"},
        // When using date type or value2, Between is defult logic operation
        "Birth": {"KeyExp":"Birth.Date","Value": "1990-01-01",
        "Value2":"1994-01-01", "Operation": "Between"}, 
        "Contact": {"Value": "info@example.com", "Operation": "Contains"},
    }
    
  • Setting Custom Null Values for Filters

    To handle custom null values for different data types in your filters, you can create a class that initializes these values and then inherit this class from FilterSearch.

    Define the FilterSearchWithNullableValue Class This class will extend FilterSearch and set default null values for various types.

    public class FilterSearchWithNullableValue : FilterSearch
    {
        public FilterSearchWithNullableValue()
        {
            NullValue = new List<(Type, object)> {
                (typeof(int), 0),
                (typeof(long), 0),
                (typeof(float), 0),
                (typeof(double), 0),
                (typeof(string), ""),
                (typeof(DateTime), DateTime.MinValue),
                (typeof(DateTime?), null)
            };
        }
    }
    

    Extend UserFilterDTO from FilterSearchWithNullableValue, allowing it to use the custom null values defined.

    public class UserFilterDTO : FilterSearchWithNullableValue
    {
        public int Age { get; set; }
        public bool IsActive { get; set; }
        public string Name { get; set; }
        public string Birth { get; set; }
        public string Contact { get; set; }
    }
    

Overloads of FilterExpression.Build

The FilterExpression.Build method has several overloads to accommodate different filtering needs. Below are the available overloads and their descriptions, followed by examples for each.


1. Build<TEntity, TFilter>(TFilter filter)

Creates a filter expression based on a strongly-typed filter DTO.

Parameters:

  • filter: A strongly-typed DTO containing the filtering criteria.

Returns:

  • An Expression<Func<TEntity, bool>> representing the filter.

Example:

var filterDto = new UserFilterDTO
{
    Name = new Filter<string> { Value = "Ahmed", Operation = "Equals" }
};

var filterExpression = FilterExpression.Build<User, UserFilterDTO>(filterDto);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();

2. Build<TEntity, TFilter>(TFilter[] filters, params string[] stringExpressions)

Combines multiple filter objects and string expressions into a single filter expression.

Parameters:

  • filter: A strongly-typed DTO containing the filtering criteria.
  • stringExpressions: Additional filter criteria as string expressions.

Returns:

  • An Expression<Func<TEntity, bool>> representing the combined filter.

Example:

var filterDto1 = new UserFilterDTO { Age = 19 };
var filterDto2 = new UserFilterDTO { IsActive =true };
var filters = new[] { filterDto1, filterDto2 };
var stringExpressions = new[] { "e => e.Name == \"Mohamed\"" };

var filterExpression = FilterExpression.Build<User, UserFilterDTO>(filters, stringExpressions);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();

3. Build<TEntity>(string stringExpression)

Parses a string expression into a LINQ expression.

Parameters:

  • stringExpressions: Additional filter criteria as string expressions.

Returns:

  • An Expression<Func<TEntity, bool>> representing the filter.

Example:

var stringExpression = "x=> x.Age == 25 && x.IsActive == true";
var filterExpression = FilterExpression.Build<User>(stringExpression);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();

4. Build<TEntity>(params Expression<Func<TEntity, bool>>[] expressions)

Combines multiple expressions into one by using the AndAlso logical operator.

Parameters:

  • expressions: An array of filter expressions.

Returns:

  • An Expression<Func<TEntity, bool>> representing the combined filter.

Example:

Expression<Func<User, bool>> expr1 = user => user.Age == 25;
Expression<Func<User, bool>> expr2 = user => user.IsActive;

var filterExpression = FilterExpression.Build(expr1, expr2);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();

5. Build<TEntity>(Expression<Func<TEntity, bool>>[] filters, params string[] stringExpressions)

Combines filter expressions and string expressions into a single expression.

Parameters:

  • filters: An array of filter expressions.
  • stringExpressions: Additional filter criteria as string expressions.

Returns:

  • An Expression<Func<TEntity, bool>> representing the combined filter.

Example:

Expression<Func<User, bool>> expr1 = user => user.Age == 25;
var stringExpressions = new[] { "x=> x.IsActive == true" };

var filterExpression = FilterExpression.Build(new[] { expr1 }, stringExpressions);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();

6. Build<TEntity>(Expression<Func<TEntity, bool>> filter, string stringExpression)

Simplifies creating an expression from a single filter and a string expression.

Parameters:

  • filter : A filter expression.
  • stringExpression : A filter criterion as a string expression.

Returns:

  • An Expression<Func<TEntity, bool>> representing the combined filter.

Example:

Expression<Func<User, bool>> filter = user => user.Age == 25;
var stringExpression = "x => x.IsActive == true";

var filterExpression = FilterExpression.Build(filter, stringExpression);
var filteredUsers = dbContext.Users.Where(filterExpression).ToList();


Extensions and Options

The FilterExpression class can be extended with additional methods to provide more flexibility and functionality. Below are some useful extension methods for enhancing the capabilities of FilterExpression.


1. TranslateExpression

Recursively translates an expression to optimize or modify it, making it suitable for caching. This method ensures that the expression can be serialized or used as a cache key in Redis.

Example Usage:

Expression<Func<User, bool>> expr = user => user.Age == 25 && user.IsActive;
var translatedExpr = expr.TranslateExpression();

2. CleanExpression

Cleans an expression to simplify it, making it more suitable for scenarios like Redis caching.

Example Usage:

Expression<Func<User, bool>> expr = user => user.Age == 25 && user.IsActive;
var cleanedExpr = expr.CleanExpression();

3. ApplyOwnership

Modifies an expression to enforce ownership rules based on the provided RequestInfo.

Example Usage:

var requestInfo = new RequestInfo
{
    TableName = "User",
    Ownership = "e => (e.UserId == 32)",
    UserStores = "1,2,3"
};

Expression<Func<User, bool>> expr = user => user.IsActive;
var ownershipExpr = expr.ApplyOwnership(requestInfo);

Utility Methods and Functions

The UtilityMethods class provides a set of helper functions that assist with various tasks such as retrieving property values, converting types, and comparing objects. These utility methods are used throughout the FilterExpression and other related classes to facilitate dynamic filtering and expression building.


1. GetObjectValue

Retrieves the value of a specified property from an object.

public static object GetObjectValue(object anonymousObject, string propertyName)

Parameters:

  • anonymousObject : The object from which to retrieve the property value.
  • propertyName : The name of the property to retrieve.

Returns:

  • The value of the specified property, or null if not found.

Example:

var user = new { Name = "John", Age = 30 };
var name = UtilityMethods.GetObjectValue(user, "Name"); // Returns "John"

2. AreEqual

Compares two objects for equality, treating them as strings.

public static bool AreEqual(object obj1, object obj2)

Parameters:

  • obj1 : The first object to compare.
  • obj2 : The second object to compare.

Returns:

  • True if the objects are equal; otherwise, false.

Example:

var isEqual = UtilityMethods.AreEqual("hello", "HELLO"); // Returns true

3. ConvertFromObject

Converts an object to a specified target type TTarget.

public static TTarget ConvertFromObject<TTarget>(object source)

Parameters:

  • source : The source object to convert.

Returns:

  • The converted object of type TTarget, or default if conversion is not possible.

Example:

int number = UtilityMethods.ConvertFromObject<int>("123"); // Returns 123

4. GetDefaultValue

Gets the default value for a specified type.

public static object GetDefaultValue(Type type)

Parameters:

  • type : The type for which to get the default value.

Returns:

  • The default value for the type.

Example:

var defaultInt = UtilityMethods.GetDefaultValue(typeof(int)); // Returns 0

5. ConvertValueToPropertyType

Converts a value to the type of a specified property.

public static object ConvertValueToPropertyType(PropertyInfo property, object value)

Parameters:

  • property : The property whose type will be used for conversion.
  • value : The value to convert.

Returns:

  • The value converted to the property's type, or null if conversion fails.

Example:

PropertyInfo propertyInfo = typeof(User).GetProperty("Age");
var convertedValue = UtilityMethods.ConvertValueToPropertyType(propertyInfo, "25"); // Returns 25 (int)

6. IsInheritedFrom

Checks if a type inherits from a specified base type.

public static bool IsInheritedFrom(Type objectType)

Parameters:

  • objectType : The type to check.

Returns: True if objectType inherits from the base type; otherwise, false.

Example:

bool isInherited = UtilityMethods.IsInheritedFrom(typeof(MyCustomFilter)); // Checks if MyCustomFilter inherits from FilterSearch

Thank You

Thank you for using the FilterExpression library. If you have any questions, suggestions, or would like to contribute to the project, your feedback and contributions are always welcome!

Happy filtering!

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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.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

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
1.2.0 59 2/20/2025
1.1.3 47 1/23/2025
1.1.2 82 12/1/2024
1.1.1 78 11/27/2024
1.1.0 108 9/17/2024
1.0.1 82 9/8/2024
1.0.0 84 7/28/2024

- Added **filter grouping** support to organize filters under shared keys.
- Enhanced **entity relationship filtering**, supporting **One-to-One** and **One-to-Many** relationships.
- Improved **case-insensitive filtering** for connectors (`And`, `Or`).
- Optimized **query execution** for better performance and efficiency.
- Bug fixes and minor stability improvements.