Pafiso.AspNetCore 2.0.0-beta.1

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

Pafiso

A .NET library for Paging, Filtering, and Sorting with DTO-to-Entity mapping support.

NuGet Version NuGet Downloads Build Status Deploy Status

Pafiso enables building dynamic, type-safe queries from query string parameters by mapping between DTOs (mapping models) and entity classes. Perfect for building flexible REST APIs with filtering, sorting, and pagination.

Features

  • Fluent API - Clean, discoverable syntax with IntelliSense support
  • Type-Safe - Strong typing with DTO-to-Entity mapping
  • Entity Framework Core - Full async support with optimized SQL via EF.Functions.Like
  • Flexible - Multiple API styles to fit your use case
  • Auto-Mapping - 1:1 field mapping when names match
  • Customizable - Transform values, map nested properties, plug in custom expression builders

Installation

Install Pafiso via NuGet Package Manager:

PM> Install-Package Pafiso.AspNetCore
PM> Install-Package Pafiso.EntityFrameworkCore  # For EF Core async support

Or via the .NET CLI:

dotnet add package Pafiso.AspNetCore
dotnet add package Pafiso.EntityFrameworkCore  # For EF Core async support

Quick Start

1. Define Your DTO and Entity

using Pafiso;

// DTO - Represents incoming query parameters
public class ProductFilterDto : MappingModel {
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public string Category { get; set; }
}

// Entity - Your database model
public class Product {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
}
using Pafiso.EntityFrameworkCore;

[HttpGet]
public async Task<PagedList<Product>> GetProducts() {
    return await _dbContext.Products
        .WithPafiso(Request.Query, configure: opt => {
            opt.WithPaging();
            opt.WithFiltering<ProductFilterDto>()
                .Map(dto => dto.ProductId, entity => entity.Id)
                .Map(dto => dto.ProductName, entity => entity.Name);
                // Category maps 1:1 automatically
            opt.WithSorting<ProductFilterDto>();
        })
        .ToPagedListAsync();
}

That's it! The query string is automatically parsed and applied.

Query String Example

GET /api/products?skip=0&take=10
    &filters[0][fields]=Category&filters[0][op]=eq&filters[0][val]=Electronics
    &sortings[0][prop]=ProductName&sortings[0][ord]=asc

Response:

{
  "totalEntries": 150,
  "pageNumber": 0,
  "pageSize": 10,
  "entries": [...]
}

API Styles

Pafiso offers three API styles to fit different use cases:

Perfect for simple, one-time queries:

using Pafiso.EntityFrameworkCore;

var products = await _dbContext.Products
    .WithPafiso(Request.Query, configure: opt => {
        opt.WithPaging();
        opt.WithFiltering<ProductFilterDto>();
    })
    .ToPagedListAsync();

Style 2: SearchParameters (For Reusability)

Build once, reuse multiple times:

using Pafiso.AspNetCore;
using Pafiso.EntityFrameworkCore;

// Build SearchParameters
var searchParams = Request.Query.ToSearchParameters<Product>(builder => {
    builder.WithPaging();
    builder.WithFiltering<ProductFilterDto>()
        .Map(dto => dto.ProductId, entity => entity.Id);
});

// Reuse across different queries
var activeProducts = await _dbContext.Products
    .Where(p => p.IsActive)
    .WithPafiso(searchParams)
    .ToPagedListAsync();

var featuredProducts = await _dbContext.Products
    .Where(p => p.IsFeatured)
    .WithPafiso(searchParams)
    .ToPagedListAsync();

Style 3: Manual with Mapper (Legacy)

For maximum control:

var mapper = new FieldMapper<ProductFilterDto, Product>(settings)
    .Map(dto => dto.ProductId, entity => entity.Id);

var searchParams = Request.Query.ToSearchParameters<ProductFilterDto, Product>(mapper);
var (countQuery, pagedQuery) = searchParams.ApplyToIQueryable(_dbContext.Products);

var totalCount = await countQuery.CountAsync();
var items = await pagedQuery.ToListAsync();

Core Concepts

Automatic 1:1 Mapping

When DTO and Entity property names match, no explicit mapping is needed:

public class ProductFilterDto : MappingModel {
    public string Category { get; set; }  // Matches Product.Category
}

// Category maps automatically
opt.WithFiltering<ProductFilterDto>();

Custom Field Mapping

Map fields with different names:

opt.WithFiltering<ProductFilterDto>()
    .Map(dto => dto.ProductId, entity => entity.Id)
    .Map(dto => dto.ProductName, entity => entity.Name);

Value Transformation

Transform values before filtering:

opt.WithFiltering<ProductFilterDto>()
    .MapWithTransform(
        dto => dto.PriceInDollars,
        entity => entity.PriceInCents,
        value => decimal.Parse(value ?? "0") * 100
    );

Optional Components

All features are opt-in:

.WithPafiso(Request.Query, configure: opt => {
    opt.WithPaging();           // Optional
    opt.WithFiltering<Dto>();   // Optional
    opt.WithSorting<Dto>();     // Optional
});

Supported Operators

Filter Operators

Operator Description Example
eq Equals filters[0][op]=eq&filters[0][val]=Electronics
neq Not equals filters[0][op]=neq&filters[0][val]=Books
gt Greater than filters[0][op]=gt&filters[0][val]=100
gte Greater than or equal filters[0][op]=gte&filters[0][val]=100
lt Less than filters[0][op]=lt&filters[0][val]=50
lte Less than or equal filters[0][op]=lte&filters[0][val]=50
contains String contains filters[0][op]=contains&filters[0][val]=laptop
startswith String starts with filters[0][op]=startswith&filters[0][val]=Pro
endswith String ends with filters[0][op]=endswith&filters[0][val]=Max

Sort Orders

