{"id":22030881,"url":"https://github.com/plainquire/plainquire","last_synced_at":"2026-04-08T13:31:08.102Z","repository":{"id":58462839,"uuid":"371304671","full_name":"plainquire/plainquire","owner":"plainquire","description":"Seamless filtering, sorting and paging for ASP.NET Core. Fully customizable.","archived":false,"fork":false,"pushed_at":"2026-03-26T12:16:28.000Z","size":2500,"stargazers_count":43,"open_issues_count":0,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-27T04:55:32.760Z","etag":null,"topics":["aps-net-core","csharp-library","expression-engine","filtering","linq-expressions","openapi","paging","sorting","swagger","swagger-ui","webapi"],"latest_commit_sha":null,"homepage":"https://www.plainquire.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/plainquire.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-05-27T08:41:02.000Z","updated_at":"2026-03-26T12:16:36.000Z","dependencies_parsed_at":"2025-05-18T22:06:43.671Z","dependency_job_id":"57440fe5-4efc-4bc0-98f1-65c4b3058820","html_url":"https://github.com/plainquire/plainquire","commit_stats":{"total_commits":82,"total_committers":1,"mean_commits":82.0,"dds":0.0,"last_synced_commit":"63a304d350877f0628cbe590578f53bce63e71fc"},"previous_names":["plainquire/plainquire","fschick/filterexpressioncreator"],"tags_count":63,"template":false,"template_full_name":null,"purl":"pkg:github/plainquire/plainquire","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plainquire%2Fplainquire","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plainquire%2Fplainquire/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plainquire%2Fplainquire/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plainquire%2Fplainquire/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/plainquire","download_url":"https://codeload.github.com/plainquire/plainquire/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plainquire%2Fplainquire/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31558379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T10:21:54.569Z","status":"ssl_error","status_checked_at":"2026-04-08T10:21:38.171Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aps-net-core","csharp-library","expression-engine","filtering","linq-expressions","openapi","paging","sorting","swagger","swagger-ui","webapi"],"created_at":"2024-11-30T08:11:29.169Z","updated_at":"2026-04-08T13:31:08.070Z","avatar_url":"https://github.com/plainquire.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Plainquire\n\n[\u003cimg src=\"https://plainquire.github.io/plainquire/badges/release.svg?\"\u003e](https://github.com/plainquire/plainquire/releases/latest) [\u003cimg src=\"https://plainquire.github.io/plainquire/badges/license.svg?\"\u003e](https://github.com/plainquire/plainquire#MIT-1-ov-file) [\u003cimg src=\"https://plainquire.github.io/plainquire/badges/build.svg?\"\u003e](https://github.com/plainquire/plainquire/actions) [\u003cimg src=\"https://plainquire.github.io/plainquire/badges/tests.svg?\"\u003e](https://github.com/plainquire/plainquire/actions) [\u003cimg src=\"https://plainquire.github.io/plainquire/badges/coverage.svg?\"\u003e](https://plainquire.github.io/plainquire/coveragereport)\n\n\nSeamless **filtering**, **sorting**, and **paging** for .NET Standard 2.1. Fully **customizable**. Model binding support and integration into Swagger UI.\n\n## Demo\n\nApplication: [https://www.plainquire.com/demo](https://www.plainquire.com/demo)\n\nSwagger UI: [https://www.plainquire.com/api](https://www.plainquire.com/api)\n\n## Usage for ASP.NET Core\n\n### 1. Install NuGet packages\n\n```cmd\ndotnet add package Plainquire.Filter\ndotnet add package Plainquire.Filter.Mvc\ndotnet add package Plainquire.Filter.Swashbuckle\n```\n\n### 2. Register services\n\n```csharp\nusing Plainquire.Filter.Mvc;\nusing Plainquire.Filter.Swashbuckle;\n\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddControllers().AddFilterSupport();\nbuilder.Services.AddSwaggerGen(options =\u003e options.AddFilterSupport());\n```\n\n### 3. Setup entity\n\n```csharp\nusing Plainquire.Filter;\n\n[EntityFilter(Prefix = \"\")]\npublic class Freelancer\n{\n    public Guid Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n}\n```\n\n### 4. Create HTTP endpoint\n\n```csharp\nusing Plainquire.Filter;\n\n[HttpGet]\npublic IEnumerable\u003cFreelancer\u003e GetFreelancers([FromQuery] EntityFilter\u003cFreelancer\u003e filter)\n{\n    var freelancers = GetFreelancersFromDatabase();\n    var filteredFreelancers = freelancers.Where(filter);\n    return filteredFreelancers;\n}\n```\n\n### 5. Send HTTP request\n\n```bash\nBASE_URL=https://www.plainquire.com/api/Freelancer\ncurl -O \"$BASE_URL/GetFreelancers?firstName=Joe\"\n```\n\n### 6. **Results in SQL statement**\n\n```sql\nSELECT *\nFROM \"Freelancer\"\nWHERE instr(upper(\"FirstName\"), 'JOE') \u003e 0\n```\n\n## Usage for non-web applications\n\n### 1. Install NuGet package\n\n```cmd\ndotnet add package Plainquire.Filter\n```\n\n### 2. Setup entity\n\nSee [setup entity](#3-setup-entity) from above.\n\n### 3. Create repository\n\n```cs\npublic IEnumerable\u003cFreelancer\u003e GetFreelancers()\n{\n    var filter = new EntityFilter\u003cFreelancer\u003e().Add(x =\u003e x.FirstName, \"~Joe\");\n    \n    var freelancers = GetFreelancersFromDatabase();\n    var filteredFreelancers = freelancers.Where(filter);\n    return filteredFreelancers;\n}\n```\n\n# Table of contents\n\n- [Features](#features)\n- [Syntax](#syntax)\n  - [Filter syntax](#filter-syntax)\n  - [Sort syntax](#sort-syntax)\n- [Filter entities](#filter-entities)\n  - [Basic usage](#basic-usage)\n  - [Configure filters](#configure-filters)\n  - [Filter by special values](#filter-by-special-values)\n  - [Logical Operators](#logical-operators)\n  - [Nested filters](#nested-filters)\n  - [Retrieve syntax and filter values](#retrieve-syntax-and-filter-values)\n  - [REST / MVC](#rest--mvc)\n  - [Swagger / OpenAPI](#swagger--openapi)\n  - [Support for Newtonsoft.Json](#support-for-newtonsoftjson)\n  - [Interception](#interception)\n  - [Advanced scenarios](#advanced-scenarios)\n- [Sort entities](#sort-entities)\n  - [Basic usage](#basic-usage)\n  - [Configure sorting](#configure-sorting)\n  - [Sort entities](#sort-entities)\n  - [Sort nested entities](#sort-nested-entities)\n  - [Retrieve syntax and sort direction](#retrieve-syntax-and-sort-direction)\n  - [REST / MVC](#rest--mvc)\n  - [Swagger / OpenAPI](#swagger--openapi)\n  - [Support for Newtonsoft.Json](#support-for-newtonsoftjson)\n  - [Interception](#interception)\n  - [Advanced scenarios](#advanced-scenarios)\n- [Page Entities](#page-entities)\n  - [Basic usage](#basic-usage)\n  - [Configure pagination](#configure-pagination)\n  - [REST / MVC](#rest--mvc)\n  - [Swagger / OpenAPI](#swagger--openapi)\n  - [Support for Newtonsoft.Json](#support-for-newtonsoftjson)\n  - [Interception](#interception)\n  - [Advanced Scenarios](#advanced-scenarios)\n- [Upgrade from FilterExpressionCreator](#upgrade-from-filterexpressioncreator)\n\n# Features\n\n* Filtering, sorting and pagination for ASP.NET Core\n* Customizable syntax\n* Support for Swagger / OpenUI and code generators via [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)\n* Support for Entity Framework an other ORM mapper using `IQueryable\u003cT\u003e`\n* Support for In-memory lists / arrays via `IEnumerable\u003cT\u003e`\n* Binding for HTTP query parameters\n* Natural language date/time interpretation (e.g. yesterday, last week Tuesday, ...)\n* Filters, sorts and pages are serializable, e.g. to persist user defined filters\n* Customizable expressions via interceptors\n\n# Syntax\n\n## Filter syntax\n\n**Syntax reference**\n\nThe default filter micro syntax uses operator/value pairs separated by `,`, `;`, or `|` \n\nSeparated values combined with logical `OR`. To combine values with logical `AND`, specify the filter multiple times.\n\n| Micro\u0026nbsp;syntax | Operator               | Description                                                  |\n| ----------------- | ---------------------- | ------------------------------------------------------------ |\n|                   | `Default`              | Selects operator `Contains` for string values; `EqualCaseInsensitive` for others. |\n| `~`               | `Contains`             | Hits when the filtered property contains the filter value    |\n| `^`               | `StartsWith`           | Hits when the filtered property starts with the filter value |\n| `$`               | `EndsWith`             | Hits when the filtered property ends with the filter value   |\n| `=`               | `EqualCaseInsensitive` | Hits when the filtered property equals the filter value (case-insensitive) |\n| `==`              | `EqualCaseSensitive`   | Hits when the filtered property equals the filter value (case-sensitive) |\n| `!`               | `NotEqual`             | Negates the `Default` operator. Operators other than `Default` cannot be negated (currently) |\n| `\u003c`               | `LessThan`             | Hits when the filtered property is less than the filter value |\n| `\u003c=`              | `LessThanOrEqual`      | Hits when the filtered property is less than or equal to the filter value |\n| `\u003e`               | `GreaterThan`          | Hits when the filtered property is greater than the filter value |\n| `\u003e=`              | `GreaterThanOrEqual`   | Hits when the filtered property is greater than or equals the filter value |\n| `ISNULL`          | `IsNull`               | Hits when the filtered property is `null`                    |\n| `NOTNULL`         | `NotNull`              | Hits when the filtered property is not `null`                |\n\n**Escape filter values**\n\nThe backslash (`\\`) is the default escape character. To search for a  backslash, use a double backslash (`\\\\`). Escape filter operator characters and separators to include them in searches.\n\n**HTTP query samples**\n\n| Query parameter                            | Description                                             |\n| ------------------------------------------ | ------------------------------------------------------- |\n| \u003ccode\u003e\u0026gender=\u003cb\u003e=female\u003c/b\u003e\u003c/code\u003e        | Gender equals `female` (case insensitive)               |\n| \u003ccode\u003e\u0026gender=\u003cb\u003e=male,=female\u003c/b\u003e\u003c/code\u003e  | Gender equals `male` OR `female` (case insensitive)     |\n| \u003ccode\u003e\u0026gender=\u003cb\u003e==female\u003c/b\u003e\u003c/code\u003e       | Gender equals `female` (case sensitive)                 |\n| \u003ccode\u003e\u0026gender=\u003cb\u003e~male\u003c/b\u003e\u003c/code\u003e          | Gender contains `male` (fetches `female` too)           |\n| \u003ccode\u003e\u0026tag=\u003cb\u003eISNULL\u003c/b\u003e\u003c/code\u003e            | Tag is `null`                                           |\n| \u003ccode\u003e\u0026tag=\u003cb\u003e=\u003c/b\u003e\u003c/code\u003e                 | Tag equals `\"\"`                                         |\n| \u003ccode\u003e\u0026tag=\u003cb\u003e=\\=A\\;B\u003c/b\u003e\u003c/code\u003e           | Tag equals `=A;B` (case insensitive due escaped syntax) |\n| \u003ccode\u003e\u0026size=\u003cb\u003e\u003c100\u003c/b\u003e\u003c/code\u003e             | Size is lower than `100`                                |\n| \u003ccode\u003e\u0026size=\u003cb\u003e\u003e100\u0026size=\u003c200\u003c/b\u003e\u003c/code\u003e   | Size is between `100` and `200`                         |\n| \u003ccode\u003e\u0026created=\u003cb\u003e\u003etwo-days-ago\u003c/b\u003e\u003c/code\u003e | Created within the last `2 days`                        |\n| \u003ccode\u003e\u0026created=\u003cb\u003eyesterday\u003c/b\u003e\u003c/code\u003e     | Created `yesterday`                                     |\n| \u003ccode\u003e\u0026created=\u003cb\u003e\u003e2020-03\u003c/b\u003e\u003c/code\u003e      | Created after `Sunday, March 1, 2020`                   |\n| \u003ccode\u003e\u0026created=\u003cb\u003e2020\u003c/b\u003e\u003c/code\u003e          | Crated in the year `2020`                               |\n\n## Sort syntax\n\n**Syntax reference**\n\nThe sort micro syntax includes a property name with an optional sort  direction marker (e.g., `customer-asc`). In an HTTP query parameter, you  can use a comma-separated list of properties (e.g.,  `\u0026orderBy=customer,number-desc`).\n\n| Direction  | Position | Values                                       |\n| ---------- | -------- | -------------------------------------------- |\n| ascending  | prefix   | `+`, `asc-`, `asc `                          |\n| ascending  | postfix  | `+`, `-asc`, `  asc`                         |\n| descending | prefix   | `-`, `~`, `desc-`, `dsc-`, `desc `, `dsc `   |\n| descending | postfix  | `-`, `~`, `-desc`, `-dsc`, `  desc`, `  dsc` |\n\n**HTTP query samples**\n\n| Query parameter                                          | Description                                                  |\n| -------------------------------------------------------- | ------------------------------------------------------------ |\n| \u003ccode\u003e\u0026orderBy=\u003cb\u003elastName\u003c/b\u003e\u003c/code\u003e            | Sort by `lastName` ascending                                 |\n| \u003ccode\u003e\u0026orderBy=\u003cb\u003elastName-\u003c/b\u003e\u003c/code\u003e           | Sort by `lastName` descending                                |\n| \u003ccode\u003e\u0026orderBy=\u003cb\u003elastName,-firstName\u003c/b\u003e\u003c/code\u003e | Sort by `lastName` ascending, than by `firstName` descending |\n| \u003ccode\u003e\u0026orderBy=\u003cb\u003elastName.length\u003c/b\u003e\u003c/code\u003e     | Sort by `length of lastName` ascending                       |\n\n## Page Syntax\n\n**Syntax reference**\n\n| Micro\u0026nbsp;syntax | Description            |\n| ----------------- | ---------------------- |\n| `page`            | The page number to get |\n| `pageSize`        | The page size to use   |\n\n**HTTP query samples**\n\n| Query parameter                               | Description                            |\n| --------------------------------------------- | -------------------------------------- |\n| \u003ccode\u003e\u0026page=\u003cb\u003e2\u003c/b\u003e\u0026pageSize=\u003cb\u003e3\u003c/b\u003e\u003c/code\u003e | Takes the 2nd page by a page size of 3 |\n\n# Filter entities\n\n## Basic usage\n\n**Install NuGet packages**\n\n```\nPackage Manager : Install-Package Plainquire.Filter\nCLI : dotnet add package Plainquire.Filter\n```\n**Bind filter from query-parameters**\n\n```csharp\nusing Plainquire.Filter;\n\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntityFilter\u003cOrder\u003e order)\n{\n    return dbContext.Orders.Where(filter).ToList();\n}\n```\n\n**Create filter from code**\n\n ```csharp\nusing Plainquire.Filter;\n\nvar orders = new[] {\n    new Order { Customer = \"Joe Miller\", Number = 100 },\n    new Order { Customer = \"Joe Smith\", Number = 200 },\n    new Order { Customer = \"Joe Smith\", Number = 300 },\n};\n\n// Create filter\nvar filter = new EntityFilter\u003cOrder\u003e()\n    .Add(x =\u003e x.Customer, \"Joe\")\n    .Add(x =\u003e x.Number, FilterOperator.GreaterThan, 250);\n\n// Print filter\nConsole.WriteLine(filter);\n// Output: x =\u003e (((x.Customer != null) AndAlso x.Customer.ToUpper().Contains(\"JOE\")) AndAlso (x.Number \u003e 250))\n\n// Filter using queryables (e.g. Entity Framework)\nvar filteredOrders = dbContext.Orders.Where(filter).ToList();\n\n// Filter using LINQ\nvar filteredOrders = orders.Where(filter).ToList();\n ```\n\n## Configure filters\n\nGenerated filter expressions can be configured via `FilterConfiguration`.\n\n### Create configuration\n\n```csharp\nusing Plainquire.Filter;\n\n// Parse filter values using german locale (e.g. \"5,5\" =\u003e 5.5f).\nvar configuration = new FilterConfiguration { CultureInfo = new CultureInfo(\"de-DE\") };\n```\n\n### Provide configuration\n\n```csharp\n// For MVC model binding via dependency injection\nservices.Configure\u003cFilterConfiguration\u003e(c =\u003e c.IgnoreParseExceptions = true);\n\n// Via constructor\nnew EntityFilter\u003cOrder\u003e(configuration);\n\n// Via static default\nFilterConfiguration.Default\n```\n\n### **Configuration reference**\n\n| Configuration           | Description                                                  |\n| ----------------------- | ------------------------------------------------------------ |\n| `CultureName`           | Culture used for parsing in `languagecode2-country`\u0026hairsp;-\u0026hairsp;`regioncode2` format (e.g., \"en-US\"). |\n| `UseConditionalAccess`  | Controls the use of conditional access to navigation properties |\n| `IgnoreParseExceptions` | Fallback to `x =\u003e true` if any exception occurs during value parsing |\n| `FilterOperatorMap`     | Map between micro syntax and operator. Micro syntax is case-sensitive |\n| `BooleanMap`            | Map between string and boolean. Strings are case-insensitive |\n| `ValueSeparatorChars`   | Characters used to split values in micro syntax              |\n| `EscapeCharacter`       | Escape character used in micro syntax                        |\n\n## Filter by special values\n\n### Filter by `== null` / `!= null`\n\n```csharp\n// For 'Customer is null'\nfilter.Add(x =\u003e x.Customer, FilterOperator.IsNull);\n// Output: x =\u003e (x.Customer == null)\n\n// For 'Customer is not null'\nfilter.Add(x =\u003e x.Customer, FilterOperator.NotNull);\n// Output: x =\u003e (x.Customer != null)\n\n// via query parameter\nvar getOrdersUrl = \"/GetOrders?customer=ISNULL\"\nvar getOrdersUrl = \"/GetOrders?customer=NOTNULL\"\n```\n\nWhile filtered for `== null` / `!= null`, (accidently) given values are ignored:\n\n```csharp\nfilter.Add(x =\u003e x.Customer, FilterOperator.NotNull, \"values\", \"are\", \"ignored\");\n```\n\n### Filter by `\"\"` / `string.Empty`\n\n```csharp\n// For 'Customer == \"\"'\nfilter.Add(x =\u003e x.Customer, string.Empty);\n// Output: x =\u003e (x.Customer == \"\")\n\n// For 'Customer is not null'\nfilter.Add(x =\u003e x.Customer, FilterOperator.NotEqual, string.Empty);\n// Output: x =\u003e (x.Customer != \"\")\n\n// via query parameter\nvar getOrdersUrl = \"/GetOrders?customer=\"\n```\n\n### Filter by Date/Time\n\nDate/Time values can be given in the form of a fault-tolerant [round-trip date/time pattern](https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#Roundtrip)\n\n```csharp\n// Date\nfilter.Add(x =\u003e x.Created, \"\u003e2020/01/01\");\n// Output: x =\u003e (x.Created \u003e 01.01.2020 00:00:00)\n\n// Date/Time\nfilter.Add(x =\u003e x.Created, \"\u003e2020-01-01-12-30\");\n// Output: x =\u003e (x.Created \u003e 01.01.2020 12:30:00)\n\n// Partial values are supported too\nfilter.Add(x =\u003e x.Created, \"2020-01\");\n// Output: x =\u003e ((x.Created \u003e= 01.01.2020 00:00:00) AndAlso (x.Created \u003c 01.02.2020 00:00:00))\n```\n\n### Filter by Date/Time with natural language\n\nThanks to [nChronic.Core](https://github.com/robbell/nChronic.Core) natural language for date/time is supported.\n\n```csharp\n// This\nfilter.Add(x =\u003e x.Created, \"\u003eyesterday\");\n\n// works as well as\nfilter.Add(x =\u003e x.Created, \"\u003e3-months-ago-saturday-at-5-pm\");\n```\n\nDetails can be found here: [https://github.com/mojombo/chronic](https://github.com/mojombo/chronic#simple)\n\n### Filter by enum\n\n`Enum` values can be filtered by its name as well as by it's numeric representation.\n\n```csharp\n// Equals by name\nfilter.Add(x =\u003e x.Gender, \"=divers\");\n// Output: x =\u003e (x.Gender == Divers)\n\n// Equals by numeric value\nfilter.Add(x =\u003e x.Gender, \"=1\");\n// Output: x =\u003e (Convert(x.Gender, Int64) == 1)\n\n// Contains, value is expanded\nfilter.Add(x =\u003e x.Gender, \"~male\");\n// Output: x =\u003e ((x.Gender == Male) OrElse (x.Gender == Female))\n\nenum Gender { Divers, Male, Female }\n```\n\n### Filter by numbers\n\nFilter for numbers support `contains` operator but may be less performant.\n\n```csharp\n// Equals\nfilter.Add(x =\u003e x.Number, \"1\");\n// Output: x =\u003e (x.Number == 1)\n\n// Contains\nfilter.Add(x =\u003e x.Number, \"~1\");\n// Output: x =\u003e x.Number.ToString().ToUpper().Contains(\"1\")\n```\n\n## Logical Operators\n\n### Add/Replace filter using logical OR\n\nMultiple values given to one call are combined using conditional `OR`.\n\n```csharp\n// Customer contains `Joe` || `Doe`\n\nvar filter = new EntityFilter\u003cOrder\u003e();\n\n// via operator\nfilter.Add(x =\u003e x.Customer, FilterOperator.Contains, \"Joe\", \"Doe\");\nfilter.Replace(x =\u003e x.Customer, FilterOperator.Contains, \"Joe\", \"Doe\");\n\n// via syntax\nfilter.Add(x =\u003e x.Customer, \"~Joe,~Doe\");\nfilter.Replace(x =\u003e x.Customer, \"~Joe,~Doe\");\n\n// via query parameter\nvar getOrdersUrl = \"/GetOrders?customer=~Joe,~Doe\"\n```\n\n### Add/Replace filter using logical AND\n\nMultiple calls are combined using conditional `AND`.\n\n```csharp\n// Customer contains `Joe` \u0026\u0026 `Doe`\n\nvar filter = new EntityFilter\u003cOrder\u003e();\n\n// via operator\nfilter\n    .Add(x =\u003e x.Customer, FilterOperator.Contains, \"Joe\")\n    .Add(x =\u003e x.Customer, FilterOperator.Contains, \"Doe\");\n\n// via syntax\nfilter\n    .Add(x =\u003e x.Customer, \"~Joe\")\n    .Add(x =\u003e x.Customer, \"~Doe\");\n\n// via query parameter\nvar getOrdersUrl = \"/GetOrders?customer=~Joe\u0026customer=~Doe\"\n```\n\n## Nested filters\n\nNested objects are filtered directly (`x =\u003e x.Address.City == \"Berlin\"`)\n\nNested lists are filtered using `.Any()` (`x =\u003e x.Items.Any(item =\u003e (item.Article == \"Laptop\"))`)\n\n```csharp\n// Create filters\nvar addressFilter = new EntityFilter\u003cAddress\u003e()\n    .Add(x =\u003e x.City, \"==Berlin\");\n\nvar itemFilter = new EntityFilter\u003cOrderItem\u003e()\n    .Add(x =\u003e x.Article, \"==Laptop\");\n\nvar orderFilter = new EntityFilter\u003cOrder\u003e()\n    .AddNested(x =\u003e x.Address, addressFilter)\n    .AddNested(x =\u003e x.Items, itemFilter);\n\n// Print filter\nConsole.WriteLine(orderFilter);\n// Output:\n// x =\u003e ((x.Address != null) AndAlso (x.Address.City == \"Berlin\"))\n// x =\u003e ((x.Items != null) AndAlso x.Items.Any(x =\u003e (x.Article == \"Laptop\")))\n\npublic class Order\n{\n    public int Number { get; set; }\n    public string Customer { get; set; }\n\n    public Address Address { get; set; }\n    public List\u003cOrderItem\u003e Items { get; set; }\n}\n\npublic record Address(string Street, string City);\npublic record OrderItem(int Position, string Article);\n```\n\n## Retrieve syntax and filter values\n\n```csharp\nvar filter = new EntityFilter\u003cOrder\u003e()\n    .Add(x =\u003e x.Customer, FilterOperator.Contains, \"Joe\", \"Doe\");\n\n// Retrive filter syntax\nstring filterSytax = filter.GetPropertyFilterSyntax(x =\u003e x.Customer);\n// Output: ~Joe,~Doe\n\n// Retrive filter values\nValueFilter[] filterValues = filter.GetPropertyFilterValues(x =\u003e x.Customer);\n// Output:\n// [{\n//   \"Operator\": \"Contains\",\n//   \"Value\": \"Joe\",\n//   \"IsEmpty\": false\n// }, {\n//   \"Operator\": \"Contains\",\n//   \"Value\": \"Doe\",\n//   \"IsEmpty\": false\n// }]\n```\n\n## REST / MVC\n\nTo filter an entity via model binding, the entity must be marked with `EntityFilterAttribute`\n\n### Register model binders\n\n```\nPackage Manager : Install-Package Plainquire.Filter.Mvc\nCLI : dotnet add package Plainquire.Filter.Mvc\n```\n\n```csharp\nusing Plainquire.Filter.Mvc;\n\n// Register required stuff by calling 'AddFilterSupport()' on IMvcBuilder instance\nservices.AddControllers().AddFilterSupport();\n```\n\n### Map HTTP query parameters to `EntityFilter`\n\nWith model binding enabled, REST requests can be filtered using query parameters:\n\n```csharp\nusing Plainquire.Filter;\n\nvar getOrdersUrl = \"/GetOrders?customer==Joe\u0026number=\u003e4711\"\n\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntityFilter\u003cOrder\u003e filter)\n{\n    Console.WriteLine(filter);\n    // Output:\n    // x =\u003e (\n    //   ((x.Customer != null) AndAlso (x.Customer.ToUpper() == \"JOE\"))\n    //   AndAlso (x.Number \u003e 4711)\n    // )\n\n    var queryParams = filter.ToQueryParams();\n    // Output: customer==Joe\u0026number=\u003e4711\n}\n```\n\n### Configure model binding\n\nBy default, parameters for properties of filtered entity are named `{Entity}{Property}`.\nBy default, all public non-complex properties (`string`, `int`, `DateTime`, ...) are recognized.\nParameters can be renamed or removed using  `FilterAttribute` and `EntityFilterAttribute`.\n\nFor the code below `Number` is not mapped anymore and `Customer` becomes `CustomerName`:\n\n```csharp\nusing Plainquire.Filter.Abstractions;\n\n// Remove prefix, e.g. property 'Number' is mapped from 'number', not 'orderNumber'\n[EntityFilter(Prefix = \"\")]\npublic class Order\n{\n     // 'Number' is removed from filter and will be ignored\n    [Filter(Filterable = false)]\n    public int Number { get; set; }\n\n    // 'Customer' is mapped from query-parameter 'customerName'\n    [Filter(Name = \"CustomerName\")]\n    public string Customer { get; set; }\n}\n```\n### Filter sets\n\nMultiple entity filters can be combined to a set of filters using the `EntityFilterSetAttribute`.\n\n```csharp\nusing Plainquire.Filter;\nusing Plainquire.Filter.Abstractions;\n\n// Use\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] OrderFilterSet filterSet)\n{\n    var order = filterSet.Order;\n    var orderItem = filterSet.OrderItem;\n}\n\n// Instead of\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntityFilter\u003cOrder\u003e order, EntityFilter\u003cOrderItem\u003e orderItem) { ... }\n\n[EntityFilterSet]\npublic class OrderFilterSet\n{\n    public EntityFilter\u003cOrder\u003e Order { get; set; }\n    public EntityFilter\u003cOrderItem\u003e OrderItem { get; set; }\n}\n```\n## Swagger / OpenAPI\n### Register OpenAPI support\nSwagger / OpenAPI is supported when using [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore).\n```\nPackage Manager : Install-Package Plainquire.Filter.Swashbuckle\nCLI : dotnet add package Plainquire.Filter.Swashbuckle\n```\n\n```csharp\nusing Plainquire.Filter.Swashbuckle;\n\nservices.AddSwaggerGen(options =\u003e\n{\n    // Register filters used to modify swagger.json\n    options.AddFilterSupport();\n});\n```\n\n### Register XML documentation\n\nTo get descriptions for generated parameters from XML documentation, paths to documentation files can be provided.\n\n```csharp\nservices.AddSwaggerGen(options =\u003e\n{\n    var filterDoc = Path.Combine(AppContext.BaseDirectory, \"Plainquire.Filter.xml\");\n    options.AddFilterSupport(filterDoc);\n    options.IncludeXmlComments(filterDoc);\n});\n```\n\n## Support for Newtonsoft.Json\n\nBy default `System.Text.Json` is used to serialize/convert Plainquire specific stuff. If you like to use Newtonsoft.Json you must register it:\n\n```\nPackage Manager : Install-Package Plainquire.Filter.Mvc.Newtonsoft\nCLI : dotnet add package Plainquire.Filter.Mvc.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Filter.Mvc.Newtonsoft;\n\n// Register support for Newtonsoft by calling\n// 'AddFilterNewtonsoftSupport()' on IMvcBuilder instance\nservices.AddControllers().AddFilterNewtonsoftSupport();\n```\n\n## Interception\n\nCreation of filter expression can be intercepted via `IFilterInterceptor`. While implicit conversions to `Func\u003cTEntity, bool\u003e` and `Expression\u003cFunc\u003cTEntity, bool\u003e\u003e` exists, explicit filter conversion is required to apply an interceptor.\n\n```csharp\nvar filter = new EntityFilter\u003cOrder\u003e();\nvar interceptor = new FilterStringsCaseInsensitiveInterceptor();\nvar filterExpression = filter.CreateFilter(interceptor) ?? (x =\u003e true);\nvar filteredList = orders.Where(filterExpression.Compile());\nvar filteredDb = _dbContext.Orders.Where(filterExpression);\n```\n\n### Default interceptor\n\nA default interceptor can be provided via static `IFilterInterceptor.Default`.\n\n### Sample interceptor\n\nInterceptor to omit filter values having an empty value. Allows to omit filters added by empty query parameters (`\u0026birthday=`) but prevents filtering for empty strings (`\u0026name=`).\n\n```csharp\npublic class OmitEmptyFilterInterceptor : IFilterInterceptor\n{\n    public Expression\u003cFunc\u003cTEntity, bool\u003e\u003e? CreatePropertyFilter\u003cTEntity\u003e(PropertyInfo propertyInfo, IEnumerable\u003cValueFilter\u003e filters, FilterConfiguration configuration)\n    {\n        var nonEmptyFilters = filters.Where(ValueIsNotNullOrEmpty).ToList();\n\n        var noFilterRequired = nonEmptyFilters.Count == 0;\n        return noFilterRequired\n            ? PropertyFilterExpression.EmptyFilter\u003cTEntity\u003e()\n            : PropertyFilterExpression.CreateFilter\u003cTEntity\u003e(propertyInfo, nonEmptyFilters, configuration, this);\n    }\n\n    private static bool ValueIsNotNullOrEmpty(ValueFilter valueFilter)\n        =\u003e !string.IsNullOrEmpty(valueFilter.Value);\n\n    Func\u003cDateTimeOffset\u003e IFilterInterceptor.Now =\u003e () =\u003e DateTimeOffset.Now;\n}\n```\n\n## Advanced scenarios\n\n### Deep copy\n\nThe `EntityFilter\u003cT\u003e` class supports deep cloning by calling the `Clone()` method\n\n```csharp\nvar copy = filter.Clone();\n```\n\n### Casting\n\nFilters can be cast between entities, e.g. to convert them between DTOs and database models.\n\nProperties are matched by type (check if assignable) and name (case-sensitive)\n\n```csharp\nvar dtoFilter = new EntityFilter\u003cOrderDto\u003e().Add(...);\nvar orderFilter = dtoFilter.Cast\u003cOrder\u003e();\n```\n\n### Serialization\n\n#### Using `System.Text.Json`\n\nObjects of type `EntityFilter\u003cT\u003e` can be serialized via `System.Text.Json.JsonSerializer` without further requirements\n\n```csharp\nvar json = JsonSerializer.Serialize(filter);\nfilter = JsonSerializer.Deserialize\u003cEntityFilter\u003cOrder\u003e\u003e(json);\n```\n\n#### Using `Newtonsoft.Json`\n\nWhen using `Newtonsoft.Json` additional converters are required\n\n```\nPackage Manager : Install-Package Plainquire.Filter.Newtonsoft\nCLI : dotnet add package Plainquire.Filter.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Filter.Newtonsoft;\n\nvar json = JsonConvert.SerializeObject(filter, JsonConverterExtensions.NewtonsoftConverters);\nfilter = JsonConvert.DeserializeObject\u003cEntityFilter\u003cOrder\u003e\u003e(json, JsonConverterExtensions.NewtonsoftConverters);\n```\n\n### Combine filter expressions\n\nTo add custom checks to a filter either call `.Where(...)` again\n\n```csharp\nvar filteredOrders = orders\n    .Where(filter)\n    .Where(item =\u003e item.Items.Count \u003e 2);\n```\n\nor where this isn't possible combine filters with `CombineWithConditionalAnd`\n\n```csharp\nusing Plainquire.Filter.Abstractions;\n\nvar extendedFilter = new[]\n    {\n        filter.CreateFilter(),\n        item =\u003e item.Items.Count \u003e 2\n    }\n    .CombineWithConditionalAnd();\n\nvar filteredOrders = orders.Where(extendedFilter.Compile());\n```\n\n# Sort entities\n\n## Basic usage\n\n**Install NuGet packages**\n\n```\nPackage Manager : Install-Package Plainquire.Sort\nCLI : dotnet add package Plainquire.Sort\n```\n**Create a sort**\n\n ```csharp\nusing Plainquire.Sort;\n\nvar orders = new[] {\n    new Order { Customer = \"Joe Miller\", Number = 100 },\n    new Order { Customer = \"Joe Smith\", Number = 200 },\n    new Order { Customer = \"Joe Smith\", Number = 300 },\n};\n\n// Create sort\n var sort = new EntitySort\u003cOrder\u003e()\n     .Add(x =\u003e x.Customer, SortDirection.Ascending)\n     .Add(x =\u003e x.Number, SortDirection.Descending);\n\n// Print sort\nConsole.WriteLine($\"{orders.OrderBy(sort)}\");\n// Output: orders.OrderBy(x =\u003e IIF((x == null), null, x.Customer)).ThenByDescending(x =\u003e x.Number)\n\n// Use sort with LINQ\nvar sortedOrders = orders.OrderBy(sort).ToList();\n// Or queryables (e.g. Entity Framework)\nvar sortedOrders = dbContext.Orders.OrderBy(sort).ToList();\n\n[EntityFilter]\npublic class Order\n{\n    public int Number { get; set; }\n    public string Customer { get; set; }\n}\n ```\n\n**Or bind sort from query-parameters**\n\n```csharp\nusing Plainquire.Sort;\n\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntitySort\u003cOrder\u003e sort)\n{\n    return dbContext.Orders.OrderBy(sort).ToList();\n}\n```\n\n## Configure sorting\n\nGenerated sort expression can be configured via `SortConfiguration`.\n\n### Create configuration\n\n```csharp\nusing Plainquire.Sort.Abstractions;\n\nvar configuration = new SortConfiguration();\nconfiguration.AscendingPostfixes.Add(\"^\");\n```\n\n### Provide configuration\n\n```csharp\n// For MVC model binding via dependency injection\nservices.Configure\u003cSortConfiguration\u003e(c =\u003e c.IgnoreParseExceptions = true);\n\n// Via constructor\nnew EntitySort\u003cOrder\u003e(configuration);\n\n// Via static default\nSortConfiguration.Default\n```\n\n### **Configuration reference**\n\n| Configuration                     | Description                                                  |\n| --------------------------------- | ------------------------------------------------------------ |\n| `AscendingPrefixes`               | Prefixes used to identify an ascending sort order            |\n| `AscendingPostfixes`              | Postfixes used to identify an ascending sort order           |\n| `DescendingPrefixes`              | Prefixes used to identify a descending sort order            |\n| `DescendingPostfixes`             | Postfixes used to identify a descending sort order           |\n| `IgnoreParseExceptions`           | Fallback to `source.OrderBy(x =\u003e 0)` if any exception occurs during value parsing |\n| `UseConditionalAccess`            | Controls the use of conditional access to navigation properties (e.g. `person =\u003e person?.Name`) |\n| `CaseInsensitivePropertyMatching` | Indicates whether to use case-insensitive property matching  |\n\n## Sort entities\n\n```csharp\n// Order is sorted by `Address` ascending.\nvar sort = new EntitySort\u003cOrder\u003e();\n\n// via operator\nsort.Add(x =\u003e x.Address, SortDirection.Ascending);\n\n// via syntax\nsort.Add(\"Address-asc\")\n\n// via query parameter\nvar getOrdersUrl = \"/GetOrders?orderBy=customer-asc\"\n```\n\n## Sort nested entities\n\nNested objects are sorted directly (`x=\u003e x.OrderBy(order =\u003e order.Customer)`).\nDeep property paths (e.g. `order =\u003e order.Customer.Length`) are supported.\nMethods calls (e.g. `order =\u003e order.Customer.SubString(1)`) are not supported for security reasons.\n\nNested lists cannot be sorted directly. You can create an own `EntitySort` for it and sort the nested list by.\n\n```csharp\n// Create sort\nvar addressSort = new EntitySort\u003cAddress\u003e()\n    .Add(x =\u003e x.City);\n\n// AddNested() is equivalent to adding the paths directly\nvar orderSort = new EntitySort\u003cOrder\u003e()\n    .AddNested(x =\u003e x.Address, addressSort);\n\n// Is equivalent to AddNested() above\nvar orderSort = new EntitySort\u003cOrder\u003e()\n    .Add(x =\u003e x.Address.City, SortDirection.Ascending);\n\n// Print sort\nConsole.WriteLine(orders.OrderBy(orderSort).ToString());\n// Output:\n// orders =\u003e orders.OrderBy(x =\u003e IIF((IIF((x == null), null, x.Address) == null), null, x.Address.City))\n\npublic class Order\n{\n    public int Number { get; set; }\n    public string Customer { get; set; }\n    public Address Address { get; set; }\n}\n\npublic record Address(string Street, string City);\n```\n\n## Retrieve syntax and sort direction\n\n```csharp\nvar orderSort = new EntitySort\u003cOrder\u003e()\n    .Add(x =\u003e x.Customer, SortDirection.Ascending);\n\n// Retrive sort syntax\nvar syntax = orderSort.GetPropertySortSyntax(x =\u003e x.Customer);\n// Output: Customer-asc\n\n// Retrive sort direction\nvar direction = orderSort.GetPropertySortDirection(x =\u003e x.Customer);\n// Output: Ascending\n\n// Retrive sort expression string:\nvar orderExpression = orders.OrderBy(orderSort).ToString()\n```\n\n## REST / MVC\n\nTo sort an entity via model binding, the entity must be marked with `EntityFilterAttribute`\n\n### Register model binders\n\n```\nPackage Manager : Install-Package Plainquire.Sort.Mvc\nCLI : dotnet add package Plainquire.Sort.Mvc\n```\n\n```csharp\nusing Plainquire.Sort.Mvc;\n\n// Register required stuff by calling 'AddSortSupport()' on IMvcBuilder instance\nservices.AddControllers().AddSortSupport();\n```\n\n### Map HTTP query parameter to `EntitySort`\n\nWith model binding enabled, REST requests can be sorted using query parameter `orderBy`.\n\n```csharp\nusing Plainquire.Sort;\n\nvar getOrdersUrl = \"/GetOrders?orderBy=customer,number-desc\"\n\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntitySort\u003cOrder\u003e sort)\n{\n    var orders = new List\u003cOrder\u003e();\n    var sortedOrders = orders.OrderBy(sort);\n    Console.WriteLine($\"{sortedOrders.OrderBy(sort)}\");\n    // Output: orders.OrderBy(x =\u003e IIF((x == null), null, x.Customer)).ThenByDescending(x =\u003e x.Number)\n\n    var queryParams = sort.ToString();\n    // Output: Customer-asc, Number-desc\n}\n```\n\n### Configure model binding\n\nBy default, parameters for properties of sorted entity are named `{Entity}{Property}`.\nBy default, all public non-complex properties (`string`, `int`, `DateTime`, ...) are recognized.\nParameters can be renamed or removed using  `FilterAttribute` and `EntityFilterAttribute`.\n\nFor the code below `Number` is not mapped anymore and `Customer` becomes `CustomerName`.\n\n```csharp\nusing Plainquire.Filter.Abstractions;\n\n// Remove prefix, e.g. property 'Number' is mapped from 'number', not 'orderNumber'\n// Use 'sortBy' as query parameter name instead of default 'orderBy'\n[EntityFilter(Prefix = \"\")]\npublic class Order\n{\n     // 'Number' is removed from sort and will be ignored\n    [Filter(Sortable = false)]\n    public int Number { get; set; }\n\n    // 'Customer' is mapped from query-parameter 'customerName'\n    [Filter(Name = \"CustomerName\")]\n    public string Customer { get; set; }\n}\n```\n### Order sets\n\nMultiple entity sorts can be combined to a set of filters using the `EntitySortSetAttribute`.\n\n```csharp\nusing Plainquire.Sort;\nusing Plainquire.Sort.Abstractions;\n\n// Use\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] OrderSortSet orderSet)\n{\n    var orderSort = orderSet.Order;\n    var orderItemSort = orderSet.OrderItem;\n}\n\n// Instead of\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntitySort\u003cOrder\u003e orderSort, EntitySort\u003cOrderItem\u003e orderItemSort) { ... }\n\n[EntitySortSet]\npublic class OrderSortSet\n{\n    public EntitySort\u003cOrder\u003e Order { get; set; }\n    public EntitySort\u003cOrderItem\u003e OrderItem { get; set; }\n}\n```\n## Swagger / OpenAPI\n### Register OpenAPI support\nSwagger / OpenAPI is supported when using [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore).\n```\nPackage Manager : Install-Package Plainquire.Sort.Swashbuckle\nCLI : dotnet add package Plainquire.Sort.Swashbuckle\n```\n\n```csharp\nusing Plainquire.Sort.Swashbuckle;\n\nservices.AddSwaggerGen(options =\u003e\n{\n    // Register filters used to modify swagger.json\n    options.AddSortSupport();\n});\n```\n\n## Support for Newtonsoft.Json\n\nBy default, `System.Text.Json` is used to serialize/convert Plainquire specific stuff. If you like to use Newtonsoft.Json you must register it.\n\n```\nPackage Manager : Install-Package Plainquire.Sort.Mvc.Newtonsoft\nCLI : dotnet add package Plainquire.Sort.Mvc.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Sort.Mvc.Newtonsoft;\n\n// Register support for Newtonsoft by calling\n// 'AddSortNewtonsoftSupport()' on IMvcBuilder instance\nservices.AddControllers().AddSortNewtonsoftSupport();\n```\n\n## Interception\n\nCreation of sort expression can be intercepted via `ISortInterceptor`.\n\n```csharp\nvar sort = new EntitySort\u003cOrder\u003e();\nvar interceptor = new CaseInsensitiveSortInterceptor();\nvar filtered = orders.OrderBy(sort, interceptor);\n```\n\nA default interceptor can be provided via static `ISortInterceptor.Default`.\n\n## Advanced scenarios\n\n### Deep copy\n\nThe `EntitySort\u003cT\u003e` class supports deep cloning by calling the `Clone()` method\n\n```csharp\nvar copy = sort.Clone();\n```\n\n### Casting\n\nSorting can be cast between entities, e.g. to convert them between DTOs and database models.\n\nProperties are matched by type (check if assignable) and name (case-sensitive)\n\n```csharp\nvar dtoSort = new EntitySort\u003cOrderDto\u003e().Add(...);\nvar orderSort = dtoSort.Cast\u003cOrder\u003e();\n```\n\n### Serialization\n\n#### Using `System.Text.Json`\n\nObjects of type `EntitySort\u003cT\u003e` can be serialized via `System.Text.Json.JsonSerializer` without further requirements\n\n```csharp\nvar json = JsonSerializer.Serialize(sort);\nsort = JsonSerializer.Deserialize\u003cEntitySort\u003cOrder\u003e\u003e(json);\n```\n\n#### Using `Newtonsoft.Json`\n\nWhen using `Newtonsoft.Json` additional converters are required\n\n```\nPackage Manager : Install-Package Plainquire.Sort.Newtonsoft\nCLI : dotnet add package Plainquire.Sort.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Sort.Newtonsoft;\n\nvar json = JsonConvert.SerializeObject(sort, JsonConverterExtensions.NewtonsoftConverters);\nsort = JsonConvert.DeserializeObject\u003cEntitySort\u003cOrder\u003e\u003e(json, JsonConverterExtensions.NewtonsoftConverters);\n```\n\n# Page Entities\n\n## Basic usage\n\n**Install NuGet packages**\n\n```\nPackage Manager : Install-Package Plainquire.Page\nCLI : dotnet add package Plainquire.Page\n```\n**Create a page**\n\n ```csharp\nusing Plainquire.Page;\n\n// Direct pageing is the preferred way\nvar pagedOrders = orders.Page(pageNumber: 2, pageSize: 3).ToList();\n\n// Alternative, create a EntityPage object\n var page = new EntityPage(pageNumber: 2, pageSize: 3);\n\n// Use page with LINQ\nvar pagedOrders = orders.Page(page).ToList();\n// Or queryables (e.g. Entity Framework)\nvar pagedOrders = dbContext.Orders.Page(page).ToList();\n\n ```\n\n## Configure pagination\n\n### Create configuration\n\n```csharp\nusing Plainquire.Page.Abstractions;\n\nvar configuration = new PageConfiguration() { IgnoreParseExceptions = true };\n```\n\n### Provide configuration\n\n```csharp\n// For MVC model binding via dependency injection\nservices.Configure\u003cPageConfiguration\u003e(c =\u003e c.IgnoreParseExceptions = true);\n\n// Via constructor\nnew EntityPage\u003cOrder\u003e(configuration);\n\n// Via static default\nPageConfiguration.Default\n```\n\n### **Configuration reference**\n\n| Configuration           | Description                                                  |\n| ----------------------- | ------------------------------------------------------------ |\n| `IgnoreParseExceptions` | Omit paging in case of any exception while parsing the value |\n\n## REST / MVC\n\nTo page an entity via model binding, the entity must be marked with `EntityFilterAttribute`\n\n### Register model binders\n\n```\nPackage Manager : Install-Package Plainquire.Page.Mvc\nCLI : dotnet add package Plainquire.Page.Mvc\n```\n\n```csharp\nusing Plainquire.Page.Mvc;\n\n// Register required stuff by calling 'AddPageSupport()' on IMvcBuilder instance\nservices.AddControllers().AddPageSupport();\n```\n\n### Map HTTP query parameter to `EntityPage`\n\nWith model binding enabled, REST requests can be paged using query parameters `page` and `pageSize`.\n\n```csharp\nusing Plainquire.Page;\n\nvar getOrdersUrl = \"/GetOrders?page=2\u0026pageSize=3\"\n\n[HttpGet]\npublic Task\u003cList\u003cOrder\u003e\u003e GetOrders([FromQuery] EntityPage\u003cOrder\u003e page)\n{\n    return dbContext.Orders.Page(page).ToList();\n}\n```\n\n### Configure model binding\n\nParameters can be renamed `EntityFilterAttribute`.\n\nFor the code below page number is taken from query parameter `pageNumber` and page size from `size`.\n\n```csharp\nusing Plainquire.Filter.Abstractions;\n\n[EntityFilter(PageNumberParameter = \"pageNumber\", PageSizeParameter = \"size\")]\npublic class Order\n{\n    public string Customer { get; set; }\n}\n```\n## Swagger / OpenAPI\n### Register OpenAPI support\nSwagger / OpenAPI is supported when using [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore).\n```\nPackage Manager : Install-Package Plainquire.Page.Swashbuckle\nCLI : dotnet add package Plainquire.Page.Swashbuckle\n```\n\n```csharp\nusing Plainquire.Page.Swashbuckle;\n\nservices.AddSwaggerGen(options =\u003e\n{\n    // Register filters used to modify swagger.json\n    options.AddPageSupport();\n});\n```\n\n## Support for Newtonsoft.Json\n\nBy default, `System.Text.Json` is used to serialize/convert Plainquire specific stuff. If you like to use Newtonsoft.Json you must register it.\n\n```\nPackage Manager : Install-Package Plainquire.Page.Mvc.Newtonsoft\nCLI : dotnet add package Plainquire.Page.Mvc.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Page.Mvc.Newtonsoft;\n\n// Register support for Newtonsoft by calling\n// 'AddPageNewtonsoftSupport()' on IMvcBuilder instance\nservices.AddControllers().AddPageNewtonsoftSupport();\n```\n\n## Interception\n\nCreation of page expression can be intercepted via `IPageInterceptor`.\n\n```csharp\nvar page = new EntityPage();\nvar interceptor = new PageBackwardInterceptor();\nvar paged = orders.Page(page, interceptor);\n```\n\nA default interceptor can be provided via static `IPageInterceptor.Default`.\n\n## Advanced Scenarios\n\n### Deep copy\n\nThe `EntityPage\u003cT\u003e` class supports deep cloning by calling the `Clone()` method\n\n```csharp\nvar copy = page.Clone();\n```\n\n### Serialization\n\n#### Using `System.Text.Json`\n\nObjects of type `EntityPage\u003cT\u003e` can be serialized via `System.Text.Json.JsonSerializer` without further requirements\n\n```csharp\nvar json = JsonSerializer.Serialize(page);\npage = JsonSerializer.Deserialize\u003cEntityPage\u003cOrder\u003e\u003e(json);\n```\n\n#### Using `Newtonsoft.Json`\n\nWhen using `Newtonsoft.Json` additional converters are required\n\n```\nPackage Manager : Install-Package Plainquire.Page.Newtonsoft\nCLI : dotnet add package Plainquire.Page.Newtonsoft\n```\n\n```csharp\nusing Plainquire.Page.Newtonsoft;\n\nvar json = JsonConvert.SerializeObject(page, JsonConverterExtensions.NewtonsoftConverters);\nsort = JsonConvert.DeserializeObject\u003cEntityPage\u003cOrder\u003e\u003e(json, JsonConverterExtensions.NewtonsoftConverters);\n```\n\n# Upgrade from FilterExpressionCreator\n\n* Install `Schick.FilterExpressionCreator*` 4.7.x.\n\n* Fix all warnings. This can largely be done by sear and replacing with regular expressions\n  * Search for: `FS.FilterExpressionCreator(\\.Abstractions|Mvc|Mvc\\.Newtonsoft|Newtonsoft)?(\\.\\w+)?`\n  * Replace with: `Plainquire.Filter$1`\n  * Search for: `\\[FilterEntity(\\(.*\\))]`\n  * Replace with: `[EntityFilter$1]`\n* Fix remaining errors and warnings following description of [breaking changes](https://github.com/plainquire/plainquire/blob/main/HISTORY.md#breaking-changes)\n* Uninstall all `Schick.FilterExpressionCreator*` stuff\n* Install corresponding `Plainquire.*` packages\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplainquire%2Fplainquire","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplainquire%2Fplainquire","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplainquire%2Fplainquire/lists"}