پس از معرفی DNTFrameworkCore، طراحی موجودیتهای سیستم و پیادهسازی DTOها، اعتبارسنجها و سرویسهای متناظر آنها، در این مطلب روش پیاده سازی CRUD API یکسری موجودیت فرضی را با استفاده از امکانات این زیرساخت بررسی خواهیم کرد.
مثال اول: پیاده سازی CRUD API یک موجودیت ساده
در پشت صحنه این اکشن نیز از متد DeleteAsync سرویس مرتبط استفاده میشود.
برای بررسی بیشتر، پیشنهاد میکنم پروژه DNTFrameworkCore.TestAPI موجود در مخزن این زیرساخت را بازبینی کنید.
برای شروع لازم است بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web
همچنین برای اعمال خودکار مهاجرتهای بانک اطلاعاتی، بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web.EntityFramework
سپس با استفاده از متد الحاقی MigrateDbContext به شکل زیر میتوان فرآیند مذکور را خودکار کرد:
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build() .MigrateDbContext<ProjectDbContext>() .Run(); } //... }
[Route("api/[controller]")] public class BlogsController : CrudController<IBlogService, int, BlogModel> { public BlogsController(IBlogService service) : base(service) { } protected override string CreatePermissionName => PermissionNames.Blogs_Create; protected override string EditPermissionName => PermissionNames.Blogs_Edit; protected override string ViewPermissionName => PermissionNames.Blogs_View; protected override string DeletePermissionName => PermissionNames.Blogs_Delete; }
کار با ارثبری از CrudController جنریک شروع میشود؛ سپس نیاز است نوع سرویس، نوع مدل و همچنین نوع شناسه مرتبط با موجودیت موردنظر را از طریق Type Parameter مشخص کنید. این کنترلر پایه تعاریف مختلفی دارد که برحسب نیاز خود میتوانید از آنها استفاده کنید. در ادامه نیز برای اعمال دسترسی خاصی برای عملیات CRUD، نیاز است نام دسترسیها را مشخص کنید. کار تمام است؛ برای استفاده از آن میتوانید با اجرای پروژه DNTFrameworkCore.TestAPI به شکل زیر عمل کنید:
ابتدا نیاز است با استفاده از افزونهی Postman یک Environment جدید ایجاد کنید.
در اینجا دو متغیر endpoint و token برای سرعت بخشیدن به فرآیند تست API تولیدی ایجاد شدهاند. مقدار endpoint آن از ابتدا مشخص میباشد؛ ولی برای مقداردهی token، از ترفند زیر میتوان استفاده کرد:
در برگه Tests آن میتوان متغیر تعریف شده در Environment ایجاد شده را با قطعه کد زیر مقداردهی کرد:
var data = JSON.parse(responseBody) pm.environment.set("token", data.token);
و برای استفاده از این متغیر به شکل زیر عمل کنید:حال برای ارسال درخواستهای HTTP به BlogsController به شکل زیر عمل کنید:
پشت صحنه اکشن GET مربوط به BlogsController از متد سرویس ReadPagedListAsync استفاده میشود که خروجی آن در صورت مشخص نکردن TReadModel، برای یک موجودیت ساده مانند واحد سنجش، از همان TModel استفاده خواهد شد. در تصویر بالا لیست صفحه بندی شده موجودیت Blog را مشاهده میکنید. برای درخواست صفحه دیگر و جستجوی پویا میتوان به شکل زیر عمل کرد:
query={"page":1,"pageSize":100,"filter":{"logic":"and","filters":[{"field":"title","value":"Blog1","operator":"startswith"}]}}
همانطور که در مطالب گذشته اشاره شد، ورودی متدهای Read موجود در سرویسها از نوع IFilteredPagedQueryModel میباشد. یک ModelBinder سفارشی هم برای بایند خودکار این کوئری استرینک با محتوای یک شیء JSON، در زیرساخت طراحی شده است.
در پشت صحنه اکشن POST از متد CreateAsync سرویس مرتبط استفاده میشود و همانطور که در قسمت قبلی عنوان شد، Id و RowVersion مدل ارسالی، مقداردهی خواهد شد.
در پشت صحنه اکشن GET/{id} از متد FindAsync سرویس مرتبط استفاده میشود و خروجی آن از نوع TModel میباشد.
در پشت صحنه اکشن PUT از متد EditAsync سرویس مرتبط استفاده میشود که ورودی آن نوع TModel میباشد. همانطور که قبلا اشاره شده بود و در خروجی حاصل از درخواست ویرایش بالا مشخص میباشد، مکانیزم مدیریت استثناهای حاصل از مباحث همزمانی نیز به درستی انجام شده است.
برای حذف یک Blog میتوان با ارسال درخواست DELETE به آدرس زیر به این هدف رسید:
{{endpoint}}/blogs/10
مثال دوم: پیاده سازی و استفاده از CRUD API در سناریوهای Master-Detail
[Route("api/[controller]")] public class UsersController : CrudController<IUserService, long, UserReadModel, UserModel> { private readonly ILookupService _lookupService; public UsersController(IUserService service, ILookupService lookupService) : base(service) { _lookupService = lookupService ?? throw new ArgumentNullException(nameof(lookupService)); } protected override string CreatePermissionName => PermissionNames.Users_Create; protected override string EditPermissionName => PermissionNames.Users_Edit; protected override string ViewPermissionName => PermissionNames.Users_View; protected override string DeletePermissionName => PermissionNames.Users_Delete; [HttpGet("[action]")] [PermissionAuthorize(PermissionNames.Users_Create, PermissionNames.Users_Edit)] public async Task<IActionResult> RoleList() { var result = await _lookupService.ReadRolesAsync(); return Ok(result); } }
موجودیت User از جمله موجودیتهایی میباشد که نیاز است ReadModel مجزایی برای آن درنظر گرفت؛ چرا که در زمان نمایش لیستی کاربران، نیاز به واکشی گروههای کاربری متصل و دسترسیهای خاص آن، نمیباشد. همچنین اکشن متد RoleList برای دریافت لیست گروههای کاربری موجود در سیستم نیز پیادهسازی شده است. باتوجه به اینکه نیاز است از این اکشن متد در عملیات ثبت و ویرایش استفاده کرد، بر روی آن با استفاده از فیلتر سفارشی PermissionAuthorize، بررسی شده است که کاربر جاری یکی از دسترسیهای Users_Create یا Users_Edit را داشته باشد.
درخواستهای GET و DELETE مشابه مثال اول میباشد؛ برای درخواستهای POST و PUT آن میتوان به شکل زیر عمل کرد:
باتوجه به اینکه UserRole به عنوان یکی از وابستگیهای موجودیت User محسوب میشود، در پاسخ درخواست GET مرتبط با کاربری با شناسه 2، roles آن، لیستی از UserRoleModel هستند که به عنوان یک DetailModel طراحی شده است. به عنوان مثال برای حذف اتصال یک گروه کاربری باید درخواست PUT را به شکل زیر ارسال کنید:
اینبار اگر برای درخواست GET کاربر با شناسه 2 اقدام کنیم، به خروجی زیر خواهم رسید:
{ "userName": "rabbal", "displayName": "غلامرضا ربال", "password": null, "isActive": false, "roles": [], "permissions": [], "ignoredPermissions": [], "rowVersion": "AAAAAAACGxI=", "id": 2 }
برای بررسی بیشتر، پیشنهاد میکنم پروژه DNTFrameworkCore.TestAPI موجود در مخزن این زیرساخت را بازبینی کنید.