Muslim.Filter 8.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Muslim.Filter --version 8.0.0
                    
NuGet\Install-Package Muslim.Filter -Version 8.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="Muslim.Filter" Version="8.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Muslim.Filter" Version="8.0.0" />
                    
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 8.0.0
                    
#r "nuget: Muslim.Filter, 8.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 Muslim.Filter@8.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=Muslim.Filter&version=8.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Muslim.Filter&version=8.0.0
                    
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 int BranchId { 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 rather than throwing, and adds nothing when the term is empty.

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. An unknown column is ignored rather than throwing, so a stale grid state cannot break the request. Set IsApplySorting = false to skip ordering entirely.

Paging

PageIndex is 1-based; values below 1 are treated as 1. 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.

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.

License

See LICENSE.txt. Free for personal and commercial use under the stated conditions.

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.
  • net8.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 And/Or building expressions EF Core could not translate, filters silently duplicating when a filter object was reused, and AndIf/OrIf being ignored by Apply(). Adds ToPagedResult with total count, multi-column and nested-property sorting, MaxPageSize, Search across columns, WhereIfNotEmpty/WhereIfHasValue, and programmatic configuration. Existing AddFilter code is unaffected. See CHANGELOG.md.