Resieve 1.1.1

dotnet add package Resieve --version 1.1.1
                    
NuGet\Install-Package Resieve -Version 1.1.1
                    
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="Resieve" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Resieve" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Resieve" />
                    
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 Resieve --version 1.1.1
                    
#r "nuget: Resieve, 1.1.1"
                    
#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 Resieve@1.1.1
                    
#: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=Resieve&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Resieve&version=1.1.1
                    
Install as a Cake Tool

Introduction

Resieve is a simple, clean and extensible to add pagination, filtering and sorting to your project. It is designed to integrate with into your .NET applications with minimal setup. By abstracting the complexity of query building, Resieve allows you to focus on your business logic while ensuring your APIs remain efficient and maintainable.

Table of Contents

Usage

Installation

Install Resieve via NuGet to make its features available in your project. This command will add the package reference to your solution.

dotnet nuget add package Resieve

Getting started

Add the following line to you Program.cs or Startup.cs file to register the Resieve services:

using Resieve;

builder.Services.AddResieve();

Add entity mapping to configure on which properties the consumer is allowed to sort or/and filter. This lets you decide exactly which properties users can sort and filter by, so you keep your API flexible and safe.

public class ProductMapper : IResieveMapper
{
    public void Map(ResieveMapper mapper)
    {        
        public void Configure(ResieveMapper mapper)
        {
            // Only sorting
            mapper.ForProperty<Product>(x => x.Id).CanSort();
            
            // Only filtering
            mapper.ForProperty<Product>(x => x.Name).CanFilter();
            
            // Both filtering and sorting
            mapper.ForProperty<Product>(x => x.Price).CanFilter().CanSort();
        }
    }
}

For a quicker way to add mapping for all properties you can use the AddResieveMappingsFromAssembly method. Where you can reference any class from the assembly containing your mappings.

builder.Services.AddResieveMappingsFromAssembly(typeof(ResieveMappingForProduct).Assembly);

By accepting a ResieveModel from query parameters, your controller can automatically handle incoming filter, sort, and pagination requests, streamlining endpoint logic.

[ApiController]
[Route("api/[controller]")]
public class ProductController(ProductRepository repository) : ControllerBase
{
    public async ValueTask<ActionResult<IEnumerable<Product>>> Get([FromQuery] ResieveModel model)
    {
        var products = await repository.GetFilteredProductsAsync(model);
        return Ok(products);
    }
    
}

For a basic repository implementation where we simply apply all filtering, sorting and pagination in one go. We get back a paginated response containing the filtered products, total count and pagination info.

There is a small extension package you have to install for this to work:

dotnet nuget add package Resieve.EntityFramework

This package add some extension methods that applies the filtering and sorting for you. And returns a paginated model.

public class ProductRepository
{
    private readonly AppDbContext _context;
    private readonly IResieveProcessor _processor;

    public ProductRepository(AppDbContext context, IResieveProcessor processor)
    {
        _context = context;
        _processor = processor;
    }

    public async Task<PaginatedResponse<IEnumerable<Product>>> GetFilteredProductsAsync(ResieveModel model)
    {
        return await context
            .Products
            .Include(p => p.Tags)
            .AsNoTracking()
            .ToResieveResult(
                model,
                processor,
                CancellationToken.None
            );
    }
}

More advanced repository implementation where we split out the filtering, sorting and pagination steps to get a total count of items before pagination is applied and transforming the query result into a dto. In this way you can adjust the way you want to handle you data, or return a different type.

    public async Task<PaginatedResponse<IEnumerable<Product>>> GetFilteredProductsAsync(ResieveModel model)
    {
        var source = context
            .Products
            .Include(p => p.Tags)
            .AsNoTracking();

        // Step 1: Apply filtering and sorting to get the total count (without pagination)
        var filteredAndSortedQuery = source
            .FilterBy(model, processor)
            .SortBy(model, processor);

        var totalCount = await filteredAndSortedQuery.CountAsync();

        // Step 2: Apply pagination only (filtering and sorting are skipped)
        var result = await filteredAndSortedQuery
            .PaginateBy(model, processor)
            .ToListAsync();

        // Step 3: Convert IQueryable to Paginated Response
        return result.ToPaginatedResponse(model, processor, totalCount);
    }

Supported operators

Sorting

Sorting enables you to order results by one or more properties. Prefixing a property with - sorts it in descending order, while multiple properties can be chained using commas for multi-level sorting.

Operator Description Example
, Comma seperator for multiple sort statements sort1,sort2
- A dash in front of a property will sort it descendingly -sort1 ,sort2

Filtering

Filtering allows you to narrow down results based on property values. Combine multiple filters using , for AND logic or | for OR logic to create complex queries.

Logical operators

Operator Description Example
, Comma seperator wil act as an AND property1==a,propeerty2==b
\| A pipe symbol will act as an OR property1==a\|propeerty2==b

Comparison operators

These operators provide granular control over how data is filtered, supporting both numeric and string comparisons. Use them to match, exclude, or partially match property values.

Operator Description Example
>= Greater than or equal NumberProperty >= 4
<= Less than or equal NumberProperty <= 5
== Equals NumberProperty == 5
!= Not equals NumberProperty != 5
> Greater than NumberProperty > 4
< Less than NumberProperty < 5
@= Contains FruitProperty @= app
!@= Does not contain FruitProperty !@= apple
_= Starts with WordProperty _= hel
_-= Ends with WordProperty _-= ld
!_= Does not start with FruitProperty !_= ap
!_-= Does not end with FruitProperty !_-= le

Case-insensitive comparison operators

Disclaimer: The case-insensitive will call a tolowercase on both sides of the comparison, so be aware of potential performance implications when working with large datasets. To properly support case-insensitive filtering consider using a case-insensitive collation on your database columns.

Operator Description Example
==* Equals (case-insensitive)) WordProperty ==* HELLO
!=* Not equals (case-insensitive)) WordProperty !=* HELLO
@=* Contains (case-insensitive) WordProperty @=* LlO
!@=* Does not contain (case-insensitive) WordProperty !@=* LlO
_=* Starts with (case-insensitive) WordProperty _=* HeL
_-=* Ends with (case-insensitive) WordProperty _-=* OrLd
!_=* Does not start with (case-insensitive) WordProperty !_=* HeL
!_-=* Does not end with (case-insensitive)} WordProperty !_-=* OrlD

Case-insensitive operators are useful when you want to ignore letter casing in your queries. For optimal performance, especially on large datasets, consider configuring your database columns with case-insensitive collation.

Advanced usage

For trickier cases like relationships or complex queries, you can plug in your own custom filtering and sorting logic. If you need to filter or sort on one-to-many or many-to-many relationships, just add a custom key and hook up your own filter or sort. You’ll get the operator and value, so you can handle it however you want.

Custom filtering

Custom filters let you define exactly how filtering should behave for a property or key. Implement the interface and register your filter to override or extend default behavior.

Example implementation of a custom filter that performs a case-insensitive equality check on the product name.

public class CustomNameFilter : IResieveCustomFilter<Product>
{    
    public Expression<Func<Product, bool>> BuildWhereExpression(string @operator, string value)
    {
        return x => x.Name.ToLower() == value.ToLower();
    }
}

Example registration of custom filter in the entity mapping.

public class ProductMapper : IResieveMapper
{
    public void Map(ResieveMapper mapper)
    {        
        public void Configure(ResieveMapper mapper)
        {            
            // Overwrite the default property filter
            mapper.ForProperty<Product>(x => x.Name).CanFilter<CustomNameFilter>();
            
            // Or add custom name
            mapper.ForKey<Product>("Tags.Name").CanFilter<CustomNameFilter>();
        }
    }
}

Don't forget to also register you custom filter in the DI container.

builder.Services.AddTransient<CustomNameFilter>();

Custom sorting

Custom sorts allow you to control the ordering logic for properties or keys, supporting advanced scenarios such as sorting on related entities or computed fields.

Example implementation of a custom sort that sorts products by their name.

public class CustomNameSort : IResieveCustomSort<Product>
{
    public IOrderedQueryable<Product> Apply(IQueryable<Product> source, bool isDescending)
    {
        return isDescending ? source.OrderByDescending(x => x.Name) : source.OrderBy(x => x.Name);
    }
    
    public IOrderedQueryable<Product> ApplyThenBy(IOrderedQueryable<Product> source, bool isDescending)
    {
        return isDescending ? source.ThenByDescending(x => x.Name) : source.ThenBy(x => x.Name);
    }
}

Example registration of custom sort in the entity mapping.

public class ProductMapper : IResieveMapper
{
    public void Map(ResieveMapper mapper)
    {        
        public void Configure(ResieveMapper mapper)
        {            
            // Overwrite the default property filter
            mapper.ForProperty<Product>(x => x.Name).CanSort<CustomNameFilter>();
            
            // Or add custom name
            mapper.ForKey<Product>("Tags.Name").CanSort<CustomNameFilter>();
        }
    }
}

Don't forget to also register you custom sort in the DI container.

builder.Services.AddTransient<CustomNameSort>();

Escaping values

If a value contains characters that would normally be interpreted by the lexer or parser, you can escape it by wrapping the value in quotes.

By enclosing the value in either double (") or single (') quotes, the parser will treat everything between the opening and closing quote as a single, complete value.

Example

var filter = 'Name=="He said \'hello\' to me"';
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 (1)

Showing the top 1 NuGet packages that depend on Resieve:

Package Downloads
Resieve.EntityFramework

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 132 3/29/2026
1.1.0 112 3/29/2026
1.0.0 221 11/5/2025