در این مقاله، ما مبحث فیلترینگ را در ASP.NET Core Web API پوشش میدهیم. ما یاد میگیرم که فیلترینگ چیست و چه تفاوتی با searching دارد و اینکه نحوه پیاده سازی آن در یک پروژه واقعی به چه صورت است.
چنانچه، مانند صفحه بندی خیلی مورد حیاتی نیست، اما فیلترینگ، همچنان یک قسمت مهم از REST API انعطاف پذیر است. بنابراین ما باید بدانیم که چطور آن را در پروژه API مان پیاده سازی کنیم. فیلترینگ به ما کمک می کند تا به جای دریافت کل نتایج بدون هیچگونه معیار، نتیجه دقیق مورد نظر خود را بدست آوریم.
نقطه شروع سورس برای این مقاله را میتوانید از اینجا و نقطه پایانی سورس را از اینجا دانلود کنید.
توجه: برای دنبال کردن این مقاله، کمی دانش پیشنیاز لازم است. اگر نحوه ایجاد دیتابیس یا نحوه کارکرد معماری مورد نظر را نمیدانید، به شدت توصیه میکنم که مقاله های مورد نظر را رصد نمایید.
در این مقاله، میخواهیم موارد زیر را یاد بگیریم:
- فیلترینگ چیست؟
- تفاوت فیلترینگ با Searching
- نحوه پیاده سازی فیلترینگ در ASP.NET Core Web API
- ارسال و تست تعدادی از Request ها
خب بریم تا وارد بحث بشیم.
فیلترینگ چیست؟
فیلتر کردن، مکانیزمی برای بازیابی نتایج با لحاظ کردن تعدادی معیار است. ما میتوانیم برای گرفتن نتایج، تعدادی فیلتر با نوع ویژگی کلاس، محدوده مقداری، محدوده تاریخ یا هرچیز دیگری بنویسیم.
هنگام اجرای فیلترینگ، شما همیشه با مجموعه گزینه های از پیش تعیین شده ای که می توانید در درخواست خود تنظیم کنید محدود می شوید. به عنوان مثال، شما میتوانید یک مقدار تاریخ برای درخواست کردن یک owner account بفرستید، اما نتیجه ای را دریافت نکنید.
در قسمت front end، فیلترینگ معمولا به صورت چک باکسها، دکمه های رادیویی یا منوهای کشویی پیاده سازی میشوند. این نوع از پیاده سازی، شما را فقط به انتخاب این گزینه ها که برای ایجاد یک فیلتر معتبر موجود هستند محدود میکند.
یک وب سایت فروش خودرو را به عنوان مثال در نظر بگیرید. زمانیکه میخواهید خودروها را فیلتر کنید، به طور ایده آل، میخواهید موارد زیر را انتخاب کنید:
- تولید کننده خودرو را به عنوان یک دسته بندی از یک لیست یا یک منوی کشویی
- مدل خودرو را از یک لیست یا یک منوی کشویی
- آیا صفر است یا کارکرده به صورت دکمه های رادیویی
- شهر فروشنده به عنوان یک منوی کشویی
- قیمت ماشین به عنوان یک فیلد input (عددی)
- …
حالا که موضوع را گرفتید، بنابراین درخواست میتواند شبیه چیزی به مانند زیر باشد:
1 |
https://bestcarswebsite.com/sale?manufacturer=ford&model=expedition&state=used&city=washington&price_from=30000&price_to=50000 |
یا حتی مثل این:
1 |
https://bestcarswebsite.com/sale/filter?data[manufacturer]=ford&[model]=expedition&[state]=used&[city]=washington&[price_from]=30000&[price_to]=50000 |
یا هرچیز دیگری که برای شما منطقیتر به نظر میرسد. API باید این فیلتر را تجزیه کند ، بنابراین لازم نیست خیلی با آن دیوانه وار رفتار کنیم :).
حالا که میدانیم فیلترینگ چیست، ببینیم که تفاوت آن با searching چیست.
تفاوت فیلترینگ با Searching چیست
هنگام search نتایج، معمولاً فقط یک ورودی دارید و آن ورودی، چیزی است که برای جستجوی هر چیزی در وب سایت استفاده می کنید.
به عبارتی دیگر، شما یک رشته به API میفرستید و API پاسخگو برای پیدا کردن هر نتیجه ای که با آن رشته ورودی تطبیق داشته باشد میباشد.
در مثال وبسایت خودرو، ما از فیلد search برای پیدا کردن مدل خودرو “Ford Expedition” استفاده میکنیم و تمام نتایجی که با نام خودروی “Ford Expedition” تطابق داشته باشد را دریافت میکنیم. این تمام خودروهای “Ford Expedition” موجود را برمیگرداند.
ما میتوانیم جست و جو را با پیاده سازی search term ها مانند کاری که گوگل انجام میدهد بهبود دهیم. اگر کاربر Ford Expedition را بدون کوتیشن در فیلد جست و جو وارد کرد، آنگاه هر چیزی که مربوط به Ford و Expedition است را برمیگردانیم. اما، اگر کاربر، کوتیشن را اول و آخر آن عبارت گذاشت، ما کل عبارت “Ford Expedition” را در دیتابیسمان جست و جو میکنیم.
این باعت تجربه کاربری بهتری میشود.
مثال:
1 |
https://bestcarswebsite.com/sale/search?name=ford focus |
استفاده از Search به این معنی نیست که نمیتوانیم فیلترها را به همراه آن استفاده کنیم. این کاملا منطقی است که فیلترینگ و searching را با یکدیگر استفاده کنیم. بنابراین، همگام نوشتن سورسمان، باید این مورد را در نظر بگیریم.
اما تئوری کافیست.
بیاید تعدادی فیلتر را پیاده سازی کنیم.
نحوه پیاده سازی فیلترینگ در ASP.NET Core Web API
ما یک ویژگی DateOfBirth در کلاس Owner داریم. بگوییم که میخواهیم آن Owner هایی که بین سالهای 1975 و 1997 به دنیا آمده اند را پیدا کنیم. همچنین میخواهیم این امکان وجود داشته باشد که فقط سال شروع و یا فقط سال پایان را بتوانیم وارد کنیم.
یک کوئری مانند این نیاز داریم:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997 |
اما میخواهیم بتوانیم همچین کوئری را نیز داشته باشیم:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975 |
یا مانند این:
1 |
https://localhost:5001/api/owner?maxYearOfBirth=1997 |
خب، ما یک مشخصاتی برای انجام این کار داریم. نحوه پیاده سازی آن را ببینیم.
ما قبلا صفحه بندی را در کنترلرمان پیاده سازی کرده بودیم. بنابراین زیرساخت لازم برای توسعه را جهت عملیات فیلترینگ در اختیار داریم. ما از کلاس OwnerParameters برای تعریف پارامترهای کوئری برای درخواست صفحه بندی استفاده کرده بودیم.
کلاس OwnerParameters را برای پشتیبانی فیلترینگ نیز توسعه میدهیم:
1 2 3 4 5 6 7 |
public class OwnerParameters : QueryStringParameters { public uint MinYearOfBirth { get; set; } public uint MaxYearOfBirth { get; set; } = (uint)DateTime.Now.Year; public bool ValidYearRange => MaxYearOfBirth > MinYearOfBirth; } |
ما دو خصوصیت int بدون علامت (برای اجتناب از پذیرش مقادیر منفی برای سال) به نامهای MinYearOfBirth
و MaxYearOfBirth
را اضافه کردیم.
از آنجایی که مقدار پیش فرض uint صفر است ، نیازی به تعریف صریح آن نداریم ، صفر در این مورد ما مناسب است. برای ویژگی MaxYearOfBirth، میخواهیم آن را به سال کنونی ست کنیم. اگر آن را از طریق پارامترهای کوئری دریافت نکنیم، اینجا مقداری را برای کار کردن با آن در اختیار داریم. مهم نیست اگر شخصی، سال را از طریق پارامترها به 3053 ست کند، چرا که بر روی نتایج تاثیری ندارد.
همچنین یک ویژگی ساده اعتبارسنجی به نام ValidYearRange اضافه کرده ایم. هدف آن این است که به ما بگوید که آیا max year بزرگتر یا مساوی با min year است یا خیر. اگر نبود، در این صورت میخواهیم کاربر API بداند که اشتباهی در کارش انجام داده است.
خب، حالا که پارامترهای ما آماده است، میتوانیم کنترلرمان را توسعه دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[HttpGet] public IActionResult GetOwners([FromQuery] OwnerParameters ownerParameters) { if (!ownerParameters.ValidYearRange) { return BadRequest("Max year of birth cannot be less than min year of birth"); } var owners = _repository.Owner.GetOwners(ownerParameters); var metadata = new { owners.TotalCount, owners.PageSize, owners.CurrentPage, owners.TotalPages, owners.HasNext, owners.HasPrevious }; Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(metadata)); _logger.LogInfo($"Returned {owners.TotalCount} owners from database."); return Ok(owners); } |
همانطور که میبینید، کار زیادی اینجا انجام ندادیم. ما فقط در خط 4 تا 7، چک میکنیم که تاریخها معتبر باشند و یک BadRequest
response را به همراه یک پیغام کوتاه برای کاربر API اینجا اضافه کرده ایم.
این کار بهتر است که در کنترلر انجام شود.
حال بریم و کلاس OwnerRepository مان را پیاده سازی کنیم:
1 2 3 4 5 6 7 8 9 10 |
public PagedList<Owner> GetOwners(OwnerParameters ownerParameters) { var owners = FindByCondition(o => o.DateOfBirth.Year >= ownerParameters.MinYearOfBirth && o.DateOfBirth.Year <= ownerParameters.MaxYearOfBirth) .OrderBy(on => on.Name); return PagedList<Owner>.ToPagedList(owners, ownerParameters.PageNumber, ownerParameters.PageSize); } |
در واقع ، در این مرحله ، پیاده سازی آن نیز بسیار ساده است.
ما داریم از متد FindByCondition برای پیدا کردن تمام owner هایی که تاریخ تولدشان بین MaxYearOfBirth
و MinYearOfBirth
است استفاده میکنیم.
کاملا ساده است. درسته؟
حالا آن را امتحان میکنیم.
ارسال و تست تعدادی از Request ها
با توجه به مشخصات لحاظ شده، ما 3 مورد برای آزمایش داریم.
به عنوان مرجع، اینها owner های موجود در دیتابیس ما هستند:
اول فقط پارامتر minYearOfBirth را تست میکنیم:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975 |
در این حالت، نباید Anna Bosh را در نتایجمان ببینیم.
در دومین تست باید فقط maxYearOf Birth را لحاظ کنیم:
1 |
https://localhost:5001/api/owner?maxYearOfBirth=1997 |
حالا Nick Somion نباید بین owner های ما ظاهر شود.
و تست نهایی، هر دوی minYearOfBirth
و maxYearOfBirth
را شامل میشود:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997 |
در این حالت، Anna و Nick هیچ یک نباید در نتایج ظاهر شوند.
همچنین باید بررسی کنیم که آیا اعتبارسنجی ما کار میکند یا خیر:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1974 |
ما باید یک bad request به همراه یک پیغام مبنی بر اینکه محدوده تاریخ وارد شده نامعتبر است دریافت کنیم.
برای تکمیل این موضوع ، می توانیم فیلترینگ را با solution صفحه بندی فعلی خود ترکیب کنیم:
1 |
https://localhost:5001/api/owner?minYearOfBirth=1975&maxYearOfBirth=1997&pageSize=2&pageNumber=2 |
آیا میتوانید حدس بزنید که نتیجه چیست (راهنمایی: فقط یک شخص به عنوان نتیجه برگردانده میشود)؟ اگر حدس زدید اجازه دهید تا در بخش نظرات آن را بدانیم.
تمام. ما تمام حالتهای ممکن را تست کردیم.
نتیجه گیری
ما مفهوم مهم دیگری را در ساخت RESTful API ها پوشش دادیم. فیلترینگ قطعا همانند صفحه بندی مهم نیست، اما اغلب در API ها نیاز است. زیرا میتوانیم نتایج را به چیزی که فقط تمایل داریم ببینیم محدود کنیم.
وقتی می دانید چه کاری انجام دهید راه حل بسیار ساده میشود. پیاده سازی آن نیز آسان بود ، زیرا ما در مقاله صفحه بندی خود، زیرساخت را آماده کرده بودیم.
در این مقاله، ما یاد گرفتیم که:
- فیلترینگ چیست
- تفاوت آن با searching چیست
- نحوه پیاده سازی فیلترینگ در ASP.NET Core به چه صورت است
- آزمایش پیاده سازیمان
امیدوارم که چیز مفید و جدیدی را یاد گرفته باشید. در مقاله بعدی ما searching را پوشش میدهیم.
سلام
فکر کنم شخص sam query به عنوان نتیجه برگردانده بشه
سلام دوست عزیز
چرا فکر میکنید sam query به عنوان نتیجه برگردانده میشه؟
Martim Miler پاسخ صحیح کوئری شماست