Muslim.Filter 10.0.1

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

Muslim.Filter

Filtering, sorting and paging for IQueryable<T>, driven by whatever the client actually sent.

Declare one filter per row type, register a predicate for each supplied value, and get a page back with its total count.

dotnet add package Muslim.Filter

No dependencies beyond the framework. Works with EF Core, LINQ to Objects, or any provider.

Defining a filter

Mark the row type with IHaveFilter, then derive from Filter<T>:

public class User : IHaveFilter
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Code { get; set; }
    public int BranchId { get; set; }
    public Branch? Branch { get; set; }
}

public class Branch
{
    public int Id { get; set; }
    public string? Name { get; set; }
}

public class UserFilter : Filter<User>
{
    public string? Name { get; set; }
    public int? BranchId { get; set; }
    public string? Term { get; set; }

    protected override void ApplyFilter()
    {
        WhereIfNotEmpty(Name,     name => x => x.Name!.Contains(name));
        WhereIfHasValue(BranchId, id   => x => x.BranchId == id);
        Search(Term, x => x.Name, x => x.Code, x => x.Branch!.Name);
    }
}

A property left null contributes no predicate, so the same filter serves every combination of criteria the client sends.

Bind it straight from the query string — PageIndex, PageSize, SortColumn and SortDirection are inherited properties.

Running it

var page = filter.ToPagedResult(repository.QueryAsNoTracking);

return new
{
    page.Items,        // this page
    page.TotalCount,   // rows matching the filter, ignoring paging
    page.TotalPages,
    page.HasNext
};
ToPagedResult(query) one page plus totals — two queries, a COUNT and the page
Apply(query) predicates, then ordering, then paging; returns the composed IQueryable
ApplyFilterOnly(query) predicates only, no ordering or paging
ToExpression() the combined predicate, for a repository Count/Any overload

Nothing executes until you enumerate, so Apply can be composed further.

Registering predicates

AddFilter(condition, predicate) the original form; still supported unchanged
WhereIf(condition, predicate) same thing, reads better
WhereIfNotEmpty(text, build) runs only when the string has content, and hands you the non-null value
WhereIfHasValue(nullable, build) runs only when the nullable has one, and hands you the unwrapped value
Search(term, ...columns) one term across several text columns, joined with ||

WhereIfNotEmpty and WhereIfHasValue pass the unwrapped value into the lambda, so the predicate needs no null-forgiving !:

AddFilter(!string.IsNullOrEmpty(Name), x => x.Name!.Contains(Name!));   // before
WhereIfNotEmpty(Name, name => x => x.Name!.Contains(name));            // after

Search skips null columns — including a column reached through a null navigation such as x.Branch!.Name — rather than throwing, and adds nothing when the term is empty. Under EF Core the term is sent as a SQL parameter. Case sensitivity is the provider's: the database collation, or ordinal comparison in memory.

Fluent form

AndIf and OrIf build a single expression instead of a list — use them when you need || between whole clauses:

protected override void ApplyFilter()
{
    AndIf(BranchId.HasValue, x => x.BranchId == BranchId);
    OrIf(IncludeArchived,    x => x.IsArchived);
}

Both forms can be mixed; everything registered is combined.

Sorting

SortColumn accepts three forms:

filter.SortColumn = "Name";                        // uses SortDirection
filter.SortColumn = "Customer.Name";               // walks navigation properties
filter.SortColumn = "BranchId asc, Name desc";     // several columns, each with its own direction

Column names are matched case-insensitively against public properties, preferring an exact-case match and then the most derived declaration (a property hidden with new). An unknown column is ignored rather than throwing, so a stale grid state cannot break the request — and a typo silently returns unordered rows. Set IsApplySorting = false to skip ordering entirely.

Paging

PageIndex is 1-based; values below 1 are treated as 1, and a page past the end returns no rows however large the index. Set IsApplyPagination = false to return everything.

MaxPageSize caps what a request may ask for. It defaults to 0, meaning no limit — set it on anything a client can reach, since PageSize normally arrives straight off the query string and an uncapped request for a million rows is honoured.

