اینجا وقتی که تعیین کردیم که route روی controller باشه ، اینطوری میشه که هر وقت درخواستی با اسم اون کنترلر بیاد اون فعال میشه و یه پاسخی رو برمیگردونه اینجا اسم کنترلرمون weatherForcast هستش
حالا اگر بزنیم weatherForcast/GetWeatherForcast این متد فعال میشه و یه دیتایی رو برای ما برمیگردونه
#lunch_Setting
نکته : lunch setting همونطوری که از اسمش پیداست یعنی چیزهایی که نیازه برای این که پروژه رو اجرا کنیم و راه بندازیم :
ولی appSetting چیزهایی هستش که ما برای develpment میخوایم استفاده کنیم
#App_Setting
نکته اگر بخوایم سرویسی رو تعریف کنیم میایم اون ها رو بین builder و builder.build تعریفشون میکنیم :
#service
#Program
#bulder_Build
و نکته ی بعدی اینه که اگر بخوایم middleware رو تعریف کنیم میایم بین app و app.run تعریفش میکنیم : #middleware
خوب اون builder که داره سرویس ها و چیزهایی که برنامه میخواد ازشون استفاده کنه قبل از این که app.build بشه رو فراهم و آماده میکنه برای همین بهش میگن IOC Controller یا همون inverstion of control container
خوب وقتی که به app میشه دیگه ما میایم middleware ها رو اضافه میکنیم که این به request pipline تاثیر میزاره
خوب اینجا به طور مثال داره میگه اگر روی ماشین خودمون و در حالت development داریم کد رو اجرا میکنیم بیاید و از این ها استفاده کنه و اون ها رو اجرا کنه swagger و swaggerUI
این برای اینه که همیشه از https استفاده کنیم
خوب model ها برای این هستند که دیتا چه شکلی باید باشن
تعریف cors این برای این هستش که client های مختلفی که روی سرور من نیستند هم بتونند به این api دسترسی داشته باشند
https://www.bytehide.com/blog/cors-aspnet-core
In this guide, we’ll unravel the core principles of Cross-Origin Resource Sharing (CORS) in ASP.NET Core. We will cover its role in web applications, provide practical examples and highlight some common mistakes to avoid.
Let’s dive in!
Introduction to CORS
Ever wondered about the invisible rules that keep your website interactions safe and secure? That’s where CORS, or Cross-Origin Resource Sharing, comes in. This primer will help us understand its importance in our web applications.
Understanding CORS and Its Importance
CORS or Cross-Origin Resource Sharing is the guardian angel that allows or restricts external domains to access resources from your domain. Imagine you’re hosting a party (your website), and CORS is the bouncer deciding who gets in (requests) based on the guest list (rules you’ve set). Oh, and that party? It’s one thrilling and secure online experience!
How Does CORS Work
Once you send a request from a client to a server in the dance of web communication, the server responds with access control headers. These headers decide who gets to dance along to the beat (the resources). But hey, let’s not only talk in riddles, let’s dive deeper into CORS’s working mechanism in ASP.NET Core context in the next sections.
Step-by-step Guide to Enabling CORS in Web API NET Core
Understanding NET Core Allow CORS Methods
https://www.bytehide.com/blog/cors-aspnet-core
بعدا کامل مطالعه شود
خوب حالا ما این رو توی service ها اضافه کردیم حالا میرسیم به این که اضافه اش کنیم به pipeline تا ازش استفاده کنیم
اینطوری میایم اسم اون policy که اون بالا تعریف کردیم رو میاریم و اینجا استفاده میکنیم
توضیحات بیشتر https://andrewlock.net/making-authenticated-cross-origin-requests-with-aspnetcore-identity/
بعدا مطالعه شود
خوب حالا میرسیم به serilog
خوب logging توی پروژه خیلی مهمه ، مخصوصا برای api ها چون توی api ها ما هیچ اینترفیسی نداریم که داره دقیقا چه اتفاقی میوفته برای همین یه موقع هایی که مثلا client میگه که من فلان کار رو کردم و فلان ارور خوردم برای ما خیلی مشکل میشه که بخوایم track کنیم و مشکل رو پیدا کنیم
خوب برای همین میخوایم یه چیزی رو ستاپ کنیم که log ها رو generate کنه و بهمون نشون بده که چه اتفاقاتی داره میوفته در هر روز و هدف بعدی اینه که بیایم همه این log رو ذخیره کنیم
میایم این رو نصب میکنیم :
خوب همچنین یه مفهمومی داریم به اسم sinks که میاد اون فایل خروجی رو بیاد کجا و به چه صورت ذخیره و Save کنه
که برای این پروژه میایم و seq رو نصب میکنیم و ازش استفاده میکنیم
این رو نصب میکنیم :
بعدش میایم serilog expression رو نصب میکنیم برای cofig کردن در پروژه
و در csProj نشون میده که چیا نصب کردیم :
خوب این نکته هستش که وقتی که اسم این پکیج ها اینجا باشه خوده vsisual studio میره اون ها رو دانلود میکنه و میاره برای اجرای پروژه
خوب اینجا نوشته که قبلا فقط مشید برای web/http استفاده بشه ولی تیم .net اومده کاری کرده که از worker service ها بتونیم استفاده کنیم برای message queus و grpc , windows serivce
این کار با هدف این که web app با قابلیت configureation و loggig و di رو با برنامه هایی با type های دیگه دراه هم بتونیم انجام بدیم
خوب اینجا توی app setting میبینیم که فرآیند logging به صورت دیفالت تعریف شده و ما برای این که بخوایم از serilog استفاده کنیم میایم این رو پاک میکنیم
خوب ما اومدیم اول روی سیستم warning ماکروسافت اومدیم override کردیم اون تنظیماتی رو که میخواستیم و بعد اومدیم تعیین کردیم که کجا save بشه اطلاعاتمون و بعد هم اومدیم گفتیم که میخوایم از seq برای ذخیره سازی اطلاعات استفاده کنیم
خوب برای استفاده از Seq میریم و دانلودش میکنیم از سایتش فقط این مورد هستش که سایتش از اونور مارو تحریم کرده
خوب ما به طور مثال شاید بخوایم یه مدل customiziation رو انجام بدیم برای نمایش لاگ هامون برای همین میایم از یه middlewate استفاده میکنیم
قبل از useHttpsRedirection میزاریمش
حالا میریم سراغ data base
اینجا میتونیم ببینیم که با اون پکیجی که نصب کردیم چه چیزهای دیگه ای باهاش نصب شدن
خوب حالا اینجا میتونیم تعیین کنیم و بنویسیم که اسم سرورمون چیه
اگر سرور روی سیستم خودمونه که local میشه اگر روی سیستم دیگه است که ip اش رو میدیم ، اگر توی یه instace دیگه روی سیستمون هست ، اسم اون رو مینویسیم
خوب اینجا trusted connection رو true گذاشتیم که بگیم connection که داریم مورد اطمینان هستش و مورد بعدی که multipleActiveResultSets هم این قابلیت رو بهمون میده که به صورت همزمان از چند جا به این app وصل بشیم
خوب حالا ما این رو اینجا تعیین کردیم حالا باید بریم توی program و به برنامه بگیم که از این استفاده کنه برای ef Core
خوب اینجا یه var رو تعریف میکنیم و بعد میریم اون connection string رو میاریم و میریزیم داخل متغییر ConnectionString برای این که وقتی که خواستیم از دیتابیس استفاده کنیم این رو بهش بدیم
بعد اومدیم سرویس AddDbContext رو اضافه کردیم و با استفاده از option بهش گفتیم که داریم از sql server استفاده میکنیم و بعد هم بهش connection string رو دادیم
خوب اینجا ما اومدیم توی کلاسی که ساختیم توی constructor اش اومدیم از DbContextOption در آرگمان ورودیش استفاده کردیم که اون options که اونجا هستش در حقیقت از همون options که در program نوشتیم داره میاد
بعد از این که options رو گرفتیم میاریم پاسش میدیم به base چون context به این option برای initilize شدن به این روش نیاز داره
خوب بعدش یه کلاس میسازیم و فیلد id رو براش تعیین میکنیم ، وقتی که ef core این پترن رو میبینه متوجه میشه که id فیلدیه PK هست و auto inctimental هستش
هم به شکل بالا متوجه میشه و هم به این شکل
به هر دو حالت میتونیم بنویسیم ولی مزیت پایینی اینه که دقیقا اسم همون فیلدی رو که میخوایم رو بهمون به شکل string بهمون میده و هر وقت که اسم اون فیلد رو تغییر بدیم بهمون ارور میده ولی در مورد بالایی اینطور نیست و هیچ اروری نمیگیرم و بعدا در برنامه دچار مشکل میشیم
خوب اینجا ما تعیین کردیم که CountryId به عنوان FK و اینجا ef با توجه به convention که هست متوجه میشه که این FK متعلقه به Country چون CountryId نوشتیم Country + Id
و اینجا ما یک ارتباط یک به چند و چند به یک داریم
خوب حالا بعد از اینها
به ارور این که این فیلدها نمیتونن null باشن برمیخوریم و برای رفع این ارور میایم
میایم و nullable رو disable میکنیم
بعد میایم و توی کلاس که رابط با دیتابیسمون رو ساختیم کلاس هایی که میخوایم ازشون استفاده کنیم رو معرفی میکنیم ، اینطوری:
خوب اینجا به این ارور برخوردیم وقتی که Add-Migration InitialMigration رو زدیم:
توی خط آخر نکته ای که گفته مشکل ما رو حل میکنه ، این که باید Options رو به base بدیم
خوب اینجا در فایل migration این ها رو میبینیم
به طور مثال میتونیم cascade رو تغییر بدیم ، معنی cascade در اینجا اینه که اگر Hotels رو پاک کنیم دیتاهای County هم پاک میشه
خوب اینجا یه بخشی داریم به اسم up که یعنی اون کاری که میخوایم انجام بدیم و اعمال کنیم و قسمت down میشه قسمتی که بخوایم undo کنیم تغییراتی که اعمال کردیم
یه راه دیگه هم برای دیدن دیتابیس استفاده از
توی قسمت server مینویسیم localdb/mssqllocaldb
حالا میخوایم دیتابیسمون رو seed کنیم :
خوب اول میایم country رو به دیتابیس seed میکنیم چون هر کدوم از هتل ها به این کشور ها وابسته هستند و هر هتل باید این رو داشته باشه
به همین ترتیب میایم هتل ها رو هم اضافه میکنیم و بعد هم add-migration و بعد هم آپدیت رو میزنیم و این دیتا ها رو وارد دیتابیس میکنیم
خوب در مرحله بعدی میایم برای درست کردن controller و میایم از scaffolding این کار رو انجام میدیم
و بعد اینطوری بهمون یه صفحه نشون میده که میایم با این اطلاعات پرش میکنیم و جالب اینجاست که خودش بر اساس اون های میاد یه اسم پیشنهادی رو میزاره برای کنترلر
اگر اینجا به ارور خوردیم دلیلش میتونه این باشه که پکیج هایی که داریم استفاده میکنیم از یک ورژن یکسان نباشند و این باعث ارور میشه
اینجا هم اومدیم db contrxt رو به عنوان یه service اومدیم Register کردیم و همین کار به ما این قابلیت رو میده که بتونیم injection رو انجام بدیم توی هر جای برنامه که بخوایم میتونیم این کار رو انجام بدیم هر جایی که بخوایم ازش استفاده کنیم
خوب اینجا اومدیم و توی constructor اومدیم کلاس مرتبط با database رو قرار دادیم و بهش اسم context رو دادیم و ریختیمیش توی یه پارامتر private
با این کار دیگه نیاز نیست که بیایم و هر سری که خواستیم از دیتابیس استفاده کنیم بیایم و یه instance جدید بسازیم و همین که از injection استفاده میکنیم باعث میشه که کمک میکنه به life time عه context چون اینجوری توسط کل برنامه کنترل میشه و اینطوری میشه که هر وقت که Request بیاد و اون کاری که قراره با دیتابیس انجام بشه ، انجام میشه، برنامه میاد database instance یا database function رو که در background داره اجرا میشه رو kill میکنه و اینطوری خیلی به بهبود memory و سرعت برنامه کمک میکنه
یه ارور خیلی عجیبی که خوردم این بود که من تمام این ها رو به ورژن 6.0.2 تغییر دادم یعنی پاک کردم و این ورژن رو ریختم ولی بعد از این که زدم یه کنترلر باسزه با همین روش خودش این ها رو تغییر داد
,وقتی که آپدیت هم کردم باز هم به ارور خوردم
برگردوندم به حالت اول ولی همچنان ارور میده برای درست کردن کنترلر به این روش
خوب entry برای فراهم اوردن دسترسی به اطلاعات و انجام عملیات روی اون هاست
خوب state برای ست کردن state همون یک entity که در entry هستش و state بقیه entity ها رو تغییر نمیده ، ولی در صورتی که state رو به delete , detach تغییر بدیم به صورت cascade روی بقیه entity ها تغییر ایجاد میکنه
اینجا ما وقتی که bracket داریم یعنی arry داریم و curly brace ها هم object هستش
در جواب اینطوری میشه :
خوب از خط اول اینطوریه که توی request url وقتی که نوشته api/countries به خاطر اینه که خودمون این رو براش تعیین کردیم
خوب توی قسمت response header
و توی قسمت Response body
این به خاطر نوشتن
چون اومده id که 4 هستش رو با route بهمون داده و خوده آجکت country که در response body اومده و ما این رو اومدیم آخر created at action نوشتیم که country رو برگردونه
خوب مشکلی که داره اول اینه که ما نباید خودمون id رو تعیین کنیم و ارسال کنیم
نکته: اگر 0 بزاریم خوده ef میفهمه که باید بهش مقدار بده و این کار رو برامون انجام میده
خوب حالا توی قسمت get اگر بخوایم خیلی مشخص کنیم که جوابی که قراره ارسال بشه از چه نوعه میایم اینطوری تعیینش میکنیم:
اینطوری بهمون مشخصا 200 رو برمیگردونه
نکته :
اگر ما اون قسمت مسیر id رو پاک کنیم اتفاقی که میوفته اینه که swagger که کلا میپوکه و اتفاق بعدی که میوقته اینه که توی postman هم request بفرستیم 404 به ما میده با ارور ambigous match exception که این یعنی این که وقتی که یه درخواست میاد این درخواست میتونه به چند تا End point بره و اون ها رو فعال کنه ، که همین قضیه باعث ارور میشه
توی ارور postman هم نشون میده که این request به دو تا end point میرسه
میرسیم به put : کاری که put میکنه اینه که کل دیتا رو هر چیزی که ما نوشته باشیم رو همش رو برمیداره و میبره میزاره جای اون دیتایی که الان هستش، برای همین هم هست که آرگومان های ورودیش فقط یدونه id میگیره و کل اون object رو
داشتن id دلیلش اینه که بره و چک کنه که اشتباهی به جای یه دیتای دیگه آپدیت رو انجام نده
یعنی میاد id رو با id که داخل country هستش چک میکنه
میتونیم برای ارور 404 که میفرسته یه پیام هم بنویسیم
نکته : هر entity یک State داره که اینطوری تعیینش میکنیم :
با تغییر state داریم یه Ef میگیم که این دیتایی که داریم ارسال میکنیم دیتایی نیست که بخواد جایی اضافه بشه یا حذف شه یا هر چیزه دیگه ای ، این دیتا یک دیتای آپدیت هستش و دیتای جدید که بخواد به کل دیتابیس اضافه بشه نیست و عملیات وقتی تکمیل میشه که saveChangesAsync اعمال بشه .
خوب دلیل این که اومدیم save changes رو توی try catch گذاشتیم اینه که اگر زمانی به علیت این که چند تا یوزر بخوان به صورت همزمان یه دیتایی رو تغییر بدن و اگر این اتفاق بیوفته exception اتفاق میوفته و اگر این اتفاق بیوفته میایم اول چک میکنیم ببنیم اصلا اون دیتا وجود داره یا نه ، ممکنه قبل از تغییر ما نفر دیگه اون رو پاک کرده باشه
خوب اگر ما نتونیم catch کنیم میایم و no content رو برمیگردونیم که یعنی عملیات انجام شده ولی دیتایی نیست که برگردونم
خوب اینجا id که داریم ارسال میکنیم با id اون کشور یکسان نیست
بعد id درست رو ارسال کردیم :
نکته اینه که no content از نوع 200 هستش
خوب اینجا اگر مسیر رو تغییر بدیم به این ارور میخوریم :
این یعنی این که اصلا همچین متدی نیست که بخوام برم و ازش استفاده کنم
خوب حالا میرسیم به delete
این جا هم میایم اول میایم id رو میگیریم بعد میریم میاریمش میزیمش توی country و بعد میایم state اش رو تغییر میدیم و بعد میایم عملیات رو انجام میدیم .
نکته : اینجا با استفاده از any میایم و تمام دیتابیس رو چک میکنیم و میبینیم که آیا همچین id وجود داره یا خیر
خوب یه مشکلی که داریم اینه که over posting داریم یعنی از سمت client دیتاهایی که میره سمت سرور زیاده و به طور مثال clinet نباید خودش id رو بتونه بفرسته و این یه مشکله و برای حل کردنش میایم از DTO ها استفاده میکنیم
و وقتی که DTO نداشته باشیم swagger چیزی که به ما نشون میده اینه که همه این فیلد ها رو میتونیم مقدار براشون تعیین کنیم و بفرستیم :
خوب DTO ها میشن یه abstraction از اون دیتایی که میخوایم بفرستیم
خوب توی پروژه یه فولدر درست میکنیم به اسم Models که توش میایم و تعیین میکنیم که دیتاهای توی برنامه باید چجوری باشن
خوب ما میخوایم از سمت یوزر فقط name , ShortName رو بفرسته
خوب حالا توی postman وقتی نگاه میکنیم میبینیم که اینطوری شده :
ولی همچنان در response این رو برمیگردونه :
نکته : ما میایم و دوباره با id میفرستیم Request رو ، اتفاقی که میوفته اینه که سرور اصلا دنبال id نیست و اون رو اصلا نمیبینه و براش فرقی نداره که چه عددی ارسال شده ، چیزی که براش تعریف شده name , short name هستش
خوب حالا اگر نیاز به چک کردن داشتیم اینطوری مینویسیم :
خوب وقتی که با postman بیایم Req بزنیم
اینطوری جوابش میشه
بعدش میایم از profile ارث بری میکنیک که برای خوده auto mapper هستش و بعدش میایم و توی constructor ازش استفاده میکنیم برای این که بیایم بین data type ها maps درست کنیم
حالا این یعنی چی؟ یعنی این که auto mapper به ما این قابلیت رو میده که بیایم CreateCountryDto رو به Country وصل کنیم که دیگه نیاز نباشه اینطوری به صورت کد بنویسیمش:
خوب
اینجا ما اومدیم Country رو به CreateCountryDto مپ کردیم و این کار رو میتونیم به در یک جهت یا برعکس یا برای هر دو اعمال کنیم که با نوشتن reverseMap برای هر دوتاشون اعمال میشه یعنی Country به CreateCountryDto و بلعکس انجام میشه
خوب حالا ما باید بیایم به برنامه مون هم بگیم که ما داریم از Auto mapper استفاده میکنیم و برای این کار باید auto mapper رو به صورت یک injectable resourse برای controller هامون درست کنیم که هر جا که خواستیم هم بتونیم ازش استفاده کنیم
خوب حالا برای این قضیه میایم اینطوری تعریفش میکنیم :
بهش اینجا داریم میگیم که Configuration که میخواد استفاده بشه توی برنامه از طریق auto mapper توی این کلاس تعریف کردیم
خوب حالا مزیت استفاده از auto mapper اینه که ما ممکنه یه جایی 20 تا فیلد داشته باشیم و نوشتن به صورت دستی این همه فیلد زمان میبره ولی auto mapper این کار رو برای ما خیلی راحت انجام میده
حالا برای استفاده کردن ازش توی controller مون باید بیایم اول inject اش کنیم
اینجا به جای قسمت بالا از auto mapper در خط پایینیش استفاده میکنیم
اینجا داره میگه که اول ما auto mapper رو کانفیگش رو اول درست میکنیم که این کانفیگه میاد وصل شدن پراپرتی ها رو بین دو تا کلاس تنظیم میکنه یا اگر نیازه که تغییراتی توی این وصل شدن رو ها اونجا مینویسیم و تنظیم میکنیم و بعد با دستور map عملیات رو انجام میدیم
خوب حالا میخوایم get رو refactor کنیم چون که موقعی که ما دستور getCountries رو میدیم
تمام اینها رو به کاربر میده که این درست نیست و برای حل این مثله میایم یه DTO دیگه براش تعریف میکنیم
اینجا ما چون نیاز داریم برای عملیات دیگه مثلا پاک کردن و ادیت کردن، که id رو داشته باشیم برای همین id رو گذاشتیم بمونه و لیست هتل ها رو از توش حذف کردیم
خوب اینجا میایم اول لیست و کالکشن تمامی کشور ها رو میگیریم در مرحله ی بعدی نمیتونیم یه کالکشن رو به یه آبجکت DTO بیایم و map کنیم و باید اینجا از IList یا IEnumerable استفاده کنیم
خوب حالا میخوایم قسمت GetCountry که با id هستش رو ریفکتور کنیم و برای این کار میتونیم از DTO های موحود استفاده کنیم یا بیایم و یکی دیگه تعریف کنیم
نکته : auto mapper میاد با استفاده از naming convetion ها میاد و آبجکت ها رو بهم map میکنه ، به طور مثال تمام پراپرتی های GetCountryDetailsDTO به Country وصل میشه ، از جمله Hotels
فقط قانون مهمی که داریم اینه که ما نباید هیچ وقت نباید هیچ DTO رو داشته باشیم که به صورت مستقیم به دیتا یا data model مون وصل شده باشه و DTO ها فقط با DTO ارتباط پیدا میکنن و تنها جایی که DTO ها با دیتا Cross path دارن جاییه که عملیات mapping انجام میشه
نکته : DTO ها حتی نباید یک فیلد داشته باشند که مستقیم به دیتا بخواد وصل بشه
مثل اینجا
و برای همین هم هست که ما اومدیم توی GetCountryDetailsDTO هتل ها رو با یه DTO دیگه که مرتبط با هتل ها هستش وصل کردیم نه خوده Hotel
خوب حالا میخوایم وقتی که با id درخواست رو فرستادیم اسم هتل هایی که توی این کشور هستند رو برامون بیاره :
ولی بهمون null میده و برای حل این مشکل باید یه refactoring انجام بدیم :
خوب ما وقتی که نوشتیم include اومدیم گفتیم که کشور رو میخوایم + هتل هایی که داخلش هستند این کار در دیتابیس معادل inner join هستش که ما در اینجا اومدیم جدول country رو با جدول hotel با هم inner join زدیم تا اطلاعات هتل هایی که توی اون کشور هستش رو بدست بیاریم
خوب در مرحله ی بعد که first or default هستش هم اومدیم گفتیم که اون دیتایی که با اون id یکسان هستش رو بردار برای ما بیار اگر هم هیچی نبود که هیچی
نکته : اینجا ما توی dto که برای hotel تعریف کردیم خوده آبجکت Country رو دیگه نیاوردیم چون اونطوری Recursive میشد
خوب حالا به این قضیه میرسیم که میخوایم برای put هم یه dto تعریف کنیم ، خوب نکته ای که اینجا هست اینه که خیلی از این DTO ها در یک سری از فیلد ها یکسان هستند ، پس میایم و کلاس از نوع abstract میسازیم
و بعد اینطوری ازش استفاده میکنیم :
نکته اگر به طور مثال ما توی کلاس base اومدیم و برای name در نظر گرفتیم که required باشه بهتره که در کلاس هایی که ازش ارث بری میکنن نیایم تغییرش بدیم چون میره روی کلاس base و override میشه
یا به بیان دیگه vallidation rules ها باید universal باشن در کل پروژه نکته ی بعدی اینه که حتی وقتی که ما Reqired در نظر گرفتیم این قضیه توی Get کردن دیتا تاثیری نداره
نکته با control + . یه سری آپشن به ما میده
خوب ما تمام این کار ها رو برای این انجام میدیم که seperation of concerns رو انجام بدیم که یکی از اصول Solid هستش
خوب اینجا اومدیم و اول با استفاده از Id دیتای country رو اوردیم و ریختیم توی country و بعد با استفاده از mapper__ اومدیم و بعد update Country Dto رو دیتاش رو گرفتیم و بعد باهاش دیتای توی country رو آپدیت کردیم
وقتی که دیتا رو از دیتابیس داریم میاریم اون رو track کردیم و وقتی که track اش کردیم و بعد تغییر روش ایجاد کردیم باید state اش رو تغییر بدیم و بعد هم save اش کنیم
خوب اون خط ای که mapper رو نوشتیم داره دیتایی که داخل UpdareCountryDTo هستش رو میزه توی country و بعد به صورت اتوماتیک به ef میگه که این دیتا تغییر داده شده و دقیقا مثل همون تغییر state این کار رو به صورت اتوماتیک میکنه
خوب حالا میرسیم به repository pattern هدف اینه که یک level abstraction بین کنترلر و intelligence ایجاد کنیم که اینجا intelligence به معنی همون business logic هست که توی کنترلمون نوشتیم و خوب ما نمیخوایم توی کنترلرمون بیایم خیلی به decision making بپردازیم ، داخل کنترلر باید جایی باشه که فقط request رو دریفات کنه و جوابش رو بده
به کنترلر باید به چشم manager نگاه کنیم و این به معنی اینه که لازم نیست همه ریز جزییات رو بدونه
خوب میایم و دو تا فولدر درست میکنیم به اسم های Repository و Contracts ، که Contracts میشه abstraction class , و Repositiory میشه کلاس پیاده سازی
خوب اینجا یه اینترفیس میسازین و این اینترفیس میاد به شکل یه قانون و قرارداد عمل میکنه و force میکنه چه چیزهایی باید انجام بشه
خوب اینجا T به معنی data object مون هستش که ما با نوشتن where : class داریم تعیین میکنیم که از جنس کلاس هستش و در اینجا یعنی Hotel , country میشه
و IGeneric Repository مسئول ارتباط با دیتا بیس هستش
دلیل این که generic در نظرش گرفتیم اینه که نخوایم یه سری از query ها رو تکرار کنیم چون وقتی که داریم کار میکنیم یه سری اتفاقات تکراری ممکنه که پیش بیاد - اصل DRY
خوب دلیل این که این رو به شکل generic نوشتیم اینه که میخوایم تفاوتی نداشته باشه که که این کارها برای country میخواد انجام بشه یا برای Hotel
خوب حالا یه interface نوشتیم که از IGenericRepository ارث بری کرده و اونجا به جای T دیگه اینجا تعریف کردیم که این اینترفیس مرتبط با country هستش و باید این کار رو انجام بدیم
خوب ما توی IGenericRepository تمامی قانون های که کلی هستند رو مینویسیم و بعد میایم هر کدوم رو با جزییاتشون توی قراداد های متفاوت مینویسیم
خوب حالا بعد از قرارداد میریم سراغ اصل کار که تعریف کلاس اصلی GenericRepository هستش
و بعد از این که ساختیمش اومدیم از Interface که براش درست کردم ارث بری کردیم
نکته اینه که اون فلش های آبی کوچولو که اون بقلن مارو میبرن به جایی که ازشون استفاده شده یا ازشون ارث بری شده
خوب حالا میایم شروع میکنیم که repository رو درست کنیم
اینجا اومدیم از async استفاده کردیم و بعد از AddAsync استفاده کردیم که این متد میاد entiy که بهش در آرگمان ورودی میدیم رو میگیره و Ef core اینجا متوجه میشه که اون entity که از جنس T بوده دقیقا چیه یعنی الان داره Hotel وارد اون entiy شده یا country و این رو با استفاده از AddAsync و قابلیت های Ef core بدست آوردیم
ولی در این حالت ما باید مشخصا تعیین کنیم که چیه
یا اینجا که دقیقا نوشیتم و بعد add رو زدیم
حالا اگر بخوایم مثل همون حالت generic بخوایم ازش استفاده کنیم (تست شود)
خوب نکته ای که هست اینه که تا زمانی که to list async اجرا نشه اتفاقی نمیوفته و وقتی که اجرا میشه میره و اون DBSet رو میاره و query رو اجرا میکنه و تمام دیتاها رو میاره
نکته عملیات remove به صورت لحظه ای انحام میشه یعنی نمیتونیم از async استفاده کنیم ولی میتونیم save changes رو به صورت async انجام بدیم
خوب اینجا اومدیم یه CountriesRepository درست کردیم که کار های مرتبط با country و دیتابیس رو انجام بده و برای این کار میایم ازGeneric Repository و ICountryRepository ارث بری میکنیم
خوب ما یه قرار داد برای یا همون اینترفیس برای country نوشته بودیم که اینجا میایم ازش ارث بری میکنیم که خوده اون اینترفیس هم از اینترفیس IGeneric Repository که مشخصا از country داره استفاده میکنه ارث بری میکنه :
خوب با ارث بری کردن ICountries Repository و GenericRepository میتونیم هر کاری رو بین Countries Repository و Generic Repository رو انجام بدیم
خوب حالا اگر بخوایم یه کاری رو با counry انجام بدیم میریم توی اینترفیس ICountry repository تعریفش میکنیم و بعد میتونیم بیایم توی Country repository پیاده سازیش میکنیم
کاری که کردیم این بوده بوده که اینترفیس Country از generic ارٍث بری کرده و خوده Country هم از Generic Repository Country ارث بری کرده و اینطوری ارتباط بین این دو تا برقرار شده
خوب حالا اینجا یه نکته ی خیلی خیلی مهم داره :
خوب حلاصه ی متن بالا اینه که ما فقط از یه جا وصل به صورت مستقیم وصل میشیم به دیتابیس و بقیه کلاس هایی که از همون اصلیه مشتق میشن باید از Base ارٍث بری کنند. چرا ؟ توی این متن توضیح میده :
نکته قسمت 3 اینه که میگه که قبل از این که کد داخل constructor کلاس Countries Repository اجرا بشه runtime میاد constructor base class generic repository رو با context اش میاد Call میکنه
قسمت 4 ام میگه که توی constructor generic repository اتفاقی که میوفته اینه که context_ با context پر میشه
قسمت 5 میگه که بعد از این کار در constructo base class انجام شد Controls میاد اون رو به constructor countries repository برمیگردونه و اونجا ما میتونیم یه سری تنظیماتی که مربوط به کشور هستش رو اونجا انجام بدیم
نکته ی بعدی :
خوب حالا اینجا میایم توی controller اصلی و اینجا حالا میخوایم از repository استفاده کنیم :
بعدش به این صورت میشه :
خوب این مورد رو باید دقت داشته باشیم که ما هر جایی از ICountry Repository استفاده کردیم ما اومدیم از IGeneric Repository که مشخصا با تایپ Country هستش ارث بری کردیم
پس هر جایی اومدیم ازش استفاده کنیم ، مشخصا داره Country رو پیاده سازی میکنه
مثل عکس بالا که مشخصا میخواد بره و لیست تمامی کشور ها رو بیاره
خوب اینجا اومدیم و GetAllAsync رو اول در اینترفیس تعریف کردیم و بعد اومدیم توی Generic Repository پیاده سازیش رو انجام دادیم و بعد اومدیم با استفاده از ارث بری که اینترفیس ها Generic و متد های اجرایی Generic میتونیم از این ها استفاده کنیم.
خوب حالا به طور مثال اینجا اول میایم id رو میگیریم و بعد عملیات delete رو انجام میدیم :
خوب حالا اینجا میخوایم از Exitst استفاده کنیم :
خوب حالا میرسیم به موردی که خاص هستش و مشخصا برای Country هستش و میخوایم الان تغییرات رو روش اعمال کنیم :
اینجا میخوایم اسم و مشخصات کشور + Hotel ها رو بیاریم که این مشخصا و فقط برای Country هستش
اینجا ما نمیتونیم از این متد استفاده کنیم، چون در این حالت فقط id رو میدیم و مشخصا اون کشور رو میگیریم و دیگه اسم هتل هایی که توی اون کشور هستش رو نمیتونیم ببینیم ، برای همین باید یه متد خاص برای این کار بنویسیم
برای همین اول میایم توی اینترفیس ها تعریفش میکنیم
نکته: اگر اینجا first or default نتونه اون Id رو پیدا کنه null برمیگردونه
خوب اینطوری ازش استفاده میکنیم .
اینجا به این ارور برخوردم وقتی که توی swagger زدم روی get System.MissingMethodException
خوب در حین اجرای پروژه به این ارور خوردم و وقتی که چک کردم دیدم که یه سری از پکیج هایی که مرتبط با ef هستش یه سریشون که مرتبط با scafolding بوده آپدیت شده و بقیه همون ورژن قدیمی هستن که همین باعث ارور شده بود
خوب اینجا هم داستان به همین صورته که اینجا هم باید از DTO ها استفاده کنیم به این دلیل که به طور مثال اینجا ما وقتی که GetHotels رو زدیم بهمون داره کشور رو هم میده و داخل اون کشور باز هم Hotels هستش که خودش میشه Recursive و برای جلوگیری کردن از این قضیه میایم و از DTO استفاده میکنیم
البته وقتی که request میزنیم این رو تحویل میده :
خوب اینجا برای این که به ارور نخوریم برای این که این مقدار double میتونه null باشه و خالی باشه یه نه میایم از ؟ استفاده میکنیم
نکته ی بعدی
اینجا ما میخوایم اطلاعات رو از دیتابیس بگیریم و به کاربر نشون بدیم ، اینجا ما میایم اطلاعات رو از دیتابیس میگیریم و بعد تبدیلش میکنیم به صورت DTO و بعد به کاربر نشونش میدیم و برای همین از اون مدل map استفاده میکنیم
در این حالت ما میخوایم اطلاعات رو از کاربر دریافت کنیم میایم اون به فرم DTO دریافتش میکنیم ولی برای این که بخوایم این اطلاعات رو توی دیتابیس بفرستیم باید به شکل اصلی خودش باشه بنابراین میایم اون dto رو به شکل اصلی دیتا که hotel هستش درمیاریم
خوب توی دو تا عکس بالا از map به دو شکل متفاوت استفاده شده :
میرسیم به قسمت api secuity
خوب یکی از راه های امن کردن api اینه که بیایم بر اساس ip دسترسی به ip رو درست کنیم
یه راهش اینه که برای هر کسی که میخواد به این api دسترسی پیدا کنه بیایم و یوزر پسورد بدیم و هر موقع که میخواست ازش استفاده کنه بیاد و user password رو وارد کنه ولی مشکل از اینجا شروع میشه که اگر توی هر درخواست Api بخواد برای دسترسی بیاد و هر سری که درخواست میاد اون رو چک کنه و اطلاعاتش رو از روی یک دیتابیسی چک میکنه که ببینه این یوزر اصلا دسترسی داره یا خیر ، و بعد اگر دسترسی داشت از یه دیتابیس دیگه بیاد و اون درخواست رو اجرا کنه که خوده این پروسه یک بار زیادی رو روی سیستم اضافه میکنه.
یه راه دیگه اش api key access هستش که شبیه به همون مورد قبلی یعنی basic authentication هستش و اینطوری عمل میکنه که میگه یه کدی داریم که هر سری که میخوایم درخواست بدیم میایم اون رو هم باهاش میفرسیتم ولی همون کلید هم که میفرستیم هم باید بیایم و چکش کنیم ببینیم که اوکی هست یا نه یه راهی که معمولا برای درست کردن این کلید ها انجام میشه اینه که همون مشخصاتی که توی basic auth استفاده کردیم ازشون رو encode میکنیم و اون رو به شکل یه کلید در میاریم و با درخواستمون میفرستیم و اون سمت توی سرور باید این کلید بیاد و Decode بشه و پروسه ی Validation check روش انجام بشه
یک بدی دیگه ای که داره اینه که هر کسی که این کلید رو بدست بیاره میتونه به جای یوزر اصلی بیاد و یه درخواست رو Spoof کنه
مورد بعدی استفاد از jwt هستش اینطوریه که یه توکن با یه محدودیت زمانی هستش و بعد از این که تایمش تموم شد دوباره درست میشه
خوب حالا سوالی که پیش میاد اینه که فرق این مورد با مورد قبلی چیه؟
اول این که این web token ها قرار نیست خیلی اساسی secure باشن و اطلاعات حساس هم نباید توشن باشن
ولی توشون یه چیزی به اسم claims رو قرار میدیم که هر یوزی برای دسترسی باید اون ها رو داشته باشه
خوب اول این که هر یوزری با user , password وارد میشه و authenticate روش انجام میشه و بعد از اون بهشون توکن میدیم و هر وقتی که خواستن درخواستی بزنن اون توکن رو باهاش میفرستن و وقتی که اون درخواست رو گرفتیم میایم دیکدش میکنیم و میبینیم که آیا اون اطلاعات پایه که تمامی یوزر های برای دسترسی باید داشته باشن رو داره یا خیر
چیزهایی که قراره ازش مطمئن بشیم اینه که بفهیمیم که از سمت سرور خودمون درست شده باشه و این که مطمئن بشیم که توسط خوده کلاینتمون ارسال شده
و به این توکن ها هم یه طول عمر 30 دقیقه ای میدیم
خوب حالا برای این کار ها میایم و از identity استفاده میکنیم :
اولی identity user که میاد یه یوزر رو تعریف میکنه و خودش یه سری چیزها رو مثل email , شماره تلفن و user name , password و encription رو همه رو با هم به صورت built in داره و میتونیم خودمون هم custumize اش کنیم ولی داستان زیاد داره ،
خوب قسمت بعدی که identity role داره اینه که اینجا میتونیم تعریفش کنیم که اون یوزر چه کار هایی رو بر اساس اون role که داره میتونه انجام بده
و بعد تعیین میکنیم که از کدوم دیتابیس برای این کار ها استفاده میکنیم با استفاده از Add Entity framework stores و بعدش تعیین میکنیم که کدوم دیتابیس رو میخوایم استفاده کنیم
خوب حالا به طور مثال میخوایم first name و last name رو به idenity user اضافه کنیم
خوب برای این کار میایم یه کلاس میسازیم از identity user ارث بری میکنیم
حالا توی program میایم identiy user رو عوض میکنیم :
خوب حالا ما اینجا تعریف کردیم که از این دیتابیس برای identity استفاده بشه ، وقتی که این کار رو کردیم حالا میایم کلاس HotelListingDbContext رو میایم ویرایش میکنیم و به اون هم میگیم که قراره به عنوان Identity db context هم ازش استفاده بشه :
خوب بعد از این که تغییرات رو انجام مدیم migration رو انجام میدیم
خوب حالا میرسیم به قسمت تعریف role ها
در اینجا میخوایم تنظیمات مرتبط با role ها رو تعیین کنیم برای همین یه کلاس به اسم role configuration ساختیم و برای این کار باید از IEntityTypeConfiguration قسمت Identity role ارث بری کنیم
خوب حالا اینجا میایم این config که تعریف کردیم رو میبریم و به dbcontext اضافه اش میکنیم :
نکته راجع به base.OnModelCreating
برای country هم میتونیم استفاده کنیم :
خوب بعد از این که این کار های رو انجام دادیم میایم و migration رو انجام میدیم و اونجا میبینیم که فقط دو تا role که تعریف کرده بودیم به قسمت up اضافه شده
خوب حالا میایم داخل دیتابیس رو نگاه میکنیم
نکته به صورت دیفالت چیزی که password validation خوده .net چک میکنه اینه که password باید حداقل یک حرف کوچیک و حداقل یک symbol , حداقل یک عدد و نمیتونه کمتر از 7 تا کارکتر باشه
خوب حالا ما میایم این رو به شکل که میخوایم custumize میکنیم
خوب اینجا میایم از UserManager در constructor استفاده میکنیم
خوب حالا میخوایم وقتی که یه یوزی خواست ثبت نام کنه اگر به مشکل خورد بیایم ارور رو بهش نشون بدیم برای این کار میایم از Identity error استفاده میکنیم :
دلیل این که IEnumerable استفاده کردیم به خاطر اینه که ممکنه چند تا ارور داشته باشیم اگر خالی باشه که هیچی
نکته : وقتی که یک یوزر رو درست میکنیم باید باید بهش آدرس ایمیل و username بهش اختصاص بدیم و این جز رفتار default identiy core هستش
خوب حالا اینجا به همین دلیل باید تعیین کنیم که آیا همون ایمیل تبدیل بشه به user name یا بیایم و user name رو جدا از email تعیین کنیم
خوب حالا اینطوریه که ما اول دیتایی که میخوایم رو با استفاده از ApiUserDto از کلاینت میگیریم و بعد میایم اون دیتایی که گرفتیم رو با استفاده از auto mapper به Api User تبدیلش میکنیم
که توی api یوزر علاوه بر فیلد هایی که خوده identity user داره ، اومدیم و بهش دو تا فیلد جدید رو هم اضافه کردیم
بعد طبق قوانینی که بالاتر گفته شده و اینطوری بود که باید به جز ایمیل username رو هم تعریف کنیم اینجا username رو با ایمیل یکی کردیم و در مرحله ی بعد میایم با user manager create async میایم اطلاعات یوزر رو میگیریم و پسوردی که وارد شده رو به صورت hash شده درمیاریم حالا اگر این عملیات موفقت آمیز بود میایم با استفاده از Add to role async اون رو به role که میخوایم اضافه میکنیم که اینجا تعیین کردیم اون role از نوع user باشه
در مرحله ی بعد چون ما توی Return داریم ارور ها رو برمیگردونیم ، میایم میگیم که اگر اروری اتفاق افتاد بیاد اون رو برگردونه
نکته patch برای update به صورت target ای هستش ولی put کلی آپدیت رو انجام میده و متفاوته
خوب اینجا تعیین کردیم که میخوایم اطلاعات رو از سمت کلاینت به سرور بفرستیم و میخوایم post رو انجام بدیم و براش یه مسیر تعیین کردیم و بعد با استفاده از attribute سوم تعیین کردیم که ممکنه این عملیات جوابی که میده ممکنه bad request باشه و اینطوری اون کسی که توی فرانت کد میزنه میتونه یه سری اطلاعات از این که چه اتفاقی ممکنه بیوفته رو میفهمه و این رو توی swagger نشون میده
نکته ی مهم : اینجا وقتی که پروژه رو run میکردم بعد از هر request که میزدم این ارور میومد
System.ArgumentException: GenericArguments[0], 'System.Char', on 'T MaxFloat[T](System.Collections.Generic.IEnumerable`1[T])' violates the constraint of type 'T'.
---> System.Security.VerificationException: Method System.Linq.Enumerable.MaxFloat: type argument 'System.Char' violates the constraint of type parameter 'T'.
at System.RuntimeMethodHandle.GetStubIfNeeded(RuntimeMethodHandleInternal method, RuntimeType declaringType, RuntimeType[] methodInstantiation)
at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
--- End of inner exception stack trace ---
at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
at AutoMapper.Internal.TypeDetails.<>c__DisplayClass25_1.<GetPublicNoArgExtensionMethods>b__10(MethodInfo extensionMethod)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at System.Linq.Enumerable.SelectManyIterator[TSource,TCollection,TResult](IEnumerable`1 source, Func`2 collectionSelector, Func`3 resultSelector)+MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.UnionIterator`1.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at AutoMapper.Internal.TypeDetails.PossibleNames()
at AutoMapper.Internal.TypeDetails.GetMember(String name)
با سرچ زدن رسیدم به این سایت که توش نوشته بود علت این ارور به خاطر ورژن 11 auto mapper هستش و در ورژن های بعدی برای .net 7 این قضیه رو درست کردن :
خوب حالا میرسیم به قسمتی که میخوایم login کنیم برای این قضیه میایم یه dto جدا درست میکنیم :
که api user dto از این dto ارث بری میکنه و بعد میایم توی اینترفیس IAuthManager متدی برای log in کردن تعیین میکنیم و بعد میایم در Auth manager پیاده سازییش رو انجام میدیم
خوب حالا اگر اتفاقی که بیوفته این باشه که null بیاد توی user که این باعث exception میشه
نکته : وقتی که متغییری نشون میده که task<> یعنی این که ما درست منتظر اون object نشدیم یعنی از await استفاده نکردیم
اینطوری درست میشه :
خوب اینجا کاری که کردیم این بوده که اگر null اومد و exception اتفاق افتاد کل api خاموش نمیشه و فقط false رو برمیگردونه
خوب ما یه پروسه دیگه به اسم sign in کردن رو هم داریم که با کاری که داریم انجا انجام میدیم متفاوته و توی sign in اتفاقی که میوفته اینه که cooke و session و کار های دیگه هم انجام میشه ولی اینجا نه
ولی api ها sateless هستند
خوب log in فقط میاد چک میکنه که ببینه که user در سیستم هست یا خیر و یه فرقی که api با یه web app داره که توی Web app یه cookie میتونیم درست کنیم یا یه کارهایی میتونیم بکنیم که ارتباطمون با اون یوزر و کلاینت حفظ بشه و Session مون حفظ بشه و از طریق اون بفهمیم که کی داره ازش استفاده میکنه ولی api ها stateless هستند و نمیدونند که چه کسی و چه زمانی قراره ازشون استفاده بشه و به همین دلیل هم هر بار که بخوایم یه درخواستی رو ارسال کنیم باید log in کنیم که میتونیم با راه هایی این رو بهترش کنیم خوب یه راهش اینه که این دسترسی و ارتباط رو از طریق log in میتونیم بهتر کنیم و مدیریتش کنیم و این کار رو میتونیم از طریق jwt ها انجام بدیم
اول authentication انجام میشه و بعد authorization انجام میشه
خوب ما به جای استفاده از magical string اومدیم از jwtBearerDefaults.authenticationScheme استفاده میکنیم
خوب این دو تایی که این بالا توضیح داده defualt authenticated scheme و default challenge schme ، خوب اولی میشه schme که برای user که authenticated هست رو انجام میده و دومی برای زمانی که یکی میخواد دسترسی بدون اجازه و غیر قانونی داشته باشه
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions?view=aspnetcore-8.0
https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/security
Dustin Metzgar - .NET in Action-Manning Publications (2024)
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidAudience = builder.Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]))
};
خوب addJwtBearer این متد JWT Bearer Authentication رو به app اضافه میکنه
خوب tokenValidation paramiter این پارامتر ها رو برای validate کردن توکن استفاده میکنیم
خوب حالا داخلش از چیا استفاده شده یکیش validate issue user هستش که با این ما مطمئن میشیم که کسی که token با key همخوانی داره
و validate audience مطمئن میشیم که این token به شخص درستی داده شده و validate life time اینجا مطمئن میشیم که token تاریخ انقضا اش نگذشته باشه و clock skew یه تلورانسی رو برای انقضا داره در نظر میگیره که اگر zero بزنیم یعنی هیچ تلورانسی نداره
خوب تنظیمات valid issuer و audience به این صورته که معمولا valid issuer میشه همون سرومون یا خوده برنامه و valid audence میشه اون client که داره از api استفاده میکنه
خوب issuer sign key برای sign کردن token استفاده میشه و یک کلید symetric هستش خوب برای این که مسائل امنیتی رو رعایت کنیم میتونیم این مقدار key رو بیایم توی configuration file ذخیره کنیم یا enviroment variable
خوب معنی symetric key اینه که یک secret key هم برای sign و هم برای verify کردن توکن استفاده میشه ، این یعنی این که از یک key برای درست کردن token signuture استفاده میشه و در مرحله بعد هم از همون برای confrim کردن authenticity اش استفاده میشه
مثل یه رمز shared sectert password میمونه که فقط فرستنده و گیرنده اون رو میدونن
خوب sigin زمانی که سرور jwt رو میسازه که برای ساختنش میاد از sectert key استفاه میکنه که یه digital signiture برای token بسازه ، این signutuee برای این هستش که مطمئن بشیم که کسی token رو دستکاری نکرده
خوب در مرحله بعد که verification هستش وفتی که client میاد jwt رو میگیره با همون secret key میاد بررسی میکنه ، اگر valid باشه که confitm میکنه و معلوم میشه که authntic هستش اگر نه یعنی این که دستکاری شده
خوب asymetric key اینطوری که private key به صورت secret و مخفی استفاده میشه و public key هم معلوم و به صورت عمومی استفاده میشه
https://medium.com/@swayamraina/symmetric-vs-asymmetric-jwts-bd5d1a9567f6
خوب این configuration که داریم اینجا ست میکنیم رو باید بریم توی app setting بریم تنظیماتش رو به صورت json بنویسیم و این که درست نوشتن در اون خیلی داستان داره و کوچکترین اشتباهی باعث ارور میشه و نمیزاره برنامه run بشه
خوب دلیل این که از encoding utf8 get yte داریم استفاده میکنیم اینه که این تابع میاد کلید رو که به صورت string هستش تبدیل و convert اش میکنه به byte arry که که این فرمت مورد نیاز symetric seecurity key هستش
برای این که بخوایم امنیت یه داده رو بیشتر کنیم میتونیم توی یه قسمت دیگه به اسم UserSercrets بزاریم :
روی پروژه کلیک راست میکنیم و بعد میزنیم روی manage user secrets اینجا هم دقیقا شبیه به configuration file هستش ولی به صورت secret
در مرحله ی بعدی میایم add authention استفاده میکنیم و jwt رو به عنوان دیفالت تنظیماتش رو انجام میدیم:
نکته
اینجا داریم مگیم که داخل configuration برو به jwtSettings و اون فیلدی که Issuer هست رو مقدارش رو بیار که اینجا میشه HotelListingAPI
حالا میخوایم توی AuthManager بیایم یه متد بسازیم که مسئول درست کردن توکنه
خوب توی jwt security token اومدیم تنظیمات برای تولید jwt token رو انجام میدیم و اطلاعات لازم رو داریم بهش میدیم ، اول این که از configuration میره نام و اطلاعات issuer , audience رو میگیره و claims ها رو برابر claim هایی که بالاتر براش نوشتیم قرار میدیم و بعد میگیم که تا چه زمانی اعتبار داره
در مرحله آخر sign in creadentials رو برابر اون crediencial که در بالاتر درست کردیم با استفاده از security key و الگوریتم هایی که براش در نظر گرفتیم
با گرفتن تمامی این اطلاعات بعدش به ما token میده
خوب roles اینجا یه collection ای از role هایی که توی برنامه داریم برای هر user هستش ، که هر کدوم از این role ها یه role name داره مثل admin , user و …
خوب متدی که توی این خط ازش استفاده شده اومده و از linq استفاده کرده که میاد هر کدوم از المان رو project میکنه به یک شکل و form جدید ، که توی این حالت میاد یه سری claim obbject رو برای هر کدوم از role های که توی لیست roles هستند درست میکنه
برای هر کدوم از role ها که میشه همون x توی لیست roles میاد یه سری claim object درست میکنه خوب claim type role میشه همون تایپ پیش فرض برای role و role name x میفرستیم برای claim ها
خوب این roleclams ها شامل یه لیست از claim ها هستند برای user role ها این claim ها به JWT اضافه میشن وقتی که میخواد generate کنه jwt token رو ازشون توی فرایند های authurization استفاده میشه و برای هر یوزر میاد action هایی رو که میتونه انجام بده رو تعیین میکنه
توضیحات عکس بالایی در بالاتر نوشته شده است
میاد با union تمامی claim هایی که داره رو به هم وصل میکنه
https://medium.com/@short_sparrow/how-hmac-works-step-by-step-explanation-with-examples-f4aff5efb40e
https://www.geeksforgeeks.org/what-is-hmachash-based-message-authentication-code/
خوب اولی که sub هستش : این منظورش عنوان توکن هستش که معمولا email یوزر هستش ، که به صورت خاص و مشخص هست
دومی jti هستش یا همون jwt id که داره یک uniqe identifer رو برای توکنمون میسازه که معمولا هم guid استفاده میشه
سومی هم که ایمیل هست
چهارمی uid هستش که شامل User uniqe identifer هستش که کمک میکنه به شناسایی کاربر سیستم
اینجا تمامی اطلاعاتی که درست کردیم رو به شکل string برمیگردونیم
در مرحله قراره که ریفکتور انجام بدیم :
خوب توی login داریم از generate token استفاده میکنیم
خوب وقتی که بخوایم از breaer استفاده کنیم باید header از نوع authorization رو داشته باشیم و اول اون توکنی که داره هم باید Bearer داشته باشه
میتونیم تعیین کنیم که به این متد چه role هایی دسترسی دارند :
خوب برای این که یک یوزر رو به شکل admin دربیاریم میایم از role id اون id که نمایانگر Adminstrator هستش رو کپی میکنیم و بعد میایم user id رو کپی میکنیم و بعد اون Role id رو بهش میدیم اینطوری admin هم به user دسترسی داره و هم به admin
توضیحات refresh توکن پایین تر نوشته شده
خوب توی response هم token تغییر کرده و هم refresh token که هر دو جدید هستند
خوب یک مرحله قبل از این که این refresh token ایجاد بشه ، باید قبلش بیایم و چکش کنیم
خوب حالا برای چک کردن میایم اول از jwt security token handler یه instance میسازیم که ازش استفاده کنیم ، بعد با استفاده از read jwt token میایم توکن رو میخونیم
خوب اطلاعات داخل token content به این شکل هستش :
توی jwt registerd clam names دقیقا اومده اسم های claim ها رو نوشته که ما میزنیم که email
بعدش با token claims to list میایم از داخل اون توکن claim ، ایمیل رو برمیداریم بعد میریم اولین claim با تایپ jwt registed claim names email رو select میکنیم اون ? value رو برای این گذاشتیم که اگر چیزی پیدا نکرد null بیاد و user name رو null بزاره
خوب بعدش میایم refresh token رو با استفاده از verify user token async که توی user manager هست ، میایم چکش میکنیم ، این متد میاد user , login provider , token name , refresh token رو از req میگیره و یه خروجی bool میده مبنی بر این که این توکن valid هستش یا خیر
خوب در مرحله ی بعد چک میکینم اگر refresh token valid بود میایم و یه توکن تازه میسازیم با استفاده از generate token و در مرحله ی بعد میایم با استفاده از dto هم توکن رو برمیگردونم سمت کاربر به همراه user id و refresh token که البته refresh token رو همونجا میسازیم با create refresh token
خوب اگر اون if بالایی درست نبود میایم user security stamp رو آپدیت میکنیم با استفاده از updatate security stamp async که یکی از متد های user manager هستش ، این کار باعث میشه که هر توکنی که از قبل درست شده باطل بشه
بعد از این که در refresh token اومدیم و دستکاری رو انجام دادیم به این صورت security stamp , concuency stamp عوض میشن
خوب اولش داره میگه که identity servis رو داره برای api user فعال میکنه
بعدش میاد قابلیت تنظیمات و مدیریت role ها رو به سیستم idenetity اضافه میکنه که با استفاده ازش میتونیم role بسازیم و به user ها مون اون role ها رو بدیم
خوب add token provider میاد token provider رو به api user اضافه میکنه و data protector token provider رو میاد برای تولید و validate کردن token ها استفاده میکنه و اون string اخر هم میشه اسم اون token provider
خوب add entity famework stores میاد identity رو configure میکنه برای استفاده از entity frame work برای این که دیتا ها که داره میگه رو با استفاده از اون توی دیتابیس ذخیره کنه مثل اطلاعات یوزر ها , token , role ها و …
https://medium.com/kocsistem/what-is-the-best-approach-for-jwt-refresh-token-682de2f5c43c
خوب حالا میرسیم به seq دوباره
در قسمت اولی اومدیم گفتیم که توی minimum level بیاد و log بگیره با این تنظیمات و در قسمت دوم گفتیم که به صورت روزانه بیاد لاگ بگیره و محل ذخیره سازیش رو هم بهش دادیم و در مرحله ی سوم اومدیم گفتیم که به این اسم برنامه و به این آدرس سرور باید وصل بشیم
برای seq اینطوریه که وقتی که api رو public کردیم برای همه قابل دسترسی میشه و میخوایم ببنیم که چه کسی داره از api چه درخواستی میکنه و respond اش چیه
خوب حالا اگر بخوایم یه چیزی رو لاگ بگیریم میایم اینطوری مینویسیم :
اومدیم ILogger رو توی constructor آوردمیش و اضافه اش کردیم نکته ای که هست وقتی که داریم از ILogger استفاده میکنیم اسم اون کلاس که داخلش هستیم رو توی تایپش مینویسیم
حالا میخوایم یه لاگ بگیریم زمانی که یه نفر به طور مثال رجیستر کرد
حالا مدل های مختلفی برای نوشتن لاگ داریم
خوب در تصویر بالا اومدیم نوشتیم که یوزر با ایمیل میخواد تلاش کنه برای register شدن در ابتدای پروسه و بعد اومدیم از try catch استفاده کردیم برای این که اگر اینجا مشکلی پیش اومد بیایم اون رو در log مون بنویسیم که بتونیم ازش استفاده کنیم
خوب پروسه رو میبریم داخل try و میایم در مرحله بعدی اگر Exception ای اتفاق افتاد میایم اون رو در لاگ مینویسیم به این شکل که اسم اونجایی که اتفاق افتاده رو هم داریم میاریم با name of و ایمیل رو هم نوشتیم
خوب اینجا به جای throw کردن میایم و ارور 500 رو با یک سری اطلاعات دیگه بهش نشون میدیم
نکته ای که اینجا هست اینه که ما یک سری پیام خطا رو برای کاربر سایت داریم مینویسیم و یک سری رو داریم برای خودمون تنظیم میکنیم که بتونیم در فرآیند اگر مشکلی پیش اومد بتونیم اون رو شناسایی کنیم
خوب به صورت منظقی وقتی که داریم لاگ ها رو میگیریم نمیخوایم که یوزر هر کاری که کرد که normal هستش رو بیایم و ازش لاگ بگیریم ، چیزی که برای ما مهمه اینه که از ارور ها بتونیم لاگ بگیریم
خوب حالا اینجا از log warning استفاده میکنیم و تعیین کردیم که اگر اون کشوری که با اون id درخواست داده شده وجود نداشت این رو به صورت warning برامون لاگ بگیره
خوب حالا میخوایم راجع به global exception handling صحبت کنیم - بحث اینه که در حالت قبلی اتفاقاتی که داره میوفته و میخوایم ازشون لاگ بگیریم رو دونه دونه باید بریم و براش در جاهای مختلف بنویسیم ولی میخوایم لاگ گرفتن رو از یه راه بهتر امتحانش کنیم :
خوب همونطور که میدونید Exception ها باعث میشن که برنامه بپوکه و ما میخوایم راهی رو پیدا کنیم که نخوایم همه جا بیایم از try catch استفاده کنیم
فقط وقتی که Ex اتفاق افتاد اون رو throw کنیم و بعد بیایم به صورت کلی در سیستم این exception ها رو بگیریم و براساس اون ex بیایم یه سری اقدامات رو انجام بدیم
خوب بیس تمامی Exception ها خوده Exception هستش :
و بقیه exception ها یه extention از این هستند
نکته ای که هست اینه که ما خودمون هم میتونیم یه EXCEPTION خودمون رو بسازیم
خوب حالا به طور مثال میخوایم همون کاری که در بالا انجام دادیم رو اینجا به یه شکل دیگه انجام بدیم :
خوب حالا میخوایم بیایم از middleware ها استفاده کنیم ، که برای استفاده از اون ها باید یه سری از کانسپت ها رو بدونیم
https://code-maze.com/working-with-asp-net-core-middleware/
https://tech-en.netlify.app/articles/en528692/index.html
اینجا next اینطوری کار میکنه که وقتی که Request میاد اون رو میگیره و میتونیم روش یه سری کارها رو انجام بدیم و next میشه اون عملیاتی که میخوایم روش انجام بدیم
خوب اینجا context تمامی اطلاعات مربوط به request رو داره ، مثل پاسخ احتمالی
using System.Net;
using System.Text.Json.Serialization;
using HotelListing.Api.Exceptions;
using Newtonsoft.Json;
namespace HotelListing.Api.Middleware
{
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception ex)
{
context.Response.ContentType = "application/json";
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
var errorDetails = new ErrorDetails
{
ErrorType = "failure",
ErrorMessage = ex.Message,
};
switch (ex)
{
case NotFoundException notFoundException:
statusCode = HttpStatusCode.NotFound;
errorDetails.ErrorType = "Not Found";
break;
default:
break;
}
string response = JsonConvert.SerializeObject(errorDetails);
context.Response.StatusCode =(int) statusCode;
return context.Response.WriteAsync(response);
}
}
public class ErrorDetails
{
public string ErrorType { get; set; }
public string ErrorMessage { get; set; }
}
}
توضیح کد : از ابتدا از RequestDelegate استفاده کردیم که request هایی که میان رو بگیریم بعد توی try catch گفتیم که اگر همه چی اوکی بود که request رو بفرست برای middleware بعدی و اگر exception اتفاق افتاد اون رو بگیریم و نسبت به تایپ و نوع اون exception بیایم یه کاری کنیم
خوب توی این حالت وقتی که exception اتفاق افتاد میایم دقیقا اون درخواست و exception ای که اتفاق افتاده رو میفرستیمش توی متد handle Exception Async و اونجا اول میایم بدترین حالت که internal server error هستش رو براش در نظر میگیریم حالا بعدش میایم توی case ها switch که اگر با یکی از case ها یکسان بود میایم و پیامش رو تغییر میدیم و اون رو به جای internal server error ارسال میکنیم
حالا ما اینجا اومدیم برای error مون کلاس تعریف کردیم و بعد اومدیم ازش استفاده کردیم برای بازگردادن پاسخ ، به این شکل که اگر به طور مثال exception اتفاق افتاده از نوع not found بود و ما اون رو توی case گرفتیمش میایم برای اون درخواست status code اش رو برابر با status code not found قرار میدیم و برای error type هم که در کلاس error detail هستش میگیم که not found هستش بعد میایم جوابمون رو به صورت json درمیاریم ، و بعد برای response به اون درخواست هم میایم status code مربوط به اون ارور رو به کاربر برمیگردونیم
خوب این نکته رو باید دقت داشته باشیم که ما یک بار همون اول کلاس error detail رو ساختیم و متن پیام ارور رو داخلش قرار دادیم و بعد توی switch اومدیم و نوع status code اش رو تعیین کردیم
خوب توی مرحله ی بعد میایم اون try catch هایی که نوشتیم رو پاک میکنیم چون دیگه بهشون نیازی نداریم و داریم به صورت globally میایم و error handling رو انجام میدیم
بعد هم میایم و به program اضافه اش میکنیم :
اینجا هم میایم و به جا استفاده از logger میایم throw میکنیم ارور رو که بیایم توی global اون رو بگیریم
خوب یک بخش مهم در بالا جا مونده بود که اینجا میخوام بنویسم :
بخش مهم register اینطوری داره کار میکنه که توی ورودی میاد api user dto رو میگیره که خوده این user dto داستان داره
خوده api user dto از یک dto دیگه داره ارث بری میکنه
خوب هدف از این که اومدیم از این دو تا dto استفاده کردیم اینه که تعداد dto کمتری بنویسیم و این که کلا هدف استفاده از dto اینه که بتونیم فقط اطلاعاتی که میخوایم از client بگیریم رو بگیریم
خوب بعدش اومدیم با استفاده از auto mapper اومدیم user dto که توی ورودی register داریم میگیریم و بعد میایم اون رو با mapper به api user وصل میکنیم
چرا؟ چون که ما تعیین کردیم که برای دسترسی و احراز هویت کاربران از کلاس api user استفاده کنیم به همین منظور اومدیم اون رو از identity user ارث بری کردیم
خوب حالا اتفاقی که میوفته اینه که اطلاعاتی که با استفاده از dto ها از کلاینت میگیریم رو میایم به api user وصل میکنیم و انتقال میدیم که اون بیاد به صورت اتوماتیک این اطلاعات رو در دیتابیس قرار بده و پسور رو hash کنه و بقیه کارهاش رو انجام بده
فقط نکته ی خیلی مهمی که هست اینه که
ما توی هیچ کدوم از کلاس ها و dto هایی که گفته شده چیزی به اسم user name نداریم ، ولی اینجا اومدیم گفتیم که user name برابر email باشه ، چطور ؟
اینطور که این user name توی کلاس identity user هستش که برای خوده .net هستش و باید بهش مقدار بدیم
اینجا هم نوشته شده
خوب حالا اگر نخوایم از mapper استفاده کنیم باید به این شکل عمل کنیم :
نتیجه میشه این که وقتی که این اطلاعات رو میزنیم :
به صورت اتوماتیک میره در دیتابیس این رکورد ها رو ایجاد میکنه:
error
"```
System.ArgumentOutOfRangeException: IDX10653: The encryption algorithm 'HS256' requires a key size of at least '128' bits. Key '[PII of type 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]', is of size: '88'. (Parameter 'key')
```"
برای پیاده سازی blazor https://www.youtube.com/watch?v=oqpNQxEfz_Y&ab_channel=CodeOverdose
خوب یه سری قسمت دیگه هم مونده که اینجا مینویسم :
خوب توی ورودی login provider داریم و refreshtoken که هر دو تا رو به صورت string اومدیم مقدار دهی کردیم و ازشون استفاده کردیم
خوب مرحله ی اول برای درست کردن refresh token اینه که اون توکنی که الان موجود هست برای یوزر رو پاکش کنیم برای همین از remove authentication token async استفاده کردیم
مرحله بعدش میایم refresh token رو درست میکنیم :
با generate user token async میایم این کار رو انجام میدیم
بعد میایم توکن جدیدی که درست شده رو به user میدیم و assign میکنیم :