مقدمه
اگر با Apiها کار کرده باشید احتمالاً با این چالش که گاهی نیاز است منابعی (Resources) که به کاربر ارسال میشوند مرتب (Sort)، بر اساس درخواست کاربر فیلتر (Filter) و در صفحهبندی (Paging) مشخصی تحویل داده شوند، برخورد کردهاید. این نیاز خصوصاً در پاسخ (Response) با روش GET از استاندارد HTTP مشهود است. در این مطلب به معرفی کتابخانهای میپردازیم که با استفاده از آن میتوان عملیات فوق را پیادهسازی نمود. Sieve یک چارچوب (Framework) ساده، تمیز و قابل توسعه برای NET Core. است. در زمان نگارش این مقاله ویرایش ۲.۱.۳ از این کتابخانه در دسترس است و همانگونه که اشاره شد، این کتابخانه منبع باز (Open Source) بوده و میتوانید آن را از مخزن گیتهاب در این پیوند دریافت نمایید.سیستمی با یک موجودیت به نام "پست" (Post) مفروض است. با استفاده از کتابخانه Sieve عملیات مرتبسازی، فیلتر و صفحهبندی را هنگام درخواست (GET) تمامی پستها اعمال خواهیم کرد.
// Post public int Id { get; set; } public string Title { get; set; } public int LikeCount { get; set; } public int CommentCount { get; set; } public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
۱. نصب کتابخانه
ابتدا لازم است از طریق Package Manager Console و اجرای دستور فوق اقدام به نصب این کتابخانه نمایید: Install-Package Sieve -Version 2.1.3
۲. اضافه کردن سرویس
در فایل Startup.cs سرویس SieveProcessor را تزریق کنید:
services.AddScoped<SieveProcessor>();
۳. تعیین ویژگیهایی از کلاس برای اعمال مرتبسازی و فیلتر
باید ویژگیهایی (Properties) از کلاس را که میخواهید اعمال مرتبسازی و فیلتر بر روی آنها انجام شوند، مشخص کنید. به دو روش این امر ممکن است:
۱.۳. از طریق اضافه کردن صفت (Attribute) به ویژگیها
تنها ویژگیهایی از کلاس که دارای صفت [(Sieve(CanSort = true, CanFilter = true] باشند، میتوانند مرتب و یا فیلتر شوند (میتوان تنها از یکی از آنها نیز استفاده نمود).
لذا کلاس پست به صورت زیر ویرایش میشود:
// Post public int Id { get; set; } [Sieve(CanFilter = true, CanSort = true)] public string Title { get; set; } [Sieve(CanFilter = true, CanSort = true)] public int LikeCount { get; set; } [Sieve(CanFilter = true, CanSort = true)] public int CommentCount { get; set; } [Sieve(CanFilter = true, CanSort = true, Name = "created")] public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;
۲.۳. از طریق Fluent API
برای استفاده از این روش، ابتدا کلاسی را ایجاد و از کلاس SieveProcessor مشتق کنید. سپس تابع MapProperties موجود در کلاس والد را override کنید.// ApplicationSieveProcessor public class ApplicationSieveProcessor : SieveProcessor { public ApplicationSieveProcessor( IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods) { } protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper) { mapper.Property<Post>(p => p.Title) .CanFilter() .HasName("a_different_query_name_here"); mapper.Property<Post>(p => p.CommentCount) .CanSort(); mapper.Property<Post>(p => p.DateCreated) .CanSort() .CanFilter() .HasName("created_on"); return mapper; } }
حال باید کلاس جدید را تزریق نمایید:
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
۴. دریافت پرسوجوهای (Queries) مرتب/فیلتر/صفحهبندی با اضافه کردن SieveModel به کنترلر (Controller)
برای دریافت پرسوجوهای مرتب/فیلتر/صفحهبندی، SieveModel را به اکشنی (Action) که پستها را برگشت میدهد به عنوان پارامتر اضافه کنید. سپس با فراخوانی تابع Apply با استفاده از تزریق SieveProcessor در کنترلر خود، پرسوجوها را به منابع خود اعمال کنید.
[HttpGet] public JsonResult GetPosts(SieveModel sieveModel) { var result = _dbContext.Posts; result = _sieveProcessor.Apply(sieveModel, result); return Json(result.ToList()); }
۵. ارسال درخواست
با تمام موارد گفته شده، اکنون میتوانید درخواستی را برای دریافت (GET) شامل پرسوجوهای مرتب/فیلتر/صفحهبندی ارسال نمایید. برای مثال:
GET /GetPosts ?sorts= LikeCount,CommentCount,-created // sort by likes, then comments, then descendingly by date created &filters= LikeCount>10,Title@=awesome title, // filter to posts with more than 10 likes, and a title that contains the phrase "awesome title" &page= 1 // get the first page... &pageSize= 10 // ...which contains 10 posts
filters= LikeCount>10,Title@=awesome title &: فیلتر بر اساس پستهایی که تعداد محبوبیت آنها بیش از ۱۰ است و در عنوان خود شامل عبارت "awesome title" میباشند.
page=1 &: صفحه اول ...
pageSize=10 &: ... که شامل ۱۰ پست است.
به صورت رسمیتر:
sorts: فهرست دستورالعملهایی شامل نام ویژگیهایی است که مرتبسازی بر روی آنها اعمال میشود و از طریق کاما (,) از یکدیگر تمایز داده میشوند. با اضافه کردن - قبل از نام ویژگی، آن را به صورت نزولی مرتب نمایید.
- filters: دستورالعملهای جدا شده توسط کاما (,) به صورت {Name}{Operator}{Value} که در آن:
- {Name} نام ویژگیای است که صفت Sieve بر روی آن تعریف شده و یا نام سفارشیای است که کاربر تعیین کرده است.
- همچنین میتوانید بیش از یک نام (برای یای منطقی (OR)) در درون جفت پرانتز باز و بسته و جداکننده یای منطقی (|) داشته باشید. برای مثال: LikeCount|CommentCount)>10) مشخص میکند مقدار LikeCount و یا CommentCount بیش از ۱۰ باشد.
- {Operator} یکی از عملگرهای ممکن است.
- {Value} مقداری است که در عمل فیلتر مورد استفاده قرار میگیرد.
- page: شماره صفحهای است که برگشت داده میشود.
- pageSize: تعداد مواردی است که در هر صفحه برگردانده خواهد شد.
۶. عملگرها (Operators)
عملگر | توضیحات | عملگر | توضیحات |
== | برابر | =@ | شامل |
=! | مخالف | =_ | شروع شود با |
< | بزرگتر | *=@ | شامل (حساس به حروف)* |
> | کوچکتر | *=_ | شروع شود با (حساس به حروف) |
=< | بزرگتر مساوی | *== | برابر (حساس به حروف) |
=> | کوچکتر مساوی | |
۷. پیکربندی
برای پیکربندی شامل مواردی چون حساس به بزرگ و کوچک بودن نام ویژگی، تعداد صفحات پیشفرض، حداکثر تعداد صفحه مجاز و نحوه برخورد با نام ویژگی ناموجود در ویژگیهای کلاس ابتدا قطعه زیر را به appsettings اضافه کنید.
{ "Sieve": { "CaseSensitive": "boolean: should property names be case-sensitive? Defaults to false", "DefaultPageSize": "int number: optional number to fallback to when no page argument is given. Set <=0 to disable paging if no pageSize is specified (default).", "MaxPageSize": "int number: maximum allowed page size. Set <=0 to make infinite (default)", "ThrowExceptions": "boolean: should Sieve throw exceptions instead of silently failing? Defaults to false" } }
services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));