پیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPI

سلام :)خب در دو قسمت قبل تعریف ساختار و معماری پروژه ما انجام شد .پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayer در این قسمت میرسیم به قسمت EndPoint یا همون Presentation .در این پروژه همونطور که قبلتر هم گفتیم میخوایم یک وب سرویس Rest بسازیم .پروژه ای از نوع WebApi رو داخل پوشه ی Endpoint , Src میسازیم . موقع ساختن پروژه حتما تیک Enable OpenApi Support رو میزنیم تا برای ما Swagger اضافه بشه.البته بعدا هم میتونیم داخل پروژه پکیج های مورد نیاز برای Swagger رو اضافه کنیم ولی هنگام ساخت پروژه اگر این تیک رو بزنیم خودکار هنگام ساخت پروژه Swagger اضافه میشه.خب ممکنه سوال پیش بیاد که چیه این Swagger در حقیقت Swagger یک زبان توصیف رابط ( Interface Description Language ) هستش که توسط یک فایل JSON یا YAML طراحی و نوشته میشه یعنی به طور ساده یک واسط بین Server و Client هستش که ارتباطات بین این دو را برای شما راحت میکنه مثلا در یک تیم برنامه نویسی یکی از مشکلات کسایی که بخش بک اند رو توسعه میدن توضیح دادن APIها به فرانت اند کارهاست ? با swagger می‌تونید بیشتر زمان و تمرکزتون رو بزارید رو بیزینس لاجیکتون چون یک Documentation خیلی کامل برای شما به صورت کاملا اتوماتیک ایجاد می‌کنه ( در اکثر زبان های برنامه نویسی ) و شما نیازی نیست وقتتون رو صرف نوشتن داکیومنت کنید.این ابزار کمک زیادی برای تست عملکرد پروژه میکنه که جلو تر میبینیم.بعد از ساخت پروژه چنین فایل هایی رو داریم.فایل WeatherForecastController و WeatherForecast رو فایل نمونه پروژه اولیه هستند رو پاک میکنیم.برای شروع کار ابتدا یک کنترلر به نام UserController ایجاد میکنیم تا عملیات مورد نیاز مربوط به انتیتی User رو داخل همین کنترلر انجام بدیم.اولین کاری که میخوایم انجام بدیم ثبت نام کاربر هستش بنابرین یک DTO برای گرفتن اطلاعات کاربر به نام AddUserDto داخل پوشه ی Src 01.Core پروژه ه ی ApplicationServices و پوشه ی Models User میسازیم تا اطلاعات ورودی برای ثبت نام رو بگیریم .کلاس های DTO برای انتقال دیتا هستند.در مورد کلاس های DTO اینجا میتونید بیشتر بخونید.در نظر داشته باشید که قبلتر هم گفتیم که مدل های برناممون رو داخل پوشه Models در پروژه ی ApplicationServices قرار میدیم.public class AddUserDto { public string Mobile { get; set; } public string Name { get; set; } public string Password { get; set; } شاید این سوال پیش بیاد که چرا مدل هامون رو داخل همین پروژه ِ EndPint نمیزاریم؟فرض کنید ما یک EndPoint نداریم یک پروژه ی Api داریم و یک پروژه ی وب سایت یا حتی بیشتر ، باید برای هرکدوم مدل های جدا تعریف کنیم؟ یا به هم رفرنس بدیم و از مدل های هم دیگه استفاده کنیم!هیچکدوم ، روش درست در معماری Onion Architecture اینه که مدل های ما جزو بیزینس ما هستش و باید یکجا داخل پروژه ی ApplicationServices ما تعریف بشه و هر قسمتی که به این مدل ها نیاز داره از همین لایه ی ApplicationServices مدل ها رو بخونه.خب کلاس AddUserDto دارای سه پراپرتی هستش برای ثبت نام کاربر که از اسم هاشون هم مشخصه ، موبایل ، نام کاربر و پسورد برای لاگین .علاوه بر این نیاز داریم تا برای سرویس User یک متد تعریف کنیم که عملیات ثبت نام کاربر رو انجام بده ، پس اولین متد ما برای سرویس User میشه متد Register .سراغ سرویس User داخل Core ApplicationServices پو شه ی Services و User میریم ، ابتدا در کلاس اینترفیس IUserService متد Register که ورودی اون AddUserDto هستش و خروجیش خود انتیتی User رو تعریف میکنیم سپس پیاده سازیش رو داخل خود کلاس UserService .IUserService.cs public interface IUserService { User Register(AddUserDto addUserModel); }UserService.cspublic class UserService : IUserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } public User Register(AddUserDto addUserModel) { var hashpassword = Security.SecurePasswordHasher.Hash(addUserModel.Password); var user = new User() { Mobile = addUserModel.Mobile, Name = addUserModel.Name, Password = hashpassword }; _userRepository.Insert(user); _userRepository.SaveChanges(); return user; } }خب اینجا یکسری موارد هست که باید توضیح داده بشه.ابتدا اینکه ما داخل سرویس هامون ریپازیتوریی که قبلتر برای همون موجودیت رو ساختیم رو به کلاس تزریق میکنیم که در این مثال IUserRepository رو که برای ریپازیتوری User هستش رو تزریق کردیم .private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; }سپس متد Register رو کامل میکنیم public User Register(AddUserDto addUserModel) { var hashpassword = Security.SecurePasswordHasher.Hash(addUserModel.Password); var user = new User() { Mobile = addUserModel.Mobile, Name = addUserModel.Name, Password = hashpassword }; _userRepository.Insert(user); _userRepository.SaveChanges(); return user; }در این متد ایتدا پسوردی که ورودی هستش رو هش میکنیم ، همونطور که میدونید پسورد ها در دیتابیس باید بشکل هش شده ذخیره بشن ، برای این کار از یک کلاس دیگه استفاده کردیم که کارش عملیات مربوط به هش کردن و مقایسه ی پسورد هستش که وارد جزییاتش نمیشیم.در ادامه از دامین کلاس User یک شیئ ایجاد میکنیم و از با اطلاعات DTO وردودیمون پرش میکنیم ، سپس به متد Insert ریپازیتوریمون پاس میدیم و در در آخر متد SaveChanges رو کال کرده تا در دیتا بیس ذخیره بشه و همچنین خود User که ساختیم رو به عنوان خروجی برمیگردونیم.متد Register سرویس User کامل شد و میتونیم ازش استفاده کنیم ، برای همینکار داخل کنترلر UserController یک اکشن برای ثبت نام کاربر میسازیم به شکل زیر. [Route(&quotapi/User/&quot)] [ApiController] public class UserController : ControllerBase { private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } [HttpPost(&quotRegister&quot)] public IActionResult Register([FromBody] AddUserDto addUser) { var user = _userService.Register(addUser); if (user != null) return StatusCode(201); return StatusCode(500); } }در ابتدا سرویس IUserService رو داخل سازنده تزریق کردیم تا ازش استفاده کنیم .ما قرار هستش که کاربر رو ثبت کنیم پس اکشن ما از نوع HttpPost هستش و ورودی اون یک AddUserDto . در ادامه داخل اکشن متد Register رو کال میکنیم و وردودیمون رو بهش پاس میدیم ، در آخر چک میکنیم اگر خروجی متد نال نبود پس یوزر ساخته شده و Status Code 201 که منظور وضعیت Created یا همون ساختن جدید رو برمیگردونیم ، در غیر این صورت وضعیت با کد 500 که خطای سرور هستش رو برگشت میدیم. برای اجرای این قسمت یکسری کارها مونده ، مثل تنظیمات اتصال به دیتا بیس و همچنین تزریق وابستگی هامون برای سرویس و ریپازیتوریمون داخل پروژه ی Api در فایل Program.cs .سراغ فایل Program.cs میریم تا موارد گفته شده رو انجام بدیم.یکسری کد ها بصورت ییشفرض داخل کلاس Program.cs هستند که فعلا با اونها کاری نداریمبرای تنظیم کانکشن استرینگ به دیتا بیسمون اون رو تو فایل appsettings.json تعریف کرده و سپس کانتکسمون رو اضافه میکنیمappsettings.json &quotConnectionStrings&quot: { &quotAppCnn&quot: &quotServer=.; Initial Catalog=SampleApi; Integrated Security=True&quot }Program.csvar cnnString = builder.Configuration.GetConnectionString(&quotAppCnn&quot); builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(cnnString));سپس توسط خود IOC Container خود دات نت کور سرویس و ریپازیتوریمون رو هم اضافه میکنیمbuilder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserRepository, UserRepository>();بحث تزریق وابستگی خودش یک یا چند مقاله ی جداست و مبحث بزرگیه ولی به طور خلاصه ما سه حالت برای این کار توسط خود دات نت کور داریم که بشکل زیر هستش1- Transient به ازای هر شی یک نمونه ساخته می شود.2- Scoped به ازای هر درخواست سرور یک محدوده ساخته می شود. 3- Singleton یک نمونه فقط ساخته می شود و در تمام طول عمر نرم افزار استفاده می شود.ما در این مثال سرویس ها و ریپازیتوری هامون رو توسط AddScoped انجام میدیم.در نهایت فایل Program.cs به این شکل میشهProgram.csvar builder = WebApplication.CreateBuilder(args); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var cnnString = builder.Configuration.GetConnectionString(&quotAppCnn&quot); builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(cnnString)); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();خب قبل از اینکه پروژه رو اجرا کنیم یکبار ارتباط با دیتا بیسمون رو تست میکنیم و اون رو میسازیم.برای این کار ایتدا دستور add-migration init رو داخل Package manager Console میزنیم.دقت کنید که اجرای این دستور باید در پروژه ی DataLayer ما که همان پروژه ی DataLayer.SqlServer هستش باید باشه ، مانند تصویر زیربعد از انجام یک پوشه به نام Migrations به پروژه ی DataLayer.SqlServer ما اضافه میشه که جزییات عملیات هستش.توضیح بیشتر در مورد Migration خارج از مقاله ی ما هستش ولی بطور خلاصه در پوشه Migration کلاس هایی که ساخته میشه شامل جرئیات عملیات Migration هستش .نوبت به دستور update-database میرسه که برای ما دیتا بیس و جداول رو از روی مدل هامون میسازه ، دستور رو داخل Package manager Console اجرا میکنیم و میبینیم که دیتا بیس ما بر اساس دامین مدل هامون ساخته شدهخب میریم که پروژه رو اجرا کنیم و از ثبت نامی که نوشتیم تست بگیریم.پروژه که اجرا میشه همونطور که توی فایل Program.cs هم تعریف شده ، در محیط Development هستیم و یو آی Swagger برای ما بالا میاد و میتونیم اکشن هایی که داریم رو ببینیم. در اینجا فقط یک اکشن Register که از نوع Post هستش رو میبینیم.برای تست میتونیم دکمه ی Try it out رو بزنیم و مدل ورودیمون رو بفرستیم تا نتیجه رو ببینیم.میبینیم که در خروجی کد 201 برگشت داده میشه و عملیات با موفقیت انجام شده.در دیتا بیس هم رکورد ما ایجاد شده.در این قسمت یک مورد مونده اون هم اعتبار سنجی ورودی هامون هستش ، چون پروژه ما از نوع API هستش از FluentValidation برای اعتبار سنجی ورودیمون استفاده میکنیم.لازمه که ابتدا پکیج هایFluentValidationFluentValidation.AspNetCoreFluentValidation.DependencyInjectionExtensionsرو نصب کنیم سپس یک مدل داخل Core ApplicationServices Models User به نام AddUserDtoValidation میسازیم ، به این شکلAddUserDtoValidation.cspublic class AddUserDtoValidation : AbstractValidator<AddUserDto> { public AddUserDtoValidation() { RuleFor(c => c.Mobile).NotEmpty().WithMessage(&quotبرای موبایل مقدار لازم است&quot) .Length(11).WithMessage(&quotموبایل 11 رقم می‌باشد&quot); RuleFor(c => c.Password).NotEmpty().WithMessage(&quotبرای پسورد مقدار لازم است&quot); } }همونطور که میبینید برای ساختن مدل اعتبار سنجی یک کلاس ساخته از AbstractValidator که حالت جنریک داره و AddUserDto رو بهش دادیم ، ارث بری میکنیم . سپس یک متد سازنده میسازیم و Rule ها مون رو تعریف میکنیم .در این مثال خالی نبودن موبایل و پسورد و مقدار طول موبایل رو اضافه کردیم.برای اینکه بشکل خودکار در API ورودی هامون اعتبار سنجی بشن باید یکسری تغییرات داخل فایل Program.cs مریوط به پروژه ی API مون بدیم ، به این شکل که کد های زیر رو داخل فایل Program.cs اضافه میکنیم.Program.csbuilder.Services.AddControllers().AddFluentValidation(); builder.Services.AddTransient<IValidator<AddUserDto>, AddUserDtoValidation>();در خط اول Fluent Validation رو در کنترلر ها فعال کردیم و در خط دوم کلاس ولیدیشنی که تعریف کردیم به خود کلاس AddUserDto به صورت Transient نسبت دادیم.با اینکار بشکل خودکار قبل از اجرای اکشن Register مدل AddUserDto توسط ولیدیشنی که بهش نسبت دادیم چک میشه . به عنوان مثال شماره موبایل رو به جای 11 رقم 12 رقم میفرستیم و خروجی به شکل زیر میشه.میبینیم که اعتبار سنجی که تعریف کردیم درست کار میکنه :)در این قسمت پروژه ی لایه ی Endpiont یا همون Peresention رو از نوع RestApi ساختیم ، سپس در سرویس User که در لایه ی AplicationService ما هستش عملیات Register رو توسط EF و SqlServer در لایه ی DataLayer انجام دادیم ، در حین کار با ابزار Swagger و EfCore و FluentValidation نیز استفاده کردیم .در ادامه به سرویس User عملیات لاگین رو اضافه میکنیم و توسط JWT اعتبار سنجی مون رو با دادن توکن به کاربر ادامه میدیم , و با همین روند سرویس هامون رو کاملتر میکنیم.البته هنوز از کارمون مونده ولی سورس کامل این مقالات رو میتونید از اینجا ببینید.امیدوارم از این مطلب خوشتون اومده باشه :)پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayerپیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPIپیاده سازی پروژه API در دات نت ، قسمت چهارم ، RestAPI Authentication Jwtپیاده سازی پروژه API در دات نت ، قسمت پنجم ، عملیات CRUD در RestAPIپیاده سازی پروژه API در دات نت ، قسمت ششم ( قسمت آخر ) ، عملیات CRUD در RestAPI پارت دوم