Muslim.Filter
8.0.0
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
<PackageReference Include="Muslim.Filter" Version="8.0.0" />
<PackageVersion Include="Muslim.Filter" Version="8.0.0" />
<PackageReference Include="Muslim.Filter" />
paket add Muslim.Filter --version 8.0.0
#r "nuget: Muslim.Filter, 8.0.0"
#:package Muslim.Filter@8.0.0
#addin nuget:?package=Muslim.Filter&version=8.0.0
#tool nuget:?package=Muslim.Filter&version=8.0.0
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 | Versions 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. |
-
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.
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.