Defaults

Configure once at startup. Binding from IConfiguration is the reliable route, because it honours environment overrides, environment variables and user secrets:

FilterDefaults.Configure(
    builder.Configuration.GetSection("DefaultFilterValue").Get<DefaultFilterValue>()
    ?? new DefaultFilterValue());

Or set them directly:

FilterDefaults.Configure(d =>
{
    d.PageSize    = 20;
    d.MaxPageSize = 200;
});

If you configure nothing, DefaultFilterValue is read from appsettings.json, as in earlier versions:

{
  "DefaultFilterValue": {
    "PageIndex": 1,
    "PageSize": 20,
    "MaxPageSize": 200,
    "SortColumn": "CreatedDate",
    "SortDirection": "Desc",
    "ApplyPagination": true,
    "ApplySorting": true
  }
}

That path reads the file off disk directly, so it cannot see anything supplied through IConfiguration. It accepts what the configuration JSON provider accepts — comments, trailing commas, any key casing, and "true"/"20" written as strings — and keeps the built-in default for any value it cannot read.

Composing predicates

And, Or and Not join predicates into one lambda over a single parameter, which EF Core can translate to SQL:

Expression<Func<User, bool>> active = x => x.IsActive;

var query = repository.Query.Where(active.And(x => x.BranchId == id));

Reusing a filter object

A filter can be run more than once. Each run rebuilds its predicates from scratch, so calling Apply twice, or ApplyFilterOnly then Apply, gives the same result both times.

Inside ApplyFilter, the protected Query property holds the query of the current run under Apply, ApplyFilterOnly and ToPagedResult, and is null under ToExpression.

Async with EF Core

ToPagedResult executes synchronously. With EF Core's async operators:

var total = await filter.ApplyFilterOnly(db.Users.AsNoTracking()).CountAsync(ct);
var items = await filter.Apply(db.Users.AsNoTracking()).ToListAsync(ct);
Package Use it for
Muslim.EntityAndDto Base entity and DTO classes
Muslim.Result One result type for every API response

Coding-agent guidance

When you build a project that uses this package, a short guide for AI coding agents is copied to .agents/skills/muslim-filter/ at the root of your repository. Agents that read skills from .agents/skills, such as Codex and GitHub Copilot in VS Code, use it to write correct code against this package. Your own files and other skills are never touched, and nothing is deleted automatically.

To turn it off, add this to your project or Directory.Build.props:

<PropertyGroup>
  <MuslimFilterGenerateSkill>false</MuslimFilterGenerateSkill>
</PropertyGroup>

License

See LICENSE.txt.

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

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Muslim.Filter:

Package Downloads
Muslim.EntityAndDto

Shared entity, audit and DTO contracts for .NET applications. Includes repository-scoped agent guidance generated on consumer builds.

Muslim.GenericRepository.Interface

Repository abstraction over EF Core: IRepository<TEntity> and the interfaces it composes — reads, writes, soft delete, bulk operations, counts and raw SQL. Reference this to depend on the contract without the implementation.

Muslim.GenericService.Interface

Service abstraction for Muslim.GenericService: IService<TEntity> and the interfaces it composes — reads, finds with paging, create, update, delete, soft delete and restore, counts and raw SQL — all returning Muslim.Result Result<T>. Reference this to depend on the contract without the implementation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.1 164 10/15/2026
8.0.0 277 9/23/2026
2.2.0 240 8/10/2026
2.0.0 248 10/5/2024
1.1.0 316 1/3/2024
1.0.0 486 1/1/2024

Fixes a large PageIndex overflowing into a negative offset (first page returned in memory, SQL error on EF Core), Search throwing on a null navigation in memory and inlining the search term into SQL, sorting failing on properties hidden with new or differing only by case, and appsettings.json defaults being ignored when the file has comments, trailing commas or different key casing. ApplyFilterOnly now sets Query. Ships version-matched agent guidance and generates .agents/skills/muslim-filter during normal consuming builds; preserves user files and supports MuslimFilterGenerateSkill=false. See CHANGELOG.md and README.md.