پیاده سازی پروژه API در دات نت ، قسمت پنجم ، عملیات CRUD در  RestAPI

سلام :)قسمت های قبلی رو اینجا میتونید ببینید.پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayerپیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPIپیاده سازی پروژه API در دات نت ، قسمت چهارم ، RestAPI Authentication Jwtالبته هنوز از کارمون مونده ولی سورس کامل این مقالات رو میتونید از اینجا ببینید.در پایان قسمت قبلی ، عملیات لاگین کاربرانمون با موفقیت انجام شد ، ادامه ی کار که اضافه کردن ، ویرایش و حذف موجودیت UserExam یا همون آزمونهای کاربرانمون هستش و نیاز به اعتبار سنجی کاربر هستش رو ادامه میدیم.برای شروع یک کنترلر جدید به نام ExamController در کنترلر های پروژه Api میسازیم ، سپس سرویس آزمون کاربران رو توسط اینترفیس IUserExamService داخل سازنده ی کنترلر تزریق میکنیم ، به این شکلExamController.cs [Route(&quotapi/[controller]&quot)] [ApiController] public class ExamController : ControllerBase { private readonly IUserExamService _userexamService; public ExamController(IUserExamService UserexamService) { this._userexamService = UserexamService; } }سپس شروع به نوشتن اکشن های مد نظرمون میکنیم ، با ثبت آزمون شروع میکنیم بنابرین یک DTO برای انتقال اطلاعات آزمون برای ثبت آزمون جدید به نام AddUserExamDto در پروژه ی ApplicationServices ، پوشه ی Src 01.Core ApplicationServices Models UserExam میسازیم. AddUserExamDtopublic class AddUserExamDto { public string Name { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } }همچنین باید در سرویس UserExamService یک متد برای پیاده سازی عملیات اضافه کردن آزمون اضافه کنیم ، ابتدا در اینترفیس IUserExamService و سپس در خود سرویس UserExamService متد MakeExam را پیاده میکنیم . از آنجا که میدونیم که قراره سرویس ما چه عملیات هایی ( اضافه کردن آزمون ، ویرایش آزمون ، گرفتن آزمون های کاربر و در نهایت حذف آزمون ) رو پیاده کنه تمام عملیات های مد نظرمون رو یکجا به اینترفیس و سرویس UserExamService اضافه میکنیم .IUserExamService public interface IUserExamService { DomainClass.UserExam.UserExam MakeExam(AddUserExamDto inputExam,string? currentUser); DomainClass.UserExam.UserExam Edit(EditUserExamDto inputExam, string? currentUser); List<UserExamItem> GetExam(int pageNumber , int pageSize, string? currentUser); bool Delete(long ID, string? currentUser); }و پیاده سازی اونها در سرویس UserExamService UserExamService public class UserExamService : IUserExamService { private readonly IUserExamRepository _examRepository; private readonly IUserService _userService; public UserExamService(IUserExamRepository examRepository, IUserService userService) { _examRepository = examRepository; _userService = userService; } public DomainClass.UserExam.UserExam MakeExam(AddUserExamDto inputExam, string? currentUser) { DomainClass.UserExam.UserExam itemForAdd = new DomainClass.UserExam.UserExam() { Name = inputExam.Name, User = _userService.GetUserByMobile(currentUser), StartDate = inputExam.StartDate, EndDate = inputExam.EndDate, CreatedDate = DateTime.Now, }; _examRepository.Insert(itemForAdd); _examRepository.SaveChanges(); return itemForAdd; } public bool Delete(long ID, string? currentUser) { var exam = _examRepository.Get(Convert.ToInt32(ID)); var user = _userService.GetUserByMobile(currentUser); if (exam.User.Id == user.Id) { exam.IsDeleted = true; _examRepository.Update(exam); _examRepository.SaveChanges(); } else { throw new UnauthorizedAccessException(); } return true; } public DomainClass.UserExam.UserExam Edit(EditUserExamDto inputExam, string? currentUser) { var exam = _examRepository.Get(inputExam.Id); var user = _userService.GetUserByMobile(currentUser); if (exam.User.Id == user.Id) { exam.Name = inputExam.Name; exam.EndDate = inputExam.EndDate; exam.StartDate = inputExam.StartDate; _examRepository.Update(exam); _examRepository.SaveChanges(); } else { throw new UnauthorizedAccessException(); } return exam; } public List<UserExamItem> GetExam(int pageNumber, int pageSize, string? currentUser) { var user = _userService.GetUserByMobile(currentUser); return _examRepository.GetQueryable().Where(u => u.User.Id.ToString().ToLower() == user.Id.ToString().ToLower()).Skip((pageNumber - 1) * pageSize).Take(pageSize).Select(o => new UserExamItem { Id = o.Id, Name = o.Name, StartDate = o.StartDate, EndDate = o.EndDate }).ToList(); } }خب در UserExamService.cs ابتدا در سازنده ی سرویس ریپازیتوری IUserExamRepository و سرویس IUserService رو تزریق کردیم ، در این سرویس ما نیاز به اطلاعات کاربر داریم و این اطلاعات توسط سرویس UserService و متد GetUserByMobile که قبلتر نوشتیم گرفته میشه ، IUserExamRepository هم ریپازیتوری خود آزمون هامون هستش و برای ذخیره و ویرایش و بازیابی اطلاعات از دیتا بیسمون لازمش داریم.ابتدا متد MakeExam که برای ساخت آزمون هستش رو پیاده کردیم به این شکلUserExamService.cs public DomainClass.UserExam.UserExam MakeExam(AddUserExamDto inputExam, string? currentUser) { DomainClass.UserExam.UserExam itemForAdd = new DomainClass.UserExam.UserExam() { Name = inputExam.Name, User = _userService.GetUserByMobile(currentUser), StartDate = inputExam.StartDate, EndDate = inputExam.EndDate, CreatedDate = DateTime.Now, }; _examRepository.Insert(itemForAdd); _examRepository.SaveChanges(); return itemForAdd; }ورودیمون یک AddUserExamDto برای اطلاعات ثبت آزمون و همچنین شماره موبایل کاربری که آزمون رو ثبت میکنه به اسم متغیر currentUser هستش ، در ادامه ار دامین مدل UserExam یک شیئ ساختیم و اطلاعات اون رو از ورودیمون پر کردیم ، برای دریافت کد کاربر هم از سرویس User متد GetUserByMobile استفاده کردیم که موبایل رو میگیره و کاربر رو به ما میده ، در نهایت شیئ ایجاد شده رو توسط ریپازیتوریمون Insert و سپس در دیتا بیس ذخیره میکنیم ، در نهایت مدل ساخته شدمون رو به عنوان خروجی برمیگردونیم.در ادامه برای ویرایش آزمون متد Edit رو پیاده کردیمUserExamService.cs public DomainClass.UserExam.UserExam Edit(EditUserExamDto inputExam, string? currentUser) { var exam = _examRepository.Get(inputExam.Id); var user = _userService.GetUserByMobile(currentUser); if (exam.User.Id == user.Id) { exam.Name = inputExam.Name; exam.EndDate = inputExam.EndDate; exam.StartDate = inputExam.StartDate; _examRepository.Update(exam); _examRepository.SaveChanges(); } else { throw new UnauthorizedAccessException(); } return exam; }در ویرایش هم اطلاعات ورودیمون برای آزمون مدل EditUserExamDto و برای کاربر متغیر currentUser هستش ، ابتدا آزمونی که قرار هستش ویرایش بشه رو از ریپازیتوری دریافت کردیم ، کاربر رو هم از سرویس UserService گرفتیم ، سپس چک میکنیم که آیا آزمونی که میخواهیم ویرایش کنیم برای همین کاربر هست یا نه ، اگر نبود که خطای UnauthorizedAccessException به معنی عدم مجوز لازم برمیگردونیم و اگر بود مقادیر جدید رو از ورودی EditUserExamDto به آیتمی که ویرایش میکنیم نسبت میدیم و در آخر توسط ریپازیتوری ذخیره میکنیم و همون مدل ویرایش شده رو به عنوان خروجی برمیگردونیم.برای حذف هم از متد Delete استفاده کردیم و مثل همان متد Edit هست با این تفاوت که کافی هستش مقدار IsDeleted موجودیت آزمون رو True کنیم تا در واقع حذف منطقی یا همون SoftDelete انجام داده باشیم ، قبلتر در کانتکست تعریف کردیم آزمون هایی که مقدار IsDeleted آنها True هستش رو در نظر نگیر و جوری رفتار میشه انگار در دیتا بیس وجود ندارند.modelBuilder.Entity<UserExam>().HasQueryFilter(c => c.IsDeleted == false);UserExamService.cspublic bool Delete(long ID, string? currentUser) { var exam = _examRepository.Get(Convert.ToInt32(ID)); var user = _userService.GetUserByMobile(currentUser); if (exam.User.Id == user.Id) { exam.IsDeleted = true; _examRepository.Update(exam); _examRepository.SaveChanges(); } else { throw new UnauthorizedAccessException(); } return true; }میمونه متد GetExam که آزمون های ثبت شده توسط کاربر رو برمیگردونه به این شکلUserExamService.cs public List<UserExamItem> GetExam(int pageNumber, int pageSize, string? currentUser) { var user = _userService.GetUserByMobile(currentUser); return _examRepository.GetQueryable().Where(u => u.User.Id == user.Id).Skip((pageNumber - 1) * pageSize).Take(pageSize).Select(o => new UserExamItem { Id = o.Id, Name = o.Name, StartDate = o.StartDate, EndDate = o.EndDate }).ToList(); } ورودی اون شماره صفحه و تعداد آیتم ها در هر صفحه و کاربری که میخوایم آزمون ها ی مربوط بهش برگرده هستش و خروجی لیستی از مدل UserExamItem که برای نمایش آزمون هاست.در این متد توسط GetQueryable در ریپازیتوریمون یک Where زدیم و آزمون ها رو به کاربر ورودیمون محدود کردیم ، سپس توسط Skip و Take منطق صفحه بندی رو پیاده کردیم و در آخر توسط Select به خروجی مورد نظرمون تبدیل کردیم.خب سرویسمون کامل شد ، میمونه استفاده از اون داخل کنترلر ExamController که قبلتر در سازنده تزریق کردیم استفاده کنیم.قبل از نوشتن اکشن هامون یک متد Private برای دریافت شماره موبایل کاربر جاری در کترلر ExamController مینویسیم تا هرجا شماره موبایل کاربر رو خواستیم ازش استفاده کنیم.getCurrentUserMobileprivate string? getCurrentUserMobile() { if (HttpContext.User.Claims.Any()) return HttpContext.User.Claims.First().Value; return null; }در این کد شماره موبایل کاربر در در اولین Claims ذخیره شده رو برمیگردونیم در هنگام دادن توکن اولین Claim که به توکن دادیم شماره موبایل بوده .بحث Claim ها از مقاله ی ما خارج هست اما به طور خلاصه یک ویژگی است که برای توصیف کردن یک موضوع استفاده میشود(از دید ما ،شناسایی هویت) و هنگام ساخت توکن در UserController اونها رو نسبت دادیم .UserControllervar claims = new[] { new Claim(JwtRegisteredClaimNames.NameId, mobile), new Claim(JwtRegisteredClaimNames.UniqueName, Guid.NewGuid().ToString()) };خب نوشتن اکشن هامون رو شروع میکنیم اولین اکشن Post هستش برای ثبت آزمون جدید به این شکلExamController.cs [HttpPost(&quotAddExam&quot)] [Authorize] public IActionResult Post([FromBody]AddUserExamDto addModel) { var result = _userexamService.MakeExam(addModel, getCurrentUserMobile()); return StatusCode(201, result.Id); }نوع اکشن Post هست و فیلتر [Authorize] رو قبل از اکشن میزاریم تا لاگین بودن کاربر چک بشه ، اگر کاربر لاگین نباشه قبل از اجرای اکشن خودکار وضعیت 401 Unauthorized برگشت داده میشه. داخل اکشن هم ورودیمون از بادی درخواست یک AddUserExamDto هستش که همون رو به سرویس UserExamService میفرستیم و در نهایت وضعیت 201 Created به معنی ثبت ایتم جدید به همراه ID آیتم ثبت شده به خروجی اکشن میفرستیم.میریم که اکشن Post در ExamController رو تست کنیم ، پروژه رو اجرا میکنیم و یو آی Swagger برامون بالا میاد ، میبینیم که یک آیتم Post برای کنترلر آزمون یا همون Exam اضافه شده .اکشن Post کنترلر Exam رو اجرا میکنیم ولی در خروجی میبینیم که به ما 401 Unauthorized برگردونند.دلیل برگشت وضعیت 401 Unauthorized بخاطر اینه که ما در اکشن از فیلتر [Authorize] استفاده کردیم ولی چون توکن معتبر نفرستادیم ، اعتبار سنجی انجام نشد و 401 Unauthorized برگشت داده شد ، برای حل این مورد باید ابتدا عملیات Login رو انجام بدیم و توکن بگیریم و در درخواستمون Token رو در Header ارسال کنیم بنابرین اکشن Login در کنترل User رو توسط خود Swagger اجرا میکنیم ، نام کاربری و پسورد رو ارسال میکنیم و توکن میگیریم.خب حالا میتونیم بریم برای ثبت آزمون جدید ، اگر api ها ما برای دسترسی نیاز به مجوز های مانند توکن JWT داشته باشند می توانیم این قابلیت را به Swagger اضافه کنیم که بتوانیم به همراه هر درخواست token را هم با header ارسال کنیم ، اما اینجا این کار را نمیکنیم و از Postman استفاده میکنیم تا یک ابزار دیگه رو هم ببینیم.در واقع Postman یک نرم افزار ( البته اکستنشن اون هم موجوده ) هستش که با اون میتویم RestApi خودمون رو تست کنیم.کار با Postman خیلی راحته و بنظرم نیاز به آموش خاصی نداره چون همه چی داخلش مشخصه ، Postman رو اجرا میکنیم و یک درخواست جدید میزنیم.آدرس درخواست رو که برای ثبت آزمون هستش رو وارد میکنیم https://localhost:7229/api/Exam/AddExamنوع درخواست هم Post هستش ، بادی درخواست رو که از نوع AddUserExamDto هستش رو به این شکل پر میکنیم&quotname&quot: &quotآزمون اول&quot, &quotstartDate&quot: &quot2022-01-09T16:55:01.785Z&quot, &quotendDate&quot: &quot2022-01-09T16:55:01.785Z&quotنام آزمون ، تاریخ شروع و تاریخ پایان که همون مدل AddUserExamDto هستش .فقط یک مورد میمونه اون هم ارسال توکن که قبلتر گرفتیم داخل درخواستمون هستش.به تب Authorization میریم نوع رو Bearer Token میزاریم و توکن که در لاگین دریافت کردیم رو داخل Token میزاریم.حالا دکمه ی Send رو میزنیم تا نتیجه رو ببینیم تنیجه خروجی وضعیت 201 Created هستش به همراه مقدار 2 که آی دی آزمون جدیدی که ذخیره شده هستش که خودمون در اکشن برگردوندیم . return StatusCode(201, result.Id);دیتا بیس رو چک میکنیم و میبینیم که آزمون با ای دی 2 ثبت شدهخب تا اینجای کار سرویس UserExamService رو کامل کردیم و از متد MakeExam در پروژه ی Api در کنترل ExamController استفاده کردیم و تست هم انجام دادیم و میتونیم آزمون های خودمون رو اضافه (Insert) کنیم.در مقاله ی بعدی اکشن های GetAll Update Delete تا عملیات CRUD ما کامل بشه البته سرویس اونها رو داخل UserExamService.cs نوشتیم و فقط باید در کنترلر ExamController اکشن های اونها رو کامل کنیم .البته هنوز از کارمون مونده ولی سورس کامل این مقالات رو میتونید از اینجا ببینید.امیدوارم از این مطلب خوشتون اومده باشه :)پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayerپیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPIپیاده سازی پروژه API در دات نت ، قسمت چهارم ، RestAPI Authentication Jwtپیاده سازی پروژه API در دات نت ، قسمت پنجم ، عملیات CRUD در RestAPIپیاده سازی پروژه API در دات نت ، قسمت ششم ( قسمت آخر ) ، عملیات CRUD در RestAPI پارت دوم