Order Description Example
asc Ascending sortings[0][ord]=asc
desc Descending sortings[0][ord]=desc

Advanced Usage

Custom Settings

Configure string comparison and other behaviors:

var settings = new PafisoSettings {
    StringComparison = StringComparison.OrdinalIgnoreCase
};

.WithPafiso(Request.Query, settings, configure: opt => {
    opt.WithFiltering<ProductFilterDto>();
});

Nested Properties

Map to nested entity properties:

public class Product {
    public Category Category { get; set; }
}

public class Category {
    public string Name { get; set; }
}

opt.WithFiltering<ProductFilterDto>()
    .Map(dto => dto.CategoryName, entity => entity.Category.Name);

Multiple Filters (AND Logic)

Multiple filters are combined with AND:

?filters[0][fields]=Category&filters[0][op]=eq&filters[0][val]=Electronics
&filters[1][fields]=ProductId&filters[1][op]=gt&filters[1][val]=100

Result: Category = 'Electronics' AND ProductId > 100

Multiple Fields (OR Logic)

Single filter with multiple fields uses OR:

?filters[0][fields]=Name,Description&filters[0][op]=contains&filters[0][val]=laptop

Result: Name LIKE '%laptop%' OR Description LIKE '%laptop%'

Case Sensitivity

Filters default to case-insensitive. Override per-filter via query string:

?filters[0][fields]=Name&filters[0][op]=eq&filters[0][val]=Test&filters[0][case]=true

Repository Pattern

public interface IProductRepository {
    Task<PagedList<Product>> GetProductsAsync(SearchParameters searchParams);
}

public class ProductRepository : IProductRepository {
    private readonly AppDbContext _context;

    public async Task<PagedList<Product>> GetProductsAsync(SearchParameters searchParams) {
        return await _context.Products
            .WithPafiso(searchParams)
            .ToPagedListAsync();
    }
}

Entity Framework Core Optimization

For optimized SQL generation with EF Core, use WithEfFiltering<TMapping>() instead of WithFiltering<TMapping>(). This automatically uses EF.Functions.Like for case-insensitive string operations:

using Pafiso.EntityFrameworkCore;

var products = await _dbContext.Products
    .WithPafiso(Request.Query, configure: opt => {
        opt.WithPaging();
        opt.WithEfFiltering<ProductFilterDto>()
            .Map(dto => dto.ProductId, entity => entity.Id);
        opt.WithSorting<ProductFilterDto>();
    })
    .ToPagedListAsync();

You can also configure case sensitivity defaults:

opt.WithEfFiltering<ProductFilterDto>(new EfFilteringSettings {
    CaseSensitive = true  // Default to case-sensitive matching
});

To control EF Core LIKE usage, pass EfCoreSettings to WithPafiso:

var settings = new EfCoreSettings { UseEfCoreLikeForCaseInsensitive = false };
await _dbContext.Products.WithPafiso(Request.Query, settings, opt => { /* ... */ }).ToPagedListAsync();

DI Registration

Register Pafiso services for dependency injection:

using Pafiso.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Register Pafiso with auto-detection of JSON settings
builder.Services.AddPafiso();

// Or configure manually
builder.Services.AddPafiso(settings => {
    settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

// Register field mappers (for legacy/manual API style)
builder.Services.AddFieldMapper<ProductFilterDto, Product>(mapper => {
    mapper.Map(dto => dto.ProductId, entity => entity.Id);
    mapper.Map(dto => dto.ProductName, entity => entity.Name);
});

Why Pafiso?

Before Pafiso

[HttpGet]
public async Task<IActionResult> GetProducts(
    [FromQuery] string? category,
    [FromQuery] decimal? minPrice,
    [FromQuery] int page = 0,
    [FromQuery] int pageSize = 10,
    [FromQuery] string? sortBy = "name",
    [FromQuery] string? sortOrder = "asc") {

    var query = _dbContext.Products.AsQueryable();

    if (!string.IsNullOrEmpty(category))
        query = query.Where(p => p.Category == category);

    if (minPrice.HasValue)
        query = query.Where(p => p.Price >= minPrice.Value);

    query = sortBy?.ToLower() switch {
        "name" => sortOrder == "desc"
            ? query.OrderByDescending(p => p.Name)
            : query.OrderBy(p => p.Name),
        "price" => sortOrder == "desc"
            ? query.OrderByDescending(p => p.Price)
            : query.OrderBy(p => p.Price),
        _ => query.OrderBy(p => p.Name)
    };

    var total = await query.CountAsync();
    var items = await query.Skip(page * pageSize).Take(pageSize).ToListAsync();

    return Ok(new { total, items });
}

After Pafiso

[HttpGet]
public async Task<PagedList<Product>> GetProducts() {
    return await _dbContext.Products
        .WithPafiso(Request.Query, configure: opt => {
            opt.WithPaging();
            opt.WithFiltering<ProductFilterDto>();
            opt.WithSorting<ProductFilterDto>();
        })
        .ToPagedListAsync();
}

Benefits:

  • 90% less boilerplate code
  • Type-safe with compile-time checking
  • Flexible query strings without code changes
  • Automatic parameter validation
  • Consistent API across endpoints
  • Testable and maintainable

Performance

Pafiso generates optimized LINQ expressions that translate to efficient SQL:

-- Generated SQL (with EF Core)
SELECT COUNT(*) FROM Products WHERE Category = 'Electronics';

SELECT * FROM Products
WHERE Category = 'Electronics'
ORDER BY Name ASC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;

No reflection in hot paths, no dynamic SQL, just clean LINQ-to-SQL.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.
  • net10.0

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
2.0.0-beta.1 46 2/13/2026
1.11.0 93 1/23/2026
1.10.1 82 1/23/2026
1.10.0 89 1/22/2026