FilterEngine.Core 1.0.0

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package FilterEngine.Core --version 1.0.0
                    
NuGet\Install-Package FilterEngine.Core -Version 1.0.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="FilterEngine.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FilterEngine.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="FilterEngine.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FilterEngine.Core --version 1.0.0
                    
#r "nuget: FilterEngine.Core, 1.0.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.
#:package FilterEngine.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FilterEngine.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=FilterEngine.Core&version=1.0.0
                    
Install as a Cake Tool

FilterEngine.Core

NuGet Version NuGet Downloads License: MIT

FilterEngine.Core is a powerful and flexible dynamic filtering engine for .NET applications. It enables you to perform runtime filtering, sorting, and pagination operations on IQueryable collections.

🚀 Features

  • Dynamic Filtering: 13 different filtering operators
  • Flexible Sorting: Ascending/descending sort support
  • Pagination: Performant page-based data retrieval
  • Type Safety: Compile-time type safety
  • Entity Framework Compatible: Works seamlessly with EF Core
  • Performance Focused: Fast queries based on expression trees
  • Easy to Use: Simple API with extension methods

📦 Installation

Package Manager Console

Install-Package FilterEngine.Core

.NET CLI

dotnet add package FilterEngine.Core

PackageReference

<PackageReference Include="FilterEngine.Core" Version="1.0.0" />

🔧 Quick Start

1. Import Namespace

using FilterEngine.Core.Extensions;
using FilterEngine.Core.Models;

2. Simple Filtering

var products = dbContext.Products.AsQueryable();

var filterRequest = new FilterRequest
{
    Filters = new List<Filter>
    {
        new() { Field = "Name", Operator = FilterOperator.Contains, Value = "laptop" },
        new() { Field = "Price", Operator = FilterOperator.GreaterThan, Value = "500" }
    }
};

var filteredProducts = products.ApplyFiltering(filterRequest);

3. Filtering + Pagination

var filterRequest = new FilterRequest
{
    Filters = new List<Filter>
    {
        new() { Field = "Category", Operator = FilterOperator.Equals, Value = "Electronics" }
    },
    SortField = "Price",
    SortDirection = SortDirection.Desc,
    Page = 1,
    PageSize = 10
};

var result = products.ApplyFilteringWithPaging(filterRequest);

Console.WriteLine($"Total: {result.TotalCount}");
Console.WriteLine($"Page: {result.Page}/{result.TotalPages}");
foreach (var product in result.Data)
{
    Console.WriteLine($"{product.Name} - ${product.Price}");
}

📖 Detailed Usage

Filter Operators

Operator Description Example
Equals Equals { Field = "Status", Operator = Equals, Value = "Active" }
NotEquals Not equals { Field = "Status", Operator = NotEquals, Value = "Deleted" }
GreaterThan Greater than { Field = "Price", Operator = GreaterThan, Value = "100" }
GreaterThanOrEqual Greater than or equal { Field = "Age", Operator = GreaterThanOrEqual, Value = "18" }
LessThan Less than { Field = "Stock", Operator = LessThan, Value = "10" }
LessThanOrEqual Less than or equal { Field = "Discount", Operator = LessThanOrEqual, Value = "50" }
Contains Contains (String) { Field = "Description", Operator = Contains, Value = "phone" }
StartsWith Starts with (String) { Field = "Code", Operator = StartsWith, Value = "PRD" }
EndsWith Ends with (String) { Field = "Email", Operator = EndsWith, Value = "@gmail.com" }
IsNull Is null { Field = "DeletedDate", Operator = IsNull, Value = null }
IsNotNull Is not null { Field = "CreatedBy", Operator = IsNotNull, Value = null }
In In list { Field = "CategoryId", Operator = In, Value = "1,2,3" }
Between Between range { Field = "CreatedDate", Operator = Between, Value = "2023-01-01,2023-12-31" }

Advanced Examples

Multiple Filters
var complexFilter = new FilterRequest
{
    Filters = new List<Filter>
    {
        new() { Field = "Category", Operator = FilterOperator.In, Value = "Electronics,Books,Clothing" },
        new() { Field = "Price", Operator = FilterOperator.Between, Value = "50,500" },
        new() { Field = "Name", Operator = FilterOperator.Contains, Value = "pro" },
        new() { Field = "IsActive", Operator = FilterOperator.Equals, Value = "true" },
        new() { Field = "DeletedDate", Operator = FilterOperator.IsNull, Value = null }
    },
    SortField = "CreatedDate",
    SortDirection = SortDirection.Desc,
    Page = 2,
    PageSize = 20
};

var result = products.ApplyFilteringWithPaging(complexFilter);
Filtering with Enum Values
public enum ProductStatus { Active, Inactive, Discontinued }

var enumFilter = new FilterRequest
{
    Filters = new List<Filter>
    {
        new() { Field = "Status", Operator = FilterOperator.Equals, Value = "Active" },
        new() { Field = "Status", Operator = FilterOperator.In, Value = "Active,Inactive" }
    }
};
DateTime Filtering
var dateFilter = new FilterRequest
{
    Filters = new List<Filter>
    {
        new() { Field = "CreatedDate", Operator = FilterOperator.GreaterThan, Value = "2023-01-01" },
        new() { Field = "UpdatedDate", Operator = FilterOperator.Between, Value = "2023-06-01,2023-12-31" }
    }
};

🏗️ Web API Integration

Controller Example

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    [HttpPost("search")]
    public async Task<ActionResult<FilterResult<ProductDto>>> SearchProducts(
        [FromBody] FilterRequest request)
    {
        try
        {
            var result = await _productService.GetFilteredProducts(request);
            return Ok(result);
        }
        catch (ArgumentException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
}

Service Implementation

public class ProductService : IProductService
{
    private readonly AppDbContext _context;

    public async Task<FilterResult<ProductDto>> GetFilteredProducts(FilterRequest request)
    {
        var query = _context.Products
            .Where(p => !p.IsDeleted)
            .Select(p => new ProductDto
            {
                Id = p.Id,
                Name = p.Name,
                Price = p.Price,
                Category = p.Category.Name
            });

        return query.ApplyFilteringWithPaging(request);
    }
}

🔍 Frontend Integration

JavaScript/TypeScript

const searchProducts = async (filters) => {
    const filterRequest = {
        filters: [
            { field: "Name", operator: "Contains", value: searchTerm },
            { field: "CategoryId", operator: "Equals", value: selectedCategory }
        ],
        sortField: "Name",
        sortDirection: "Asc",
        page: currentPage,
        pageSize: 20
    };

    const response = await fetch('/api/products/search', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(filterRequest)
    });

    const result = await response.json();
    return result;
};

React Hook Example

const useProductSearch = () => {
    const [filters, setFilters] = useState([]);
    const [page, setPage] = useState(1);
    const [sortField, setSortField] = useState('Name');
    
    const searchProducts = useCallback(async () => {
        const request = {
            filters,
            sortField,
            sortDirection: 'Asc',
            page,
            pageSize: 20
        };
        
        const result = await productApi.search(request);
        return result;
    }, [filters, page, sortField]);

    return { searchProducts, setFilters, setPage, setSortField };
};

⚡ Performance Tips

1. Use Indexes

// Create indexes for fields you'll filter on
[Index(nameof(Category))]
[Index(nameof(Price))]
[Index(nameof(CreatedDate))]
public class Product
{
    // ...
}

2. Use Projections

// Select only the fields you need
var query = context.Products
    .Select(p => new ProductSummary 
    { 
        Id = p.Id, 
        Name = p.Name, 
        Price = p.Price 
    });

var result = query.ApplyFilteringWithPaging(request);

3. Async Operations

// Use async for large datasets
public async Task<FilterResult<T>> GetFilteredDataAsync<T>(
    IQueryable<T> query, 
    FilterRequest request)
{
    var filteredQuery = query.ApplyFiltering(request);
    
    var totalCount = await filteredQuery.CountAsync();
    var data = await filteredQuery
        .Skip((request.Page - 1) * request.PageSize)
        .Take(request.PageSize)
        .ToListAsync();

    return new FilterResult<T>
    {
        Data = data,
        TotalCount = totalCount,
        Page = request.Page,
        PageSize = request.PageSize
    };
}

🚨 Error Handling

Common Errors and Solutions

1. Property Not Found
// ❌ Wrong
new Filter { Field = "ProductName", ... } // Property doesn't exist

// ✅ Correct
new Filter { Field = "Name", ... } // Correct property name
2. Type Conversion Error
// ❌ Wrong
new Filter { Field = "Price", Operator = FilterOperator.Equals, Value = "abc" }

// ✅ Correct
new Filter { Field = "Price", Operator = FilterOperator.Equals, Value = "99.99" }
3. Between Operator Usage
// ❌ Wrong
new Filter { Field = "Price", Operator = FilterOperator.Between, Value = "100" }

// ✅ Correct
new Filter { Field = "Price", Operator = FilterOperator.Between, Value = "100,500" }

Exception Handling

try
{
    var result = products.ApplyFilteringWithPaging(request);
    return Ok(result);
}
catch (ArgumentException ex)
{
    // Field not found or invalid value
    return BadRequest($"Filter error: {ex.Message}");
}
catch (InvalidOperationException ex)
{
    // Operator error
    return BadRequest($"Operation error: {ex.Message}");
}
catch (Exception ex)
{
    // Unexpected error
    _logger.LogError(ex, "Unexpected error in filtering");
    return StatusCode(500, "Internal server error");
}

📊 Testing Examples

Unit Test

[Test]
public void ApplyFiltering_WithContainsFilter_ShouldReturnMatchingResults()
{
    // Arrange
    var products = new List<Product>
    {
        new() { Name = "iPhone 14 Pro", Category = "Electronics" },
        new() { Name = "Samsung Galaxy", Category = "Electronics" },
        new() { Name = "Programming Book", Category = "Books" }
    }.AsQueryable();

    var request = new FilterRequest
    {
        Filters = new List<Filter>
        {
            new() { Field = "Name", Operator = FilterOperator.Contains, Value = "phone" }
        }
    };

    // Act
    var result = products.ApplyFilteringWithPaging(request);

    // Assert
    Assert.AreEqual(1, result.TotalCount);
    Assert.IsTrue(result.Data.Any(p => p.Name.Contains("iPhone")));
}

Integration Test

[Test]
public async Task SearchProducts_WithValidFilter_ShouldReturnResults()
{
    // Arrange
    var client = _factory.CreateClient();
    var filterRequest = new FilterRequest
    {
        Filters = new List<Filter>
        {
            new() { Field = "Category", Operator = FilterOperator.Equals, Value = "Electronics" }
        },
        Page = 1,
        PageSize = 10
    };

    // Act
    var response = await client.PostAsJsonAsync("/api/products/search", filterRequest);
    var result = await response.Content.ReadFromJsonAsync<FilterResult<ProductDto>>();

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);
    result.Should().NotBeNull();
    result.Data.Should().NotBeEmpty();
    result.Data.Should().OnlyContain(p => p.Category == "Electronics");
}

🔒 Security Considerations

Input Validation

public class FilterRequestValidator : AbstractValidator<FilterRequest>
{
    private readonly string[] _allowedFields = { "Name", "Price", "Category", "CreatedDate" };

    public FilterRequestValidator()
    {
        RuleFor(x => x.Page).GreaterThan(0);
        RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
        
        RuleForEach(x => x.Filters).ChildRules(filter =>
        {
            filter.RuleFor(f => f.Field)
                .Must(field => _allowedFields.Contains(field))
                .WithMessage("Invalid field name");
                
            filter.RuleFor(f => f.Value)
                .MaximumLength(1000)
                .WithMessage("Filter value too long");
        });
    }
}

SQL Injection Protection

The library uses Expression Trees and parameterized queries, providing built-in protection against SQL injection attacks.

🌍 Internationalization

Custom Error Messages

public class LocalizedFilterEngine : IFilterEngine
{
    private readonly IStringLocalizer _localizer;

    public LocalizedFilterEngine(IStringLocalizer localizer)
    {
        _localizer = localizer;
    }

    // Implementation with localized error messages
}

📱 Platform Support

  • .NET 7.0
  • Not tested on lower and higher versions.

🤝 Contributing

We welcome contributions! Here's how you can help:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Setup

git clone https://github.com/mahmutcansaribal/FilterEngine.Core.git
cd FilterEngine.Core
dotnet restore
dotnet build
dotnet test

📄 License

This project is licensed under the MIT License

🆘 Support

📈 Roadmap

v1.1.0 (Next Release)

  • Async operations support
  • Nested property filtering (e.g., Product.Category.Name)

🏆 Acknowledgments

  • Built with ❤️ for the .NET community
  • Inspired by OData and GraphQL filtering capabilities
  • Thanks to all contributors and users

⭐ If this project helped you, please give it a star!

Made with ❤️ by Mahmut Can Saribal

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  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.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows 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