{"id":13819873,"url":"https://github.com/Biarity/Sieve","last_synced_at":"2025-05-16T07:32:15.108Z","repository":{"id":41142824,"uuid":"119140400","full_name":"Biarity/Sieve","owner":"Biarity","description":"⚗️ Clean \u0026 extensible Sorting, Filtering, and Pagination for ASP.NET Core","archived":false,"fork":false,"pushed_at":"2024-05-27T20:13:20.000Z","size":1787,"stargazers_count":1239,"open_issues_count":59,"forks_count":137,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-05-14T08:07:41.664Z","etag":null,"topics":["asp-net-core-mvc","aspnetcore","filter","netcore2","pagination","sort"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Biarity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2018-01-27T06:42:15.000Z","updated_at":"2025-05-12T15:25:33.000Z","dependencies_parsed_at":"2024-01-15T15:09:51.574Z","dependency_job_id":"fdebfda7-6036-4822-bbf0-e82e9efb3daf","html_url":"https://github.com/Biarity/Sieve","commit_stats":{"total_commits":160,"total_committers":25,"mean_commits":6.4,"dds":0.58125,"last_synced_commit":"b73f748dba7606da067f644dde4f543c360dfbfe"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Biarity%2FSieve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Biarity%2FSieve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Biarity%2FSieve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Biarity%2FSieve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Biarity","download_url":"https://codeload.github.com/Biarity/Sieve/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254488372,"owners_count":22079420,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["asp-net-core-mvc","aspnetcore","filter","netcore2","pagination","sort"],"created_at":"2024-08-04T08:00:54.313Z","updated_at":"2025-05-16T07:32:15.069Z","avatar_url":"https://github.com/Biarity.png","language":"C#","readme":"# Sieve\n⚗️ Sieve is a simple, clean, and extensible framework for .NET Core that **adds sorting, filtering, and pagination functionality out of the box**. \nMost common use case would be for serving ASP.NET Core GET queries.\n\n[![NuGet Release](https://img.shields.io/nuget/v/Sieve?style=for-the-badge)](https://www.nuget.org/packages/Sieve)\n[![NuGet Pre-Release](https://img.shields.io/nuget/vpre/Sieve?style=for-the-badge)](https://www.nuget.org/packages/Sieve)\n\n[Get Sieve on nuget](https://www.nuget.org/packages/Sieve/)\n\n## Usage for ASP.NET Core\n\nIn this example, consider an app with a `Post` entity. \nWe'll use Sieve to add sorting, filtering, and pagination capabilities when GET-ing all available posts.\n\n### 1. Add required services\n\nInject the `SieveProcessor` service. So in `Startup.cs` add:\n```C#\nservices.AddScoped\u003cSieveProcessor\u003e();\n```\n\n### 2. Tell Sieve which properties you'd like to sort/filter in your models\n\nSieve will only sort/filter properties that have the attribute `[Sieve(CanSort = true, CanFilter = true)]` on them (they don't have to be both true).\nSo for our `Post` entity model example:\n```C#\npublic int Id { get; set; }\n\n[Sieve(CanFilter = true, CanSort = true)]\npublic string Title { get; set; }\n\n[Sieve(CanFilter = true, CanSort = true)]\npublic int LikeCount { get; set; }\n\n[Sieve(CanFilter = true, CanSort = true)]\npublic int CommentCount { get; set; }\n\n[Sieve(CanFilter = true, CanSort = true, Name = \"created\")]\npublic DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;\n\n```\nThere is also the `Name` parameter that you can use to have a different name for use by clients.\n\nAlternatively, you can use [Fluent API](#fluent-api) to do the same. This is especially useful if you don't want to use attributes or have multiple APIs. \n\n### 3. Get sort/filter/page queries by using the Sieve model in your controllers\n\nIn the action that handles returning Posts, use `SieveModel` to get the sort/filter/page query. \nApply it to your data by injecting `SieveProcessor` into the controller and using its `Apply\u003cTEntity\u003e` method. So for instance:\n```C#\n[HttpGet]\npublic JsonResult GetPosts(SieveModel sieveModel) \n{\n    var result = _dbContext.Posts.AsNoTracking(); // Makes read-only queries faster\n    result = _sieveProcessor.Apply(sieveModel, result); // Returns `result` after applying the sort/filter/page query in `SieveModel` to it\n    return Json(result.ToList());\n}\n```\nYou can also explicitly specify if only filtering, sorting, and/or pagination should be applied via optional arguments.\n\n### 4. Send a request\n\n[Send a request](#send-a-request)\n\n### Add custom sort/filter methods\n\nIf you want to add custom sort/filter methods, inject `ISieveCustomSortMethods` or `ISieveCustomFilterMethods` with the implementation being a class that has custom sort/filter methods that Sieve will search through.\n\nFor instance:\n```C#\nservices.AddScoped\u003cISieveCustomSortMethods, SieveCustomSortMethods\u003e();\nservices.AddScoped\u003cISieveCustomFilterMethods, SieveCustomFilterMethods\u003e();\n```\nWhere `SieveCustomSortMethodsOfPosts` for example is:\n```C#\npublic class SieveCustomSortMethods : ISieveCustomSortMethods\n{\n    public IQueryable\u003cPost\u003e Popularity(IQueryable\u003cPost\u003e source, bool useThenBy, bool desc) // The method is given an indicator of whether to use ThenBy(), and if the query is descending \n    {\n        var result = useThenBy ?\n            ((IOrderedQueryable\u003cPost\u003e)source).ThenBy(p =\u003e p.LikeCount) : // ThenBy only works on IOrderedQueryable\u003cTEntity\u003e\n            source.OrderBy(p =\u003e p.LikeCount)\n            .ThenBy(p =\u003e p.CommentCount)\n            .ThenBy(p =\u003e p.DateCreated);\n\n        return result; // Must return modified IQueryable\u003cTEntity\u003e\n    }\n\n    public IQueryable\u003cT\u003e Oldest\u003cT\u003e(IQueryable\u003cT\u003e source, bool useThenBy, bool desc) where T : BaseEntity // Generic functions are allowed too\n    {\n        var result = useThenBy ?\n            ((IOrderedQueryable\u003cT\u003e)source).ThenByDescending(p =\u003e p.DateCreated) :\n            source.OrderByDescending(p =\u003e p.DateCreated);\n\n        return result;\n    }\n}\n```\nAnd `SieveCustomFilterMethods`:\n```C#\npublic class SieveCustomFilterMethods : ISieveCustomFilterMethods\n{\n    public IQueryable\u003cPost\u003e IsNew(IQueryable\u003cPost\u003e source, string op, string[] values) // The method is given the {Operator} \u0026 {Value}\n    {\n        var result = source.Where(p =\u003e p.LikeCount \u003c 100 \u0026\u0026\n                                        p.CommentCount \u003c 5);\n\n        return result; // Must return modified IQueryable\u003cTEntity\u003e\n    }\n\n    public IQueryable\u003cT\u003e Latest\u003cT\u003e(IQueryable\u003cT\u003e source, string op, string[] values) where T : BaseEntity // Generic functions are allowed too\n    {\n        var result = source.Where(c =\u003e c.DateCreated \u003e DateTimeOffset.UtcNow.AddDays(-14));\n        return result;\n    }\n}\n```\n\n## Configure Sieve\nUse the [ASP.NET Core options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) with `SieveOptions` to tell Sieve where to look for configuration. For example:\n```C#\nservices.Configure\u003cSieveOptions\u003e(Configuration.GetSection(\"Sieve\"));\n```\nThen you can add the configuration:\n```json\n{\n    \"Sieve\": {\n        \"CaseSensitive\": \"boolean: should property names be case-sensitive? Defaults to false\",\n        \"DefaultPageSize\": \"int number: optional number to fallback to when no page argument is given. Set \u003c=0 to disable paging if no pageSize is specified (default).\",\n        \"MaxPageSize\": \"int number: maximum allowed page size. Set \u003c=0 to make infinite (default)\",\n        \"ThrowExceptions\": \"boolean: should Sieve throw exceptions instead of silently failing? Defaults to false\",\n        \"IgnoreNullsOnNotEqual\": \"boolean: ignore null values when filtering using is not equal operator? Defaults to true\",\n        \"DisableNullableTypeExpressionForSorting\": \"boolean: disable the creation of nullable type expression for sorting. Some databases do not handle it (yet). Defaults to false\"\n    }\n}\n```\n\n## Send a request\n\nWith all the above in place, you can now send a GET request that includes a sort/filter/page query.\nAn example:\n```curl\nGET /GetPosts\n\n?sorts=     LikeCount,CommentCount,-created         // sort by likes, then comments, then descendingly by date created \n\u0026filters=   LikeCount\u003e10, Title@=awesome title,     // filter to posts with more than 10 likes, and a title that contains the phrase \"awesome title\"\n\u0026page=      1                                       // get the first page...\n\u0026pageSize=  10                                      // ...which contains 10 posts\n\n```\nMore formally:\n* `sorts` is a comma-delimited ordered list of property names to sort by. Adding a `-` before the name switches to sorting descendingly.\n* `filters` is a comma-delimited list of `{Name}{Operator}{Value}` where\n    * `{Name}` is the name of a property with the Sieve attribute or the name of a custom filter method for TEntity\n        * You can also have multiple names (for OR logic) by enclosing them in brackets and using a pipe delimiter, eg. `(LikeCount|CommentCount)\u003e10` asks if `LikeCount` or `CommentCount` is `\u003e10`\n    * `{Operator}` is one of the [Operators](#operators)\n    * `{Value}` is the value to use for filtering\n        * You can also have multiple values (for OR logic) by using a pipe delimiter, eg. `Title@=new|hot` will return posts with titles that contain the text \"`new`\" or \"`hot`\"\n* `page` is the number of page to return\n* `pageSize` is the number of items returned per page \n\nNotes:\n* You can use backslashes to escape special characters and sequences:\n    * commas: `Title@=some\\,title` makes a match with \"some,title\"\n    * pipes: `Title@=some\\|title` makes a match with \"some|title\"\n    * null values: `Title@=\\null` will search for items with title equal to \"null\" (not a missing value, but \"null\"-string literally)\n* You can have spaces anywhere except *within* `{Name}` or `{Operator}` fields\n* If you need to look at the data before applying pagination (eg. get total count), use the optional paramters on `Apply` to defer pagination (an [example](https://github.com/Biarity/Sieve/issues/34))\n* Here's a [good example on how to work with enumerables](https://github.com/Biarity/Sieve/issues/2)\n* Another example on [how to do OR logic](https://github.com/Biarity/Sieve/issues/8)\n\n### Nested objects\nYou can filter/sort on a nested object's property by marking the property using the Fluent API. \nMarking via attributes not currently supported.\n\nFor example, using this object model:\n\n```C#\npublic class Post {\n    public User Creator { get; set; }\n}\n\npublic class User {\n    public string Name { get; set; }\n}\n```\n\nMark `Post.User` to be filterable:\n```C#\n// in MapProperties\nmapper.Property\u003cPost\u003e(p =\u003e p.Creator.Name)\n    .CanFilter();\n```\n\nNow you can make requests such as: `filters=User.Name==specific_name`.\n\n### Creating your own DSL\nYou can replace this DSL with your own (eg. use JSON instead) by implementing an [ISieveModel](https://github.com/Biarity/Sieve/blob/master/Sieve/Models/ISieveModel.cs). You can use the default [SieveModel](https://github.com/Biarity/Sieve/blob/master/Sieve/Models/SieveModel.cs) for reference.\n\n### Operators\n| Operator   | Meaning                  |\n|------------|--------------------------|\n| `==`       | Equals                   |\n| `!=`       | Not equals               |\n| `\u003e`        | Greater than             |\n| `\u003c`        | Less than                |\n| `\u003e=`       | Greater than or equal to |\n| `\u003c=`       | Less than or equal to    |\n| `@=`       | Contains                 |\n| `_=`       | Starts with              |\n| `_-=`      | Ends with                |\n| `!@=`      | Does not Contains        |\n| `!_=`      | Does not Starts with     |\n| `!_-=`     | Does not Ends with       |\n| `@=*`      | Case-insensitive string Contains |\n| `_=*`      | Case-insensitive string Starts with |\n| `_-=*`     | Case-insensitive string Ends with |\n| `==*`      | Case-insensitive string Equals |\n| `!=*`      | Case-insensitive string Not equals |\n| `!@=*`     | Case-insensitive string does not Contains |\n| `!_=*`     | Case-insensitive string does not Starts with |\n\n### Handle Sieve's exceptions\n\nSieve will silently fail unless `ThrowExceptions` in the configuration is set to true. 3 kinds of custom exceptions can be thrown:\n\n* `SieveMethodNotFoundException` with a `MethodName`\n* `SieveIncompatibleMethodException` with a `MethodName`, an `ExpectedType` and an `ActualType`\n* `SieveException` which encapsulates any other exception types in its `InnerException`\n\nIt is recommended that you write exception-handling middleware to globally handle Sieve's exceptions when using it with ASP.NET Core.\n\n\n### Example project\nYou can find an example project incorporating most Sieve concepts in [SieveTests](https://github.com/Biarity/Sieve/tree/master/SieveTests).\n\n## Fluent API\nTo use the Fluent API instead of attributes in marking properties, setup an alternative `SieveProcessor` that overrides `MapProperties`. For [example](https://github.com/Biarity/Sieve/blob/master/Sieve.Sample/Services/ApplicationSieveProcessor.cs):\n\n```C#\npublic class ApplicationSieveProcessor : SieveProcessor\n{\n    public ApplicationSieveProcessor(\n        IOptions\u003cSieveOptions\u003e options, \n        ISieveCustomSortMethods customSortMethods, \n        ISieveCustomFilterMethods customFilterMethods) \n        : base(options, customSortMethods, customFilterMethods)\n    {\n    }\n\n    protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)\n    {\n        mapper.Property\u003cPost\u003e(p =\u003e p.Title)\n            .CanFilter()\n            .HasName(\"a_different_query_name_here\");\n\n        mapper.Property\u003cPost\u003e(p =\u003e p.CommentCount)\n            .CanSort();\n\n        mapper.Property\u003cPost\u003e(p =\u003e p.DateCreated)\n            .CanSort()\n            .CanFilter()\n            .HasName(\"created_on\");\n\n        return mapper;\n    }\n}\n```\n\n\n\nNow you should inject the new class instead:\n```C#\nservices.AddScoped\u003cISieveProcessor, ApplicationSieveProcessor\u003e();\n```\nFind More on Sieve's Fluent API [here](https://github.com/Biarity/Sieve/issues/4#issuecomment-364629048).\n\n### Modular Fluent API configuration\nAdding all fluent mappings directly in the processor can become unwieldy on larger projects.\nIt can also clash with vertical architectures.\nTo enable functional grouping of mappings the `ISieveConfiguration` interface was created together with extensions to the default mapper.\n```C#\npublic class SieveConfigurationForPost : ISieveConfiguration\n{\n    public void Configure(SievePropertyMapper mapper)\n    {\n        mapper.Property\u003cPost\u003e(p =\u003e p.Title)\n            .CanFilter()\n            .HasName(\"a_different_query_name_here\");\n\n        mapper.Property\u003cPost\u003e(p =\u003e p.CommentCount)\n            .CanSort();\n\n        mapper.Property\u003cPost\u003e(p =\u003e p.DateCreated)\n            .CanSort()\n            .CanFilter()\n            .HasName(\"created_on\");\n\n        return mapper;\n    }\n}\n```\nWith the processor simplified to:\n```C#\npublic class ApplicationSieveProcessor : SieveProcessor\n{\n    public ApplicationSieveProcessor(\n        IOptions\u003cSieveOptions\u003e options, \n        ISieveCustomSortMethods customSortMethods, \n        ISieveCustomFilterMethods customFilterMethods) \n        : base(options, customSortMethods, customFilterMethods)\n    {\n    }\n\n    protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)\n    {\n        return mapper\n            .ApplyConfiguration\u003cSieveConfigurationForPost\u003e()\n            .ApplyConfiguration\u003cSieveConfigurationForComment\u003e();       \n    }\n}\n```\nThere is also the option to scan and add all configurations for a given assembly\n```C#\npublic class ApplicationSieveProcessor : SieveProcessor\n{\n    public ApplicationSieveProcessor(\n        IOptions\u003cSieveOptions\u003e options, \n        ISieveCustomSortMethods customSortMethods, \n        ISieveCustomFilterMethods customFilterMethods) \n        : base(options, customSortMethods, customFilterMethods)\n    {\n    }\n\n    protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)\n    {\n        return mapper.ApplyConfigurationsFromAssembly(typeof(ApplicationSieveProcessor).Assembly);            \n    }\n}\n```\n\n## Upgrading to v2.2.0\n\n2.2.0 introduced OR logic for filter values. This means your custom filters will need to accept multiple values rather than just the one.\n\n* In all your custom filter methods, change the last argument to be a `string[] values` instead of `string value`\n* The first value can then be found to be `values[0]` rather than `value`\n* Multiple values will be present if the client uses OR logic\n\n## Upgrading from v1.* to v2.*\n\n* Changes to the `SieveProcessor` API:\n    * `ApplyAll` is now `Apply`\n    * `ApplyFiltering`, `ApplySorting`, and `ApplyPagination` are now depricated - instead you can use optional arguments on `Apply` to achieve the same\n* Instead of just removing commas from `{Value}`s, [you'll also need to remove brackets and pipes](#send-a-request)\n\n\n## License \u0026 Contributing\nSieve is licensed under Apache 2.0. Any contributions highly appreciated!\n","funding_links":[],"categories":["others","C#","🗒️ Cheatsheets","C\\#"],"sub_categories":["📦 Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBiarity%2FSieve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FBiarity%2FSieve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBiarity%2FSieve/lists"}