0% found this document useful (0 votes)
13 views352 pages

Core5 0webapi

Uploaded by

tara Narimani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views352 pages

Core5 0webapi

Uploaded by

tara Narimani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 352

‫‪.

‬‬

‫راهنمای عملی‬
‫‪ASP.NET Core 5.0‬‬
‫‪Web API‬‬

‫زهرا بیات قلیالله‬


‫علی بیات قلیالله‬
‫راهنمای عملی‬
‫‪ASP.NET Core 5.0‬‬
‫‪Web API‬‬

‫مولفین ‪ :‬زهرا بیات قلیالله – علی بیات قلیالله‬


‫طراح جلد ‪ :‬زهرا بیات قلیالله‬
‫مشخصات ظاهری ‪352 :‬ص‬
‫سال انتشار‪ :‬بهمن ‪99‬‬
‫قیمت ‪ :‬رایگان‬
‫فهرست‬

‫تقدیم به ‪11 ........................................................................................‬‬

‫با تشکر ‪12 ........................................................................................‬‬

‫درباره این کتاب ‪13 ..............................................................................‬‬

‫فصل اول ‪ :‬تنظیمات پروژه ‪14 ..................................................................‬‬

‫تنظیمات پروژه ‪15 ...............................................................................‬‬

‫ایجاد پروژه جدید ‪15 ............................................................................‬‬

‫تنظیمات فایل ‪17 ...................................................... launchSettings.json‬‬

‫‪19 .................................................................‬‬ ‫کالس ‪ Program‬و ‪Startup‬‬

‫کالس ‪21 ................................................................................ Startup‬‬

‫اکستنشن متد تنظیمات ‪21 ............................................................ CORS‬‬

‫اکستنشن متد تنظیمات ‪23 ............................................................... IIS‬‬

‫اعمال تنظیمات در ‪24................................................................. Startup‬‬

‫تنظیمات ‪27 .................................................................... Environment‬‬

‫فصل دوم ‪ :‬سرویس ‪30 ................................................................ Logger‬‬

‫پیکربندی سرویس ‪31 ................................................................. Logger‬‬

‫ایجاد پروژههای زیرساخت ‪31 ..................................................................‬‬

‫ایجاد اینترفیس ‪ ILoggerManager‬و نصب ‪33 ....................................... NLog‬‬

‫پیادهسازی اینترفیس ‪ ILoggerManager‬و پیکربندی فایل ‪34 ............Nlog.Config‬‬

‫پیکربندی ‪36 ................................................................ Logger Service‬‬


‫‪ DI‬و ‪ IoC‬چیست؟‪38 ............................................................................‬‬

‫تست سرویس ‪ Logger‬با ‪41 ........................................................ Postman‬‬

‫فصل سوم ‪ :‬دیتابیس و ‪44............................................. Repository Pattern‬‬

‫مدل دیتابیس و ‪45 ..................................................... Repository Pattern‬‬

‫ایجاد مدلها ‪45 .................................................................................‬‬

‫ایجاد کالس ‪48 ....................................................................... Context‬‬

‫‪ ConnectionString‬چیست؟ ‪49................................................................‬‬

‫رجیستر ‪ DbContext‬از طریق ‪51 ........................................................... DI‬‬

‫‪ Migration‬چیست؟ ‪53 .........................................................................‬‬

‫‪ Seed Data‬چیست؟ ‪55 ........................................................................‬‬

‫پیادهسازی ‪59 .......................................................... Repository Pattern‬‬

‫پیادهسازی کالسهای ‪62 ......................................................... Repository‬‬

‫ایجاد یک ‪64........................................................... Repository Manager‬‬

‫فصل چهارم ‪ :‬مسیریابی و ‪70 ........................................................... REST‬‬

‫کنترلرها و مسیریابی در ‪71 ......................................................... Web API‬‬

‫‪ REST‬چیست؟ ‪74 ..............................................................................‬‬

‫‪ HTTP Method‬چیست؟ ‪76 ....................................................................‬‬

‫مقایسه بین ‪77 ....................................................... PUT - POST - PATCH‬‬

‫فرمت بازگشتی ‪78 ...................................................................... REST‬‬

‫قوانین نامگذاری مسیر اکشن متد ‪78 ........................................................‬‬

‫گرفتن اطالعات شرکتها از دیتابیس ‪79 ....................................................‬‬


83 ................................................. GetCompanies ‫تست کردن اکشن متد‬

84 ...................................... Entity Model ‫ در مقابل کالسهای‬DTO ‫کالسهای‬

87 ..............................................ASP.Net Core ‫ در‬AutoMapper ‫استفاده از‬

94........................... Content Negotiation ‫ها و‬Exception ‫ مدیریت‬: ‫فصل پنجم‬

95 ...................................................................... ‫ها‬Exception ‫مدیریت‬

100 ............................................................. UseExceptionHandler ‫تست‬

102 ............................................. ‫ از دیتابیس‬Resource ‫گرفتن اطالعات یک‬

106 ..................................................... Web API ‫ در‬Parent/Child ‫ارتباطات‬

113 .................................................................... ‫واکشی اطالعات کارمند‬

117 ............................................................ ‫ چیست؟‬Content Negotiation

118 ............................................................... Response ‫تغییر پیکربندی‬

119 ............................................................... Content Negotiation ‫تست‬

120 ................................................................. Media Type ‫محدود کردن‬

121 ....................................................................... ‫ایجاد فرمت سفارشی‬

125 .................................................. DELETE ‫ و‬POST , PUT : ‫فصل ششم‬

126 ....................................................................Post Request ‫مدیریت‬

133 .................................................................... Child Resource ‫ایجاد‬

137 ............................................ Parent ‫ همراه با یک‬Child Resource ‫ایجاد‬

139 .........................................................‫ها‬Resource ‫ایجاد مجموعهای از‬

145 .................................................................. ‫ چیست؟‬Model Binding

149 ................................................................... Delete ‫ایجاد اکشن متد‬


‫حذف یک ‪ Parent Resource‬همراه با ‪Child‬هایش ‪152 ....................................‬‬

‫آپدیت ‪157 .......................................................................... Employee‬‬

‫انواع روشهای ‪160 ..................................................................... Update‬‬

‫ایجاد ‪ Resource‬در هنگام آپدیت یک ‪161 ...................................... Resource‬‬

‫فصل هفتم ‪ Patch Request :‬و اعتبارسنجی ‪165 ...........................................‬‬

‫کار کردن با ‪166 .............................................................. Patch Request‬‬

‫اضافه کردن ‪ Patch‬به ‪168 ................................................ Employee Entity‬‬

‫اعتبارسنجی چیست؟ ‪174 .....................................................................‬‬

‫ولیدیشن در زمان ایجاد ‪176 ...................................................... Resource‬‬

‫اعتبارسنجی نوع ‪183 ....................................................................... int‬‬

‫اعتبارسنجی برای ‪185 ........................................................ PUT Request‬‬

‫اعتبارسنجی برای ‪188 .................................................... PATCH Request‬‬

‫فصل هشتم ‪ :‬برنامهنویسی ‪ Async‬و ‪Action Filter‬ها ‪194 ..................................‬‬

‫برنامهنویسی ‪ Async‬چیست؟ ‪195 ............................................................‬‬

‫کلمههای کلیدی ‪ await‬و ‪196 ......................................................... async‬‬

‫ریفکتور ‪197 ....................................................................... Repository‬‬

‫ریفکتور ‪201 .......................................................... CompaniesController‬‬

‫‪ Action Filter‬چیست؟ ‪216 .....................................................................‬‬

‫پیادهسازی ‪216 .................................................................. Action Filter‬‬

‫سطح ‪218 ........................................................................Action Filter‬‬

‫ترتیب اجرا شدن ‪Filter‬ها ‪219 .................................................................‬‬


220 ............................................................. Action Filter ‫اعتبارسنجی با‬

226 ............................................... Action Filter ‫ در‬Dependency Injection

233 ............................................. Paging, Filtering, Searching : ‫فصل نهم‬

234........................................................................... ‫ چیست؟‬Paging

234........................................................................ Paging ‫پیادهسازی‬

240 .............................................................................. Paging ‫ارتقای‬

246 .......................................................................... ‫ چیست؟‬Filtering

247 ................................................ ASP.NET Core ‫ در‬Filtering ‫پیادهسازی‬

249 ................................................................... Filtering ‫ارسال و تست‬

251 ....................................................................... ‫ چیست؟‬Searching

252 ....................................................... ‫پیادهسازی جستجو در اپلیکیشن‬

254 ............................................................. Searching ‫تست پیادهسازی‬

255................................................... Data Shaping ‫و‬ Sorting : ‫فصل دهم‬

256 ........................................................................... ‫ چیست؟‬Sorting

256 ................................................. ASP.NET Core ‫ در‬Sorting ‫پیادهسازی‬

262 ............................................................................... Sorting ‫تست‬

263 .................................................................. ‫ چیست؟‬Data Shaping

263 .............................................. ‫ را پیادهسازی کنیم؟‬Data Shaping ‫چطور‬

272 ........................................................... API Versioning : ‫فصل یازدهم‬

273 ........................................................................... API Versioning

274 .......................................................................... Versioning ‫تست‬


277 ............................................................. URL Versioning ‫استفاده از‬

278 ............................................................... HTTP Header Versioning

279 .................................................................... Version ‫منسوخ کردن‬

281 ................................................ Rate Limiting ‫ و‬Cache : ‫فصل دوازدهم‬

282.......................................................................... ‫ چیست؟‬Caching

282................................................................................. Cache ‫انواع‬

283 .................................................................. Cache ‫افزودن هدرهای‬

285 ................................................................ cache-store ‫اضافه کردن‬

288 ............................................................. ‫ چیست؟‬Expiration Model

290 ............................................................... ‫ چیست؟‬Validation Model

291 .................................................................. ‫پیادهسازی اعتبارسنجی‬

293 ........................................... Validation‫ و‬Expiration ‫پیکربندی هدرهای‬

295 ................................................................... ‫ چیست؟‬Rate Limiting

296 .................................................................... Rate-Limit ‫پیادهسازی‬

300.......................................................... Identity‫ و‬JWT : ‫فصل سیزدهم‬

301.............................................................................. Identity‫ و‬JWT

301............................................................... ‫ چیست؟‬ASP.NET Identity

302 ............................................. ‫ چیست؟‬Authorization ‫ و‬Authentication

306 ......................................................‫ها‬Role ‫ایجاد جداول و اضافه کردن‬

310................................................................................... User ‫ایجاد‬

316 ............................................................................... Login ‫فرآیند‬


‫‪ JWT‬چیست؟ ‪317 ...............................................................................‬‬

‫پیکربندی ‪319 ............................................................................. JWT‬‬

‫پیادهسازی ‪323 .............................................................. Authentication‬‬

‫اعتبارسنجی براساس ‪333 .............................................................. Role‬‬

‫فصل چهاردهم ‪ :‬ایجاد داکیومنت با ‪336 ......................................... Swagger‬‬

‫داکیومنت ‪ API‬چیست؟ ‪337 ..................................................................‬‬

‫‪ Swagger‬چیست؟ ‪337 ........................................................................‬‬

‫تنظیمات ‪346.........................................................................Swagger‬‬
‫تقدیم به‬
‫تقدیم به تمام دوستتداران برنامهنویستی که آمادهی استتفاده از تمام قابلیتهای خود‬
‫برای یادگیری هستند‪.‬‬

‫‪11‬‬
‫با تشکر‬
‫برنامه نویسها معموال به داشتتتن یک دوستتت هنرمند افترار میکنند‪ .‬من هم خیلی‬
‫خوشتحالم که با یکی از بهترین گرافیستتهای کشتورم یعنی خانم عستل ضتیایی دوستت‬
‫هستتم و تشتکر میکنم که با ایدههای زیبایش به هر چه بهتر شتدن این کتاب کمک‬
‫کردند‪.‬‬

‫‪12‬‬
‫درباره این کتاب‬
‫که میخواهن به صااورت عملی‪ ،‬صاا ر ا صاا‬ ‫این کتاب برای برنامهنویسااانی نوشااته شاا‬
‫ساخت ‪ Web API‬را برای ورود به بازار کار یاد بگیرن ‪.‬‬

‫این روزها به دلیل اسات اد روز افوو از اللیکیشانهای موبایل‪ ،‬نیاز به ‪API‬هایی که امکا ارائه‬
‫است‪.‬‬ ‫سرویس به هوارا کالینت به طور هموما را دارن به یک نیاز حیا ی ب یل ش‬

‫اگر نگاهی به آگهیهای شاللی بین ازی خواهی دی که ساه عم ای از بازار برنامهنویسای نیو‬
‫مربوط به وسعهی ‪ API‬است‪.‬‬

‫‪Web API‬‬ ‫از این رو در این کتااب ساااعی کردم برای برطرف کرد نیاازهاایی کاه در لروه هاای‬
‫وجود دارد مثال بیاورم و جربیا را با شما به اشتراک بگذارم‪ .‬این کتاب می وان یک نقشه را‬
‫‪.‬‬ ‫برای است اد در لروه های واقعی باش‬

‫‪13‬‬
‫فصل اول ‪ :‬تنظیمات پروژه‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ بررسی تنظیمات فایل ‪launchSettings.json‬‬

‫➢ آشنایی با کالس ‪ Program‬و‪Startup‬‬


‫➢ اعمال تنظیمات ‪ CORS‬و ‪ IIS‬در پروژه‬
‫➢ بررسی تنظیمات ‪Environment‬‬
‫تنظیمات پروژه‬
‫برای اینکه بتوانی اللیکیشانهای خوبی را وساعه دهی بای قبل از هر کاری‪ ،‬ب انی که چطور‬
‫اللیکیشن و سرویسهای درو آ را لیکربن ی کنی ‪.‬‬

‫‪ .NET‬اسااات ااد میکردیا‬ ‫‪Framework‬‬ ‫‪ .NET‬باا چیوی کاه در لروه هاای‬ ‫‪Core‬‬ ‫نظیماات در‬
‫‪ ،.NET‬دیگر خبری از فاایال ‪ web.config‬نیسااات و ماا از‬ ‫‪Core‬‬ ‫خیلی فرق دارد‪ .‬در لروه هاای‬
‫نظیمات داخلی فری ورک است اد میکنی ‪.‬‬

‫شاما در این فصال با کالس ‪ Startup‬و مت های درو آ آشانا میشاوی و یاد میگیری چطور‬
‫سرویسهای خود را رجیستر کنی ‪.‬‬

‫ایجاد پروژه جدید‬


‫‪ Visual Studio‬را باز کنی و روی ‪ Create a new project‬کلیک کنی ‪.‬‬

‫در کادر بع ی گوینه ‪ ASP.NET Core Web Application‬را انتخاب و بر روی ‪ Next‬بونی ‪.‬‬

‫‪15‬‬
‫حاال نام و مسیر لروه را انتخاب کنی ‪.‬‬

‫مرحله بع انتخاب ‪ ASP.NET Core 5.0 ، .NET Core‬و ‪ ASP.Net Core Web API‬است‪.‬‬

‫‪16‬‬
‫حاال بر روی ‪ Create‬کلیک کنی ا لروه ایجاد شود‪.‬‬

‫تنظیمات فایل ‪launchSettings.json‬‬


‫‪ ASP.NET‬را در زماا را انا ازی اللیکیشااان عیین‬ ‫‪Core‬‬ ‫فاایال ‪ launchSettings.json‬رفتاار‬
‫میکن ‪ .‬این فایل شامل نظیمات مربوط به ‪ IIS‬و اللیکیشنهای ‪ self-hosted‬است (‪.)Kestrel‬‬

‫‪)Solution‬‬ ‫‪Explorer‬‬ ‫(لاناجار‬ ‫‪Properties‬‬ ‫را مایاوا در باخا‬ ‫‪launchSettings.json‬‬ ‫فااایاال‬


‫ببینی ‪.‬‬

‫فایل ‪: launchSettings.json‬‬

‫‪17‬‬
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58753",
"sslPort": 44370
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"CompanyEmployees": {
"commandName": "Project",
"launchBrowser": false,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
: ‫بررسی فایل‬

‫ زما اسااتارت‬، ‫ وجود دارد که عیین میکن‬launchBrowser ‫در این فایل یک لرالر ی‬ •

.‫ مرورگر باز شود یا خیر‬،‫اللیکیشن‬


‫ لس‬، ‫ اسات اد میکنی و نیازی به مرورگر ن اری‬Postman ‫ از‬API ‫اگر برای چک کرد‬
. ‫لییر دهی‬ false ‫مق ار این لرالر ی را می وانی به‬
"launchBrowser": false,

18
‫یاک زد بااشااایا باایا در بخ‬ ‫‪Setup‬‬ ‫اگر چاک بااکس مربوط باه ‪ HTTPS‬را در مرحلاه‬ ‫•‬

‫‪HTTPS‬‬ ‫‪ applicationUrl‬دو ‪ URL‬ببینیا ‪ .‬این ‪URL‬هاا یکی برای ‪ HTTP‬و دیگری برای‬
‫است‪.‬‬
‫"‪"applicationUrl": "https://localhost:5001;http://localhost:5000‬‬

‫در این فاایال یاک لرالر ی ‪ sslPort‬وجود دارد کاه یاک عا د میگیرد‪ .‬این عا د لورت‬ ‫•‬

‫اجرا کنیا این لورت باه‬ ‫‪IISExpress‬‬ ‫را نظی و زماانیکاه اللیکیشااان را باا‬ ‫‪HTTPS‬‬

‫اللیکشن داد خواه ش ‪.‬‬


‫‪"sslPort": 44370‬‬

‫نکته!!‬
‫توجه داشتته باشتید که این پیکربندی ‪ ،HTTPS‬تنها در محیط ‪ Local‬معتبر استت‪ .‬پس‬
‫زمانیکه برنامه را در ستتروری ‪ Deploy‬میکنید باید این ‪ HTTPS‬را مجددا پیکربندی و‬
‫در اینجا یک ‪ HTTPS‬معتبر قرار دهید‪.‬‬

‫در اینجاا یاک ‪ Property‬باا ناام ‪ launchUrl‬وجود دارد کاه عیین میکنا کا ام ‪ URL‬در‬ ‫•‬

‫ابت ای اللیکیشاان اجرا شااود‪ .‬البته برای اینکه این ‪ Property‬به درسااتی کار کن بای‬
‫نظی کنی ‪.‬‬ ‫‪True‬‬ ‫‪ launchBrowser‬را نیو بر روی‬

‫بناابراین اگر لرالر ی ‪ launchUrl‬بر روی ‪ weatherforecast‬بااشااا ‪ ،‬زماانیکاه برنااماه اجرا‬


‫شود‪ ،‬اللیکیشن به مسیر زیر ه ایت خواه ش ‪.‬‬
‫‪https://localhost:5001/weatherforecast‬‬

‫کالس ‪ Program‬و ‪Startup‬‬


‫‪Program.cs‬‬ ‫مام اللیکیشاانهای ‪ ASP.NET Core‬همانن برنامههای ‪ ،Console‬با یک کالس‬
‫دارد و نقطه ورود اللیکیشااان اسااات‪ .‬زمانی که‬ ‫‪Main‬‬ ‫شااارو میشاااون ‪ .‬این کالس یک مت‬
‫اللیکیشن ‪ Start‬شود‪ ،‬این مت اجرا خواه ش ‪.‬‬
‫;‪using Microsoft.AspNetCore.Hosting‬‬
‫;‪using Microsoft.Extensions.Hosting‬‬

‫‪namespace CompanyEmployee.API‬‬

‫‪19‬‬
‫{‬
‫‪public class Program‬‬
‫{‬
‫)‪public static void Main(string[] args‬‬
‫{‬
‫;)(‪CreateHostBuilder(args).Build().Run‬‬
‫}‬

‫>= )‪public static IHostBuilder CreateHostBuilder(string[] args‬‬


‫)‪Host.CreateDefaultBuilder(args‬‬
‫>= ‪.ConfigureWebHostDefaults(webBuilder‬‬
‫{‬
‫;)(>‪webBuilder.UseStartup<Startup‬‬
‫;)}‬
‫}‬
‫}‬
‫اکنو خیلی‬ ‫اگر با ‪ .NET Core 1.0‬آشانایی داشاته باشای ‪ ،‬میبینی ک این کالس از این وره‬
‫اساات‪ .‬بعضاای قساامتها مثل )(‪ UseKestrel‬یا )(‪ UseIISIntegration‬دیگر وجود‬ ‫کوچک شا‬
‫نا ارد چو متا )‪ CreateDefaultBuilder(args‬برای خواناایی کا ‪ ،‬ماام این موارد را کپساااولاه‬
‫کرد است‪ .‬البته شما هنوز ه می وانی این نظیمات را دستی انجام دهی ‪.‬‬

‫این متا کاه کا )(>‪ webBuilder.UseStartup<Startup‬را صااا ا میزنا اا کالس ‪ Startup‬را‬


‫‪ ASP.NET Core‬الوامیسااات‪ .‬در این‬ ‫‪Web API‬‬ ‫‪ Initialize‬کنا ‪ .‬کالس ‪ Startup‬در لروه هاای‬
‫کالس ما مامی ساارویسهای موردنیاز اللیکیشاان را لیکربن ی میکنی ‪ .‬لایین ر در مورد این‬
‫است‪.‬‬ ‫کالس بیشتر وضیح داد ش‬

‫نکته!!‬
‫متتد )‪ CreateDefaultBuilder(args‬فتایتلهتای پیشفرض‪ ،‬متغیرهتای پروژه و پیکربنتدی‬
‫‪ Logger‬را تنظیم میکند‪.‬‬

‫قبال ‪ Logger‬در فرآیند ‪ Bootstrapping‬انجام میشتد و ما میتوانستتیم هر مشتکلی که‬


‫در طول ‪ Start‬اتفتا میافتتاد را‪ ،‬بتا آن ‪ Log‬کنیم‪ .‬این فرآینتد در نستترتههتای قبلی‬
‫سرتتر بود‪.‬‬

‫‪20‬‬
‫کالس ‪Startup‬‬
‫همانطور که دی ی کالس ‪ Program‬برای پیکربندی سااختار اللیکیشان شاما بود اما لیکربن ی‬
‫برخی از رفتارهای اپلیکیشن در کالس ‪ Startup‬انجام میشود‪.‬‬

‫هرگونه لیکربن ی که بای در زما اجرای ‪ ASP.NET Core‬انجام شااود‪ ،‬از این کالس شاارو‬
‫خواه شا ‪ .‬این کالس یک ‪ Constructor‬و دو مت دارد که مساالول لیکربن ی وب اللیکیشاان‬
‫است‪.‬‬

‫معلوم اسات‪ ،‬برای رجیساتر‬ ‫متد ‪ : ConfigureServices‬این مت همانطور که از نام‬ ‫•‬

‫کرد سرویس ها میباش ‪.‬‬


‫سارویس کالسای اسات که برخی از قابلیتها را به اللیکیشان اضاافه میکن ‪ .‬یا به زبان‬
‫ستادهتر ‪ :‬هر کالسای که اللیکیشان به آ وابساته باشا (چه در اللیکیشان مشاخش شاود‬
‫و چه وسط فری ورک است اد شود) بای در این مت رجیستر شود‪.‬‬
‫‪Pipeline‬‬ ‫را باه‬ ‫متتد ‪ : Configure‬باا این متا می وانی ‪Middleware‬هاای مختل‬ ‫•‬

‫که ک ی اساات که می وان ‪ HTTP Request‬و‬ ‫‪Middleware‬‬ ‫اللیکیشاان اضااافه کنی ‪.‬‬
‫‪ HTTP Response‬را لردازش‪ ،‬لییر و در نهایت به ‪ Middleware‬بع ی ده ‪.‬‬

‫نکته!!‬
‫از آنجا که اپلیکیشتنهای بزرگتر‪ ،‬سترویسهای بیشتتری هم دارند پس حتما کدهای‬
‫آشتفته زیادی در متد ‪ ConfigureServices‬خواهیم داشتت‪ .‬برای آنکه خوانایی این متد‬
‫باالتر رود‪ ،‬میتوانیم با استتفاده از اکستتنشتن متدها‪ ،‬ستاختاری تعریف کنیم و از این‬
‫آشفتگی دور باشیم‪.‬‬

‫اکستنشن متد تنظیمات ‪CORS‬‬


‫اکستنشن متد چیست؟‬

‫اکستنشن مت ‪ ،‬یک مت استا یک است که بای قبل از اولین لارامتر ورودی آ ‪ ،‬کلمه کلی ی‬
‫‪ this‬را قرار دهی ‪ .‬این اولین لارامتر‪ ،‬نشا دهن ی نو داد ای است که این اکستنشن مت بر‬
‫آ یک قابلیت اضافه میکن ‪.‬‬

‫‪21‬‬
‫وجه داشته باشی که اکستنشن مت بای درو یک کالس استا یک عری شود‪.‬‬

‫خب حاال بیایی ک نویسی را شرو کنی ا ببینی این چیوهایی که گ تی چیست؟ به چه دردی‬
‫میخورد؟ و چطور بای اضافه شود؟‬

‫یک فول ر ج ی با نام ‪ Infrastructure‬ایجاد کنی سپس درو این فول ر یک فول ر دیگر با نام‬
‫‪ Extensions‬اضافه نمایی ‪.‬‬

‫حاال در اینجا یک کالس استا یک با نام ‪ ServiceExtensions‬ایجاد کنی ‪.‬‬


‫‪namespace CompanyEmployee.API.Infrastructure.Extensions‬‬
‫{‬
‫‪public static class ServiceExtensions‬‬
‫{‬
‫}‬
‫}‬
‫خب حاال می وانی چیوهایی که در لروه به آ نیاز داری را با این کالس لیاد سازی کنی ‪.‬‬

‫‪CORS (Cross-‬‬ ‫اولین کاری که میخواه انجام ده لیکربن ی ‪ CORS‬در اللیکیشاان اساات‪.‬‬
‫)‪ Origin Resource Sharing‬مکانیوم دساترسای داد یا مو ود کرد دساترسای به دومینهای‬
‫مختل اللیکیشن است‪.‬‬

‫اگر بخواهی در اللیکیشااان درخواساااتهایی را از یک ‪ Domain‬به ‪ Domain‬دیگر ب رساااتی ‪،‬‬


‫لیکربن ی ‪ CORS‬الوامی اسات‪ .‬بنابراین برای شارو میخواه ک ی را اضاافه کن که به مامی‬
‫درخواستها‪ ،‬از هر مب ایی اجاز دسترسی به ‪API‬های ما را ب ه ‪.‬‬
‫;‪using Microsoft.Extensions.DependencyInjection‬‬

‫‪namespace CompanyEmployee.API.Infrastructure.Extensions‬‬
‫{‬
‫‪public static class ServiceExtensions‬‬
‫{‬
‫)‪public static void ConfigureCors(this IServiceCollection services‬‬
‫>=‬
‫>= ‪services.AddCors(options‬‬
‫{‬
‫>= ‪options.AddPolicy("CorsPolicy", builder‬‬
‫)(‪builder.AllowAnyOrigin‬‬

‫‪22‬‬
‫)(‪.AllowAnyMethod‬‬
‫;))(‪.AllowAnyHeader‬‬
‫;)}‬
‫}‬

‫}‬
‫بررسی کد ‪:‬‬

‫در ک باال از نظیمات لایهی ‪ CORS policy‬اسات اد کردی ا به هر ‪ Method ،Origin‬و‬ ‫•‬

‫‪ Header‬اجاز دسترسی دهی ‪.‬‬

‫وجه داشاته باشای این نظیمات در مویط ‪ Production‬بای مو ود ر باشا ‪ .‬بنابراین‬


‫بااایاا از‬ ‫)(‪AllowAnyOrigin‬‬ ‫در ایاان مااواایااط بااه جااای اساااااتاا اااد از‬
‫)"‪ WithOrigins("https://example.com‬اسااات اد کنی ا درخواساااتها‪ ،‬نها از منبع‬
‫موردنظر اجاز دسترسی داشته باش ‪.‬‬

‫همچنین )(‪ AllowAnyMethod‬به مامی ‪HTTP method‬ها اجاز دساترسای میده ‪ ،‬در‬ ‫•‬

‫‪HTTP‬‬ ‫نها به‬ ‫)"‪WithMethods("POST", "GET‬‬ ‫حالیکه ما می وانی با اسات اد از مت‬


‫‪ method‬موردنظر اجاز دهی ‪.‬‬
‫و می وانیا متا )(‪ AllowAnyHeader‬را ه لییر دهیا اا فقط باه ها رهاای خااا اجااز‬ ‫•‬

‫دسترسی ده ‪ .‬برای مثال ‪:‬‬


‫)"‪WithHeaders("accept", "contenttype‬‬

‫اکستنشن متد تنظیمات ‪IIS‬‬


‫فرض ‪ Self-Hosted‬هسااتن و اگر بخواهی‬ ‫اللیکیشاانهای ‪ ASP.NET Core‬به صااورت لی‬
‫اللیکیشان خود را روی ‪ IIS‬هاسات کنی بای نظیمات ‪ IIS integration‬را انجام دهی ‪ .‬بنابراین‬
‫برای انجام این کار ما ک زیر را به کالس ‪ ServiceExtensions‬اضافه میکنی ‪.‬‬
‫;‪using Microsoft.AspNetCore.Builder‬‬
‫;‪using Microsoft.Extensions.DependencyInjection‬‬

‫‪namespace CompanyEmployee.API.Infrastructure.Extensions‬‬
‫{‬
‫‪public static class ServiceExtensions‬‬

‫‪23‬‬
{
public static void ConfigureCors(this IServiceCollection
services) =>
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});

public static void ConfigureIISIntegration(this IServiceCollection


services) => services.Configure<IISOptions>(options =>
{
});

Startup ‫اعمال تنظیمات در‬


‫ حاال برای اینکه‬. ‫خب ا اینجا ما اکسااتنشاان مت های خود را برای سااازمان هی ک نوشااتی‬
‫ برگردی ا این‬Startup ‫لشاتیبانی کن بای به کالس‬ IIS integration ‫ و‬CORS ‫اللیکیشان از‬
. ‫نظیمات را در مت های این کالس اضافه کنی‬
using CompanyEmployee.API.Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

namespace CompanyEmployee.API
{
public class Startup
{
public Startup(IConfiguration configuration)

24
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.ConfigureCors();
services.ConfigureIISIntegration();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title =
"CompanyEmployee.API", Version = "v1" });
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment


env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json",
"CompanyEmployee.API v1"));
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});

app.UseRouting();
app.UseAuthorization();
25
‫>= ‪app.UseEndpoints(endpoints‬‬
‫{‬
‫;)(‪endpoints.MapControllers‬‬
‫;)}‬
‫}‬
‫}‬
‫}‬
‫بررسی کد ‪:‬‬

‫در ک باال نظیمات ‪ CORS‬و ‪ IIS‬را در مت ‪ ConfigureServices‬اضاافه کردی و ساپس‬ ‫•‬

‫لیکربن ی ‪ CORS‬در ‪ Pipeline‬ر ا در مت ‪ Configure‬انجام دادی ‪.‬‬


‫‪ .NET‬باا وره ‪ 3.1‬فرق زیاادی نا ارد اماا باا این حاال لییرا ی را در‬ ‫‪5.0‬‬ ‫لیکربنا ی در‬ ‫•‬

‫کالس ‪ Startup‬میبینی که بهتر است آ ها را با ه بررسی کنی ‪.‬‬


‫در متا ‪ ConfigureServices‬متا هاای ‪ AddControllers‬و ‪ AddSwaggerGen‬را‬ ‫▪‬

‫میبینی ‪ .‬متا ‪ AddControllers‬مااننا وره قبال‪ ،‬وظی اهی رجیساااتر کرد‬


‫دارد‪ .‬این متا باا ‪View‬هاا و ‪Page‬هاا‬ ‫کنترلرهاا در ‪ IServiceCollection‬را برعها‬
‫کاری ن ارد چو در لروه ‪ Web API‬به آنها نیازی ن اری ‪.‬‬
‫‪WebAPI‬‬ ‫ه برای اضافه کرد ‪ Swagger‬جهت ست‬ ‫‪AddSwaggerGen‬‬ ‫مت‬ ‫▪‬

‫است‪.‬‬
‫در متا ‪ Configure‬ه ‪ ،‬متا هاای ‪ UseRouting‬و ‪ UseAuthorization‬باه ر یاب‬ ‫▪‬

‫برای اضافه کرد قابلیتهای ‪ Routing‬و ‪ Authorization‬به اللیکیشن هستن ‪.‬‬


‫متا هاای ‪ UseEndpoints‬باا ‪ MapControllers‬ه وظی اهی ‪ Routing‬را برعها‬ ‫▪‬

‫دارن ‪.‬‬
‫بااه‬ ‫‪Proxy‬‬ ‫کرد ه ا رهااای‬ ‫‪Forward‬‬ ‫برای‬ ‫‪UseForwardedHeaders‬‬ ‫مت ا‬ ‫▪‬

‫ریکوئست جاری است‬


‫مت ‪ ،UseStaticFiles‬اساات اد از فایلهای اسااتا یک را برای ریکوئساات مهیا‬ ‫▪‬

‫میکنا ‪ .‬اگر مسااایری را برای دایرکتوری فاایالهاای اساااتاا یاک نظی نکنیا ‪،‬‬
‫ریکوئست به صورت لی فرض از فول ر ‪ wwwroot‬است اد میکن ‪.‬‬

‫نکته!!‬

‫‪26‬‬
‫توجه داشتته باشتید که ترتیب اضتافه کردن ‪Middleware‬ها بستیار مهم استت بنابراین‬
‫قترار دهتیتد و هتمتچتنتیتن‬ ‫‪UseAuthorization‬‬ ‫را بتایتد بتعتد از‬ ‫‪UseRouting‬‬ ‫متتتد‬
‫باید قبل از ‪ UseRouting‬صدا زده شوند‪.‬‬ ‫متدهای ‪ UseCors‬یا ‪UseStaticFiles‬‬

‫تنظیمات ‪Environment‬‬
‫وسااعه میدهی اما به موض اینکه‬ ‫‪Development‬‬ ‫ما در حال حاضاار برنامه خود را در مویط‬
‫برنامه خود را ‪ Publish‬کنی ‪ ،‬اللیکیشن به مویط ‪ Production‬میرود‪.‬‬

‫مویط ‪ Development‬و ‪ Production‬بای ‪URL‬ها‪Port ،‬ها‪Connection string ،‬ها‪Password ،‬ها‬


‫و دیگر نظیمات حسااااس آنها از ه مساااتقل باشاا ‪ .‬بنابراین بای برای هر ک ام از مویطها‪،‬‬
‫لیکربن ی ج اگانهای داشته باشی که این در ‪ .NET 5.0‬بسیار ساد است‪.‬‬

‫در اینجا بهتر اسات به ساراف فایل ‪ appsettings.json‬بری ‪ .‬این فایل حاوی نظیمات ماسات‪ .‬اگر‬
‫فارض یااک فااایاال‬ ‫کانای ا مایبایانای ا کااه بااه صاااورت لایا‬ ‫‪Expand‬‬ ‫ایان فااایاال را‬
‫‪ appsetings.Development.json‬وجود دارد‪.‬‬

‫‪appsettings.json‬‬ ‫کرد‬ ‫‪Override‬‬ ‫برای‬ ‫‪apsettings.{EnvironmentSuffix}.json‬‬ ‫فاایالهاای‬


‫اصالی اسات اد میشاود‪ .‬ما همچنین می وانی یک مویط خاا را در لروه ایجاد کنی ‪ .‬به طور‬
‫مثال ‪ :‬مویط ‪.Production‬‬

‫گوینهی‬ ‫برای ایجاد مویط ‪ Production‬بای بر روی لروه راسات کلیک کنی و از منو باز شا‬
‫‪ Add→New Item‬را انتخاب نمایی ‪.‬‬

‫را انااتااخاااب و نااام ایاان فااایاال را‬ ‫‪App Settings File‬‬ ‫حاااال از کااادر باااز شاااا‬
‫‪ appsettings.Production.json‬بگذاری ‪.‬‬

‫‪27‬‬
‫بع از زد ‪ Add‬یک مویط ج ی اضافه خواه ش ‪.‬‬

‫خب ا اینجا در مورد مویطهای وسااعه اللیکیشاان و نوو ایجاد آ ها چیوهایی را یاد گرفتی‬
‫اما چطور بای مشخش کنی که اللیکیشن در ک ام یک از مویطها اجرا شود؟‬

‫‪ ASPNETCORE_ENVIRONMENT‬متلیری اسات که ‪ ASP.NET Core‬برای مشاخش شا‬


‫مویط اللیکیشان آ را جساتجو خواه کرد‪ .‬اگر مق ار این متلیر برابر با ‪ Development‬باشا ‪،‬‬
‫‪ASP.NET‬‬ ‫‪ IsDevelopment‬برابر با ‪ true‬خواه شاا ‪ .‬اگر این ‪ Property‬وجود ن اشااته باش ا‬
‫‪ Core‬فرض را بر این خواه گذاشت که در مویط ‪ Production‬هستی ‪.‬‬

‫این متغیر را میتوانید از دو روش تغییر داد ‪:‬‬

‫روش اول‪ :‬روی لروه راست کلیک کنی و با انتخاب گوینه ‪ Properties‬وارد کادر زیر شوی ‪.‬‬

‫‪28‬‬
‫ماااقاا ار ماااتااالااایااار‬ ‫مااای اااوانااایاا‬ ‫‪Environment variables‬‬ ‫حااااال در کاااادر‬
‫‪ ASPNETCORE_ENVIRONMENT‬را لییر دهی ‪.‬‬

‫‪ASPNETCORE_ENVIRONMENT‬‬ ‫روش دوم‪ :‬در فاایال ‪ launchSettings.json‬مقا ار متلیر‬


‫را لییر دهی ‪.‬‬

‫‪29‬‬
‫فصل دوم ‪ :‬سرویس ‪Logger‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ پیکربندی سرویس ‪Logger‬‬

‫➢ ‪ DI‬و ‪ IoC‬چیست؟‬
‫➢ تست سرویس ‪ Logger‬با ‪Postman‬‬
‫پیکربندی سرویس ‪Logger‬‬
‫چرا پیامهای ‪ Log‬در طول توسعه اپلیکیشن بسیار مهم هستند؟‬

‫بیای ‪ ،‬به راحتی می وانی ک هایما را دیباگ و‬ ‫وسااعه اللیکیشاان خطایی لی‬ ‫اگر در زما‬
‫را حل کنی ‪ .‬اما دیباگ کرد در مویط ‪ Production‬ساااد نیساات به‬ ‫آم‬ ‫مشااکلی که لی‬
‫مشاکل و بررسای اکساپشان‬ ‫همین دلیل لیامهای ‪ Log‬می وان یک را حل خوب برای فهمی‬
‫باش ‪.‬‬

‫عالو بر این‪ ،‬با است اد از ‪ Log‬می وا در زمانیکه به دیباگر دسترسی ن اری ‪ ،‬به راحتی جریا‬
‫برنامه را کنترل کنی ‪.‬‬

‫ما در این فصاال میخواهی با اساات اد از لکی ‪ ،NLog‬یک ساارویس س ا ارشاای ‪ Log‬به لروه‬
‫اضافه کنی ‪.‬‬

‫ایجاد پروژههای زیرساخت‬


‫برای اضافه کرد ‪ Log‬به اللیکیشن به دو لروه ج ی نیاز داری ‪.‬‬

‫اولین لروه را ‪ Contracts‬ناامگاذاری میکنی چو قرار اسااات اینترفیسهاای ماا در این‬ ‫•‬

‫لروه قرار گیرد‪ .‬این لروه قراردادهای کل اللیکیشن را شامل میشود‪.‬‬


‫لروه دوم ‪ LoggerService‬نام دارد که درآ ‪ ،‬منطق ‪ Logger‬قرار میگیرد‪.‬‬ ‫•‬

‫راساااات کلیاک و ساااپس‬ ‫‪Solution Explorer‬‬ ‫خاب برای ایجااد یاک لروه جا یا بر روی‬
‫‪Class Library (.NET‬‬ ‫→‪ Add‬را انتخااب کنیا ‪ .‬حاال در کادر باز شااا‬ ‫گویناهی‪New Project‬‬

‫)‪ Core‬را انتخاب نمایی ‪.‬‬

‫در کادر بع ‪ ،‬نام آ را ‪ Contracts‬بگذاری و بر روی ‪ Create‬کلیک کنی ‪.‬‬


‫‪31‬‬
‫برای لروه دوم ه همین مسیر را ادامه دهی اما نام آ را ‪ LoggerService‬بگذاری ‪.‬‬

‫حاال باید پروژهها به هم رفرنس داشته باشند‪.‬‬

‫برای این کار به ‪ Solution Explorer‬بروی ‪.‬‬

‫گویناه ‪Add Project‬‬ ‫در لروه ‪ LoggerService‬روی ‪ Dependencies‬راسااات کلیاک و‬ ‫•‬

‫‪ Reference‬را انتخاب نمایی ‪ .‬سااپس همانن صااویر لایین‪ ،‬چک باکس ‪ Contracts‬را‬
‫یک زد و بر روی ‪ OK‬کلیک کنی ‪.‬‬

‫‪32‬‬
‫گوینه ‪Add Project‬‬ ‫در لروه اصاالی بر روی ‪ Dependencies‬راساات کلیک و سااپس‬ ‫•‬

‫را بونیا ‪ .‬چو در لروه‬ ‫‪LoggerService‬‬ ‫یاک‬ ‫را انتخااب و در لاایاا‬ ‫‪Reference‬‬

‫‪ LoggerService‬رفرنس لروه ‪ Contracts‬را داری ‪ ،‬لس در اینجا نیازی به اضاافه کرد‬


‫رفرنس این لروه نیست‪.‬‬

‫ایجاد اینترفیس ‪ ILoggerManager‬و نصب ‪NLog‬‬


‫‪ Warning ،Debug‬و‪Error‬‬ ‫و‬ ‫‪Info‬‬ ‫در سااارویس ‪ ،Logger‬به مت هایی برای ‪ Log‬کرد لیامهای‬
‫نیاازمنا ی ‪ .‬بناابراین باایا در لروه ‪ Contracts‬یاک فولا ر باا ناام ‪ IServices‬ایجااد و ساااپس‬
‫اینترفیسی با نام ‪ ILoggerManager‬را در آ اضافه کنی ‪.‬‬
‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface ILoggerManager‬‬
‫{‬
‫;)‪void LogInfo(string message‬‬
‫;)‪void LogWarn(string message‬‬
‫;)‪void LogDebug(string message‬‬
‫;)‪void LogError(string message‬‬
‫}‬

‫}‬

‫قبل لیاد سازی این اینترفیس‪ ،‬بای در لروه ‪ LoggerService‬لکی ‪ NLog‬را نصب کنی ‪.‬‬

‫‪ NLog‬للت رم ‪ Log‬برای ‪ .NET‬است که به ما کمک میکن ا لیامهای خود را ‪ Log‬کنی ‪.‬‬

‫برای اضاافه کرد ‪ NLog‬وارد مسایر ‪ Tools→NuGet Package Manager‬شاوی ساپس بر روی‬
‫‪ Package Manager Console‬کلیک نمایی ‪.‬‬

‫حاال در اینجا عبارت لایین را ایپ و در لایا ‪ Enter‬بونی ‪.‬‬

‫‪33‬‬
Install-Package NLog.Extensions.Logging -Version 1.7.0 -ProjectName
LoggerService

.‫ در اللیکیشن نصب میشود‬NLog ‫بع از چن ثانیه‬

Nlog.Config ‫ و پیکربندی فایل‬ILoggerManager ‫پیادهسازی اینترفیس‬


‫ یک کالس با نام‬LoggerService ‫ بای در لروه‬ILoggerManager ‫برای لیاد سااازی اینترفیس‬
. ‫ اضافه کنی‬LoggerManager
using Contracts.IServices;
using NLog;

namespace LoggerService
{
public class LoggerManager : ILoggerManager
{
private static ILogger logger =
LogManager.GetCurrentClassLogger();
public LoggerManager()
{
}

public void LogDebug(string message)


{
logger.Debug(message);
}
34
public void LogError(string message)
{
logger.Error(message);
}

public void LogInfo(string message)


{
logger.Info(message);
}

public void LogWarn(string message)


{
logger.Warn(message);
}
}

}
: ‫بررسی کد‬

. ‫ میباشاان‬NLog ‫ی بر روی مت های‬Wrapper ‫ فقط‬،‫• همانطور که میبینی مت های ما‬


. ‫ هستن‬NLog ‫ ه هر دو بخشی از فضای نام‬LogManager ‫ و‬ILogger

‫ ناام این فاایالهاا و حا اقال ساااطوی کاه‬،Log ‫ باایا اطالعاا ی در مورد مکاا فاایالهاای‬NLog
‫ بنابراین در لروه اصالی بای مام این قراردادها‬. ‫ را در اختیار داشاته باشا‬، ‫ کنی‬Log ‫میخواهی‬
. ‫عری کنی‬ nlog.config ‫را در یک فایل متنی به نام‬

‫ ایجاد و ک های لایین را‬nlog.config ‫ یک فایل‬CompanyEmployee.API ‫خب در ریشاه لروه‬


. ‫در آ اضافه نمایی‬
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Trace"
internalLogFile="d:\Projects\LogsFolder\internal_logs\internallog.tx
t">
<targets>
<target name="logfile" xsi:type="File"

35
fileName="d:\Projects\LogsFolder/logs\${shortdate}_logfile.tx
t"
layout="${longdate} ${level:uppercase=true} ${message}"/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>

Logger Service ‫پیکربندی‬


‫ را در لروه اصالی نصاب‬NLog ‫ ابت ا بای لکی‬.‫ بسایار آساا اسات‬Logger ‫لیکربن ی سارویس‬
. ‫کنی‬
Install-Package NLog -Version 4.7.6 -ProjectName CompanyEmployee.API

. ‫ اضافه کنی‬Startup ‫های لایین را به کالس‬Namespace ‫حاال‬


using NLog;
using System.IO;

. ‫ را همانن ک لایین لییر دهی‬Startup ‫ کالس‬Constructor ‫در لایا بای‬


public Startup(IConfiguration configuration)
{
LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDirecto
ry(), "/nlog.config"));
Configuration = configuration;
}

‫ اساات اد‬LogManager ‫ کالس‬LoadConfiguration ‫ ما از مت‬، ‫همانطور که در ک باال میبینی‬


. ‫ را مشخش کنی‬Nlog ‫ ا مسیر لیکربن ی فایل‬، ‫کردی‬

36
‫بع از این مرحله بای سارویس ‪ Logger‬را در مت ‪ ConfigureServices‬رجیساتر کنی ‪ .‬رجیساتر‬
‫کرد این سرویس می وان به سه حالت انجام شود ‪:‬‬

‫‪ )1‬از متد ‪ services.AddSingleton‬استتفاده کنیم‪ .‬با این مت ‪ ،‬اولین بار که ریکوئساتی‬


‫دریافت کنی ‪ ،‬اللیکیشان یک ‪ Instance‬از سارویس ایجاد میکن و ریکوئساتهای بع ی‬
‫همه از همین ‪ Instance‬است اد میکنن ‪.‬‬
‫‪ )2‬از متد ‪ services. AddScoped‬استتتفاده کنیم‪ .‬با این مت ‪ ،‬هر بار که ریکوئساااتی‬
‫دریاافات کنی ‪ ،‬اللیکیشااان نهاا یاک ‪ Instance‬از سااارویس باابات آ ریکوئسااات ایجااد‬
‫میکن ‪ .‬یعنی اگر در یک ریکوئسات چن بار سارویس ما صا ا زد شاود ما هر بار هما‬
‫‪ Instance‬را میفرستی ‪.‬‬
‫استتتتفتاده کتنتیتم‪ .‬ایان مات ا بارعاکاس مات ا‬ ‫‪services. AddTransient‬‬ ‫‪ )3‬از متتتد‬
‫‪ services.AddScoped‬اسات‪ .‬یعنی اگر در یک ریکوئسات چن بار سارویس ما صا ا زد‬
‫شود ما ه چن ‪ Instance‬از سرویس ایجاد میکنی ‪.‬‬
‫خب حاال بیایی در کالس ‪ ServiceExtensions‬یک اکستنشن مت ج ی برای رجیستر‬
‫کرد سرویس ‪ Logger‬اضافه کنی ‪.‬‬
‫;‪using Contracts.IServices‬‬
‫;‪using LoggerService‬‬
‫;‪using Microsoft.AspNetCore.Builder‬‬
‫;‪using Microsoft.Extensions.DependencyInjection‬‬

‫‪namespace CompanyEmployee.API.Infrastructure.Extensions‬‬
‫{‬
‫‪public static class ServiceExtensions‬‬
‫{‬
‫)‪public static void ConfigureCors(this IServiceCollection services‬‬
‫>=‬
‫>= ‪services.AddCors(options‬‬
‫{‬
‫>= ‪options.AddPolicy("CorsPolicy", builder‬‬
‫)(‪builder.AllowAnyOrigin‬‬
‫)(‪.AllowAnyMethod‬‬
‫;))(‪.AllowAnyHeader‬‬
‫;)}‬

‫‪37‬‬
‫‪public static void ConfigureIISIntegration(this IServiceCollection‬‬
‫>= )‪services‬‬
‫>= ‪services.Configure<IISOptions>(options‬‬
‫{‬
‫;)}‬

‫‪public static void ConfigureLoggerService(this IServiceCollection‬‬


‫;)(>‪services) => services.AddScoped<ILoggerManager, LoggerManager‬‬

‫}‬

‫}‬

‫اال بای اکساتنشان مت ‪ ConfigureLoggerService‬را در مت ‪ ConfigureServices‬رجیساتر‬


‫کنی ‪.‬‬
‫;)(‪services.ConfigureIISIntegration‬‬

‫اسااات ااد کنی باایا اینترفیس‬ ‫‪Logger‬‬ ‫از این لس هر زماا کاه بخواهی از سااارویس‬
‫‪ .NET‬باا اجرای‬ ‫‪Core‬‬ ‫‪ ILoggerManager‬را باه ‪ Constructor‬کالس موردنظر ‪ Inject‬کنی ‪ ،‬اا‬
‫اللیکیشن‪ ،‬این سرویس را ‪ Resolve‬و ویژگی ‪ Log‬را قابل دسترسی نمای ‪.‬‬

‫این نو ‪ Inject‬کرد را ‪ Dependency Injection‬مینامن ‪ Dependency Injection .‬به صااورت‬


‫وکار در ‪ .NET Core‬وجود دارد‪.‬‬

‫بیایی کمی بیشتر در مورد این موضو صوبت کنی ‪.‬‬

‫‪ DI‬و ‪ IoC‬چیست؟‬
‫ممکن اسات در مورد ‪ DI‬شانی ‪ ،‬و حتی در اللیکیشانهای خود از آ اسات اد کرد باشای ؛ اما‬
‫اجاز دهی کمی در مورد این موضو صوبت کنی ‪.‬‬

‫کنیکی اساااات کااه بااا اسااات اااد از آ می وانی آبجکااتهااا و‬ ‫‪Dependency Injection‬‬

‫وابسااتگیهایشااا را از ه ج ا کنی ‪ .‬به بیان دیگر ‪ :‬به جای اینکه هر بار که به آبجکت نیاز‬
‫اسات‪ ،‬آ را به صاورت صاریح در کالس ‪ new‬کنی ‪ ،‬می وانی یک بار از شای ‪ Instance‬بساازی و‬
‫سپس آ را به کالس ارسال کنی ‪.‬‬

‫‪38‬‬
‫درک ‪ DI‬بستیار مهم اساات زیرا ‪ ASP.NET Core‬برای دریافت هر ساارویساای از ‪ DI‬اساات اد‬
‫میکن ‪.‬‬

‫‪ DI‬یک ‪ Design Pattern‬است که قابلیت نوشتن ک های ‪ Loosely Coupled‬را فراه میکن ‪.‬‬

‫شاید بپرسید‪ ،‬کدهای ‪ Loosely Coupled‬چیست و به چه دردی میخورد؟‬

‫ج ایی نالذیر در اللیکیشانهای ماسات‪ .‬این لییرات به مرور‬ ‫در دنیای نرمافوار‪ ،‬لییرات بخ‬
‫لایههای اللیکیشان میشاود‪ .‬آقای رابرت ساسایل مار ین معروف به عمو‬ ‫زما باعث خراب شا‬
‫بااب‪ ،‬اصاااولی باه ناام ‪ SOLID‬طراحی کرد کاه باه ماا کماک میکنا اا نرمافوار را طوری طراحی‬
‫کنی ‪ ،‬که کمتر به شکست بینجام ‪.‬‬

‫اصتل اول ‪ : S - SRP - Single Responsibility Principle‬هر ماژول نرمافزاری‬ ‫•‬

‫میبایست تنها یک دلیل برای تغییر داشته باشد‪.‬‬


‫‪ :‬ماژولهای نرمافزار باید‬ ‫‪O - OCP – Open Closed Principle‬‬ ‫اصتتل دوم‬ ‫•‬

‫و برای توسعه باز باشند‪.‬‬ ‫برای تغییرات بسته‬


‫اصتل ستوم ‪SubClass : L – LSP – Liskov Substitution Principle‬ها باید‬ ‫•‬

‫بتوانند جایگزین نوع پایهی خود باشند‪.‬‬


‫‪:‬کالینتتهتا نبتایتد‬ ‫‪I – ISP– Interface Segregation Principle‬‬ ‫اصتتل چهتارم‬ ‫•‬

‫وابسته به متدهایی باشند که آنها را پیادهسازی نمیکنند‪.‬‬

‫اصل پنجم ‪ : D – DIP– Dependency Inversion principle‬مهمترین اصل که‬ ‫•‬

‫میگوید ‪ :‬ماژولهای ستتطح باال نبتاید به ماژولهای ستتطح پایین وابستتتته‬


‫باشند‪ ،‬هر دو باید به ابسترکشن وابسته باشند‪.‬‬

‫قلب این اصاول‪ ،‬اصال ‪ D‬اسات که با اسات اد از ‪ DI‬لیاد ساازی میشاود‪ .‬همین م هوم باعث ایجاد‬
‫ک های ‪ Loosely Coupled‬میشاود‪ .‬ک های ‪ Loosely Coupled‬ک هایی هساتن که وابساتگی‬
‫این وابساااتگیها‪ ،‬لییر یک ماهول‪ ،‬مام برنامه را‬ ‫بین ماهولها را کمتر میکنن ‪ .‬با ک شا ا‬
‫وت اثیر قرار نمیده ‪.‬‬

‫‪39‬‬
‫فری ورک ‪ ASP.NET Core‬برای ماهوالر بود از بهترین شایو ی مهن سای نرمافوار‪ ،‬یعنی اصاول‬
‫‪ SOLID‬لیروی میکن ‪ .‬اصاول ‪ SOLID‬یکی از بهترین جربهها در طول دورا برنامهنویسای شای‬
‫گرا است‪.‬‬

‫مزایای ‪: DI‬‬

‫‪ DI‬به اللیکیشاان این امکا را میده ا به صااورت داینامیک با خودش لینک داشااته‬ ‫•‬

‫باشا ‪ DI Provider .‬به این صاورت عمل میکن که ریکوئسات را دریافت و ساپس با وجه‬
‫به این ریکوئست‪ ،‬کالس موردنظر را ایجاد مینمای ‪.‬‬

‫به طور مثال‪ :‬اگر یک کالس به کالس دیگری نیاز داشاااته باشااا ‪ DI ،‬آ را آماد‬
‫‪new‬‬ ‫می کن و دیگر نیازی نیسات که در هر کالس به صاورت دساتی این کالس را‬
‫کنی و ک های ‪ Tightly Coupled‬ایجاد نمایی ‪.‬‬

‫اسااات اد از ‪ ،DI‬اللیکیشااان را ‪ Loosely Coupled‬میکن ‪ Coupling .‬م هوم مهمی در‬ ‫•‬

‫است‪.‬‬ ‫برنامهنویسی شیگرا است‪ ،‬که در آ عملکرد یک کالس به کالس دیگر وابسته‬
‫شاما می وانی یک اینترفیس داشاته باشای که دو کالس آ را لیاد ساازی کرد باشان و‬
‫هر زما هر ک ام از کالسها را که نیاز داشته باشی ‪ ،‬با این اینترفیس ‪ Match‬کنی ‪ .‬این‬
‫روش در ‪ Unit Test‬بسایار کاربرد دارد‪( .‬شاما می وانی یک سارویس را با یک وره دیگر‬
‫آ جایگوین کنی ‪).‬‬
‫ما می وانی با اسات اد از ‪ ،DI‬تنظیمات پیشترفته داشاته باشای و با وجه به ریکوئسات‪،‬‬ ‫•‬

‫کالس موردنظر را برگردانی ‪.‬‬

‫مثال‪ :‬بخواهیا در برنااماه جااری خود یاک کاارت اعتبااری سااااختگی باه جاای کاارت‬
‫اعتباری واقعی داشته باشی و این موضو ‪ ،‬از طریق نظیمات م یریت شود‪.‬‬

‫اصل ‪ ،DI‬سیم کشی را به برنامه شما اضافه میکند‪ .‬شما با این اصل میتوانید‬
‫پیچیدگی را کمتر کنید‪.‬‬

‫‪40‬‬
‫ بنابراین نیاز به کالسای‬. ‫ وابساتگی خود را درخواسات میکن‬،Constructor ‫ معموال از طریق‬DI
‫ به این‬. ‫ فراه کن‬Constructor ‫اساات که م یریت کالسها و وابسااتگی بین آ ها را از طریق‬
.‫ گ ته میشود‬IoC Container ‫ یا به اصطالحی‬Container ‫کالس‬

. ‫را فراه میکن‬ ‫ درخواست ش‬Instance ‫ است که‬Factory ‫ یک‬IoC container ‫در اصل یک‬

Postman ‫ با‬Logger ‫تست سرویس‬


‫ را‬WeatherForecastController ‫ بنابراین‬. ‫که این سارویس را با ه سات کنی‬ ‫وقت آ رسای‬
. ‫باز و لییرات لایین را به آ اعمال کنی‬
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
private ILoggerManager _logger;
public WeatherForecastController(ILoggerManager logger)
{
_logger = logger;
}

[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInfo("Here is info message from our values
controller.");
_logger.LogDebug("Here is debug message from our values
controller.");
_logger.LogWarn("Here is warn message from our values
controller.");
_logger.LogError("Here is an error message from our values
controller.");

41
‫;} "‪return new string[] { "value1", "value2‬‬
‫}‬
‫}‬

‫}‬
‫برای بررسای نتیجه‪ ،‬میخواهی از یک ابوار فوق العاد به نام ‪ Postman‬اسات اد کنی ‪ .‬این ابوار‬
‫‪Response‬ها به ما خیلی کمک میکن ‪.‬‬ ‫در ارسال ریکوئست و نمای‬

‫حاال اللیکیشن را اجرا و در ‪ Postman‬آدرس لایین را وارد کنی ‪.‬‬

‫‪https://localhost:5001/weatherforecast‬‬

‫بع از اجرا‪ ،‬به مسایری که برای فایل ‪ nlog.config‬مشاخش کردی بروی ‪ .‬در این فول ر دو فایل‬
‫‪ internal_logs‬و ‪ logfile‬وجود دارد‪.‬‬

‫‪42‬‬
‫اگر فایلها را باز کنی نتیجه لایین را میبینی ‪.‬‬

‫این مام کاری بود که ما برای لیکربن ی ‪ Logger‬بای انجام میدادی ‪.‬‬

‫‪43‬‬
‫فصل سوم ‪ :‬دیتابیس و ‪Repository Pattern‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ ارتباط با دیتابیس‬
‫➢ پیادهسازی ‪ Repository Pattern‬در اپلیکیشن‬
‫➢ ‪ Seed Data‬چیست؟‬
‫مدل دیتابیس و ‪Repository Pattern‬‬
‫در این فصاال میخواهی نوو ایجاد دیتابیس با رویکرد ‪ ،Code First‬کار کرد با ‪،DbContext‬‬
‫سازی ‪Repository‬‬ ‫به دیتابیس) و لیاد‬ ‫نوو است اد از ‪( Migration‬برای انتقال م ل ایجاد ش‬
‫‪ Pattern‬را بیاموزی ‪.‬‬

‫‪ Data‬اسااات‪ .‬باه عباار ی می وا گ ات‪:‬‬ ‫‪Access‬‬ ‫‪ Repository‬واساااطی بین ‪ Domain‬و الیاهی‬


‫‪ ،Repository‬لترنی است که همهی ار باطات با دیتابیس را کپسوله میکن و باعث میشود ‪:‬‬

‫دسترسی به دادهها متمرکز شود‪.‬‬ ‫•‬

‫نگهداری کد سادهتر شود‪.‬‬ ‫•‬

‫دسترسی به دیتابیس از ‪ Domain Model‬جدا شود‪.‬‬ ‫•‬

‫در این لتر می وا رویکرد ‪ Loosely Coupled‬را جهت دسااترساای به داد های دیتابیس اجرا‬
‫کرد‪.‬‬

‫برای لیاد سازی این لتر ‪ ،‬بهتر است (براساس اصل ‪ )Separated Interface Pattern‬اینترفیس‬
‫را از ه ج ا کنی ا کالینت وابسته لیاد سازی نشود‪.‬‬ ‫سازی ‪Repository‬‬ ‫و لیاد‬

‫خب بیایی ابت ا با کالس م ل شرو کنی ‪.‬‬

‫ایجاد مدلها‬
‫‪Class Library (.NET‬‬ ‫میخواهی مثال فصاال دوم این کتاب را کامل کنی بنابراین یک لروه‬
‫)‪ Core‬ج ی با نام ‪ Entities‬ایجاد کنی ‪.‬‬

‫فراموش نکنی که در لروه اصلی بای از این لروه رفرنس بگیری ‪.‬‬

‫در این لروه فول ری با نام ‪ Models‬ایجاد کنی ا ‪Entity‬ها را درو آ قرار دهی ‪.‬‬

‫‪45‬‬
‫ کرد با ج اول‬Map ‫ از آ ها برای‬Entity Framework Core ،‫ها کالسهایی هساتن که‬Entitiy
. ‫ میشون‬Map ‫ با ستو های دیتابیس‬،Entity ‫های درو‬Property . ‫دیتابیس است اد میکن‬

. ‫ ایجاد کنی‬Company ‫ و‬Employee ‫ دو کالس با نامهای‬Models ‫در فول ر‬

: Employee ‫کالس‬

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Entities.Models
{
public class Employee
{
[Column("EmployeeId")]
public Guid Id { get; set; }

[Required(ErrorMessage = "Employee name is a required field.")]


[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30
characters.")]
public string Name { get; set; }

[Required(ErrorMessage = "Age is a required field.")]


public int Age { get; set; }

[Required(ErrorMessage = "Position is a required field.")]


[MaxLength(20, ErrorMessage = "Maximum length for the Position is
20 characters.")]
public string Position { get; set; }

[ForeignKey(nameof(Company))]
public Guid CompanyId { get; set; }

public Company Company { get; set; }

46
}

: Company ‫کالس‬
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Entities.Models
{
public class Company
{
[Column("CompanyId")]
public Guid Id { get; set; }

[Required(ErrorMessage = "Company name is a required field.")]


[MaxLength(60, ErrorMessage = "Maximum length for the Name is 60
characters.")]
public string Name { get; set; }

[Required(ErrorMessage = "Company address is a required field.")]


[MaxLength(60, ErrorMessage = "Maximum length for the Address is
60 characte")]
public string Address { get; set; }

public string Country { get; set; }

public ICollection<Employee> Employees { get; set; }


}

: ‫بررسی کد‬

47
‫‪Entity Framework Core‬‬ ‫ایاجاااد کاردیا ‪.‬‬ ‫‪Employee‬‬ ‫و‬ ‫‪Company‬‬ ‫مااا دو کاالس‬ ‫•‬

‫لرالر یهای این کالسها را برای ‪ Map‬کرد با ساتو های ج اول دیتابیس اسات اد می‬
‫‪.‬‬ ‫کن‬
‫(یاعانای ‪ )Employees‬و آخاریان لارالارای کاالس‬ ‫‪Company‬‬ ‫آخاریان لارالارای کاالس‬ ‫•‬

‫‪ Navigational‬هساااتن که ه فشاااا‬ ‫‪Properties‬‬ ‫‪( Employee‬یعنی ‪ )Company‬یک‬


‫برقراری ار باط بین م لهاست‪.‬‬
‫است‪.‬‬ ‫همانطور که میبینی ‪ ،‬چن ین ‪ Attribute‬در ‪Entity‬ها اضافه ش‬ ‫•‬

‫‪Map‬‬ ‫[‪ ]Column‬مشاااخش میکنا کاه ‪ Property‬باا یاک ناام دیگر در دیتاابیس‬ ‫▪‬

‫شود‪.‬‬
‫]‪ [Required‬ورود اطالعات برای لرالر ی را اجباری میکن ‪.‬‬ ‫▪‬

‫]‪ [MaxLength‬حا اکثر کااراکتری کاه لرالر ی باایا داشاااتاه بااشااا را مشاااخش‬ ‫▪‬

‫میکن ‪.‬‬

‫و‬ ‫‪Attribute‬‬ ‫بعا از اینکاه این ما ل باه دیتاابیس انتقاال یاافات خواهی دیا کاه چطور این‬
‫‪Navigational Property‬ها روی ستو ها اثیر میگذارن ‪.‬‬

‫ایجاد کالس ‪Context‬‬


‫برای ار باط با دیتابیس و است اد از ‪ Entity Framework Core‬بای یک کالس ایجاد کنی که‬
‫از کالس ‪ DbContext‬ارثبری کن ‪ DbContext .‬قلب ‪ EF Core‬است‪.‬‬

‫عری میشااود‪ T .‬درواقع ‪ Entity‬ما‬ ‫>‪DbSet<T‬‬ ‫در ‪ DbContext‬کالسهای شااما به صااورت‬


‫میباش ‪ Employee( .‬یا ‪)Company‬‬

‫‪ EF‬بتوان با ج اول دیتابیس‬ ‫‪Core‬‬ ‫هر ‪ DbSet‬به یک ج ول در دیتابیس ‪ Map‬خواه شا ا ا‬


‫ار باط برقرار کن ‪ .‬لس شما بای بابت هر ‪ Entity‬یک ‪ DbSet‬داشته باشی ‪.‬‬

‫قااباال از هاار کاااری لااکاایاا‬ ‫باارای ارثبااری از کااالس ‪ ،DBContext‬بااایاا‬


‫‪ Microsoft.EntityFrameworkCore‬را در لروه ‪ Entities‬اضافه کنی ‪.‬‬

‫‪48‬‬
Tools→NuGet Package Manager→Package ‫مستتیتر‬ ‫بترای انتجتام ایتن کتار وارد‬
.‫ شوید و دستور پایین را اجرا کنید‬Manager Console
Install-Package Microsoft.EntityFrameworkCore -Version 5.0.2 -ProjectName
Entities

‫یااک کااالس بااا نااام‬ Entities ‫بااع ا از نصااااب ایاان لااکاایا بااای ا در ریشااااه لااروه‬
. ‫ ایجاد کنی‬CompanyEmployeeDbContext
using Entities.Models;
using Microsoft.EntityFrameworkCore;

namespace Entities
{
public class CompanyEmployeeDbContext : DbContext
{
public CompanyEmployeeDbContext(DbContextOptions options):
base(options)
{
}

public DbSet<Company> Companies { get; set; }


public DbSet<Employee> Employees { get; set; }

‫ چیست؟‬ConnectionString
، ‫زمانیکه میخواهی اللیکیشاان ا را وسااعه دهی و در ماشااینهای مختل مسااتقر نمایی‬
.‫موضو مشخش کرد مکا دیتابیس مطرح میشود‬
49
‫نو دیتابیس با وجه به بیونس شاما عری خواه شا اما مکان دیتابیس بای روی سایسات‬
‫شما یا هر جایی در سرور دیتابیس قرار گیرد‪.‬‬

‫برای مثال‪:‬‬

‫در اللیکیشااانهاای وب‪ ،‬باهطور معمول موال دیتاابیس بر روی یاک ‪ Host‬قرار دارد (جاایی کاه‬
‫کاربرا واقعی به آ دساترسای داشاته باشان ) و درو ساخت افوار شاما نیسات‪ .‬بنابراین مکا و‬
‫نظیمات مختل دیتابیس معموال در یک ‪ Connection String‬ذخیر میشود‪.‬‬

‫‪ Connection String‬به فری ورک میگوی ‪ ،‬دیتابیس روی چه ساروری قرار دارد‪ .‬لس بهتر اسات‬
‫آ را درو فاایال ‪ appsettings.json‬قرار دهی اا بتوانی با و کاامپاایال مجا د‪ ،‬موال دیتاابیس‬
‫را در کامپیو رهای مختل مشخش کنی ‪.‬‬

‫‪ Connection‬خود را مشاااخش کنی ‪ .‬لس باه‬ ‫‪String‬‬ ‫خاب حااال بیااییا برای ار بااط باا دیتاابیس‬
‫لروه اصلی بروی و فایل ‪ appsettings.Json‬را مانن فایل زیر لییر دهی ‪.‬‬
‫{‬
‫{ ‪"ConnectionStrings":‬‬
‫‪"sqlConnection": "server=.; database=CompanyEmployee; Integrated‬‬
‫"‪Security=true‬‬
‫‪},‬‬

‫{ ‪"Logging":‬‬
‫{ ‪"LogLevel":‬‬
‫"‪"Default": "Warning‬‬
‫}‬
‫‪},‬‬

‫"*" ‪"AllowedHosts":‬‬
‫}‬

‫‪50‬‬
DI ‫ از طریق‬DbContext ‫رجیستر‬
‫ وجود‬DbContext ‫ یاک مرحلاه دیگر باه ناام رجیساااتر کرد‬،‫برای کمیال لیکر بنا ی دیتاابیس‬
‫ بای نو دیتابیس خود را‬،‫این ساارویس‬ ‫ اما قبل از رجیسااتر شاا‬.‫دارد که بای انجام شااود‬
. ‫مشخش کنی‬

‫ شااما می وانی با وجه به مهارت‬، ‫ از طی وساایعی از دیتابیسها لشااتیبانی میکن‬EF Core


: ‫خود یکی را انتخاب کنی‬
• PostgreSQL—Npgsql.EntityFrameworkCore.PostgreSQL
• Microsoft SQL Server—Microsoft.EntityFrameworkCore.SqlServer
• MySQL—MySql.Data.EntityFrameworkCore
• SQLite—Microsoft.EntityFrameworkCore.SQLite

‫اساااات ا اااد ک ان ا لااس لااکاای ا‬ SQL Server ‫ماان در ایاان کااتاااب ماایخااواه ا از‬
. ‫ را با دستور زیر نصب میکن‬Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.2 -
ProjectName CompanyEmployee.API

‫ را همااننا کالس‬CompanyEmployeeDbContext ‫ کالس‬،Startup ‫خاب حااال باایا در کالس‬


‫ را باز کنی و اکساتنشان‬ServiceExtensions ‫ بنابراین کالس‬. ‫ رجیساتر کنی‬LoggerManager
. ‫ را به آ اضافه کنی‬ConfigureSqlContext ‫مت‬
using Contracts.IServices;
using Entities;
using LoggerService;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;

51
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace CompanyEmployee.API.Infrastructure.Extensions
{
public static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection services)
=>
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});

public static void ConfigureIISIntegration(this


IServiceCollection services) =>
services.Configure<IISOptions>(options =>
{
});

public static void ConfigureLoggerService(this IServiceCollection


services) => services.AddScoped<ILoggerManager, LoggerManager>();

public static void ConfigureSqlContext(this IServiceCollection


services, IConfiguration configuration) =>
services.AddDbContext<CompanyEmployeeDbContext>(opts =>
opts.UseSqlServer(configuration.GetConnectionString("sqlConnectio
n")));

}
: ‫بررسی کد‬

Connection ‫وانساااتی مقا ار‬ GetConnectionString ‫در کا بااال باا اسااات ااد از متا‬ •

. ‫ بخوانی‬appsettings.json ‫ را از فایل‬String

52
‫از ورودی میگیرد و این رشاااتاه را باا ‪Key‬هاای درو فاایال‬ ‫‪string‬‬ ‫این متا یاک‬ ‫•‬

‫‪ appsettings.json‬مقایسه و در نهایت ‪ Value‬موردنظر را برمیگردان ‪.‬‬


‫اگر روی این مت کلی ‪ F12‬بونی ‪ ،‬به عری این اکساتنشان مت میروی و ک لایین را‬ ‫•‬

‫خواهی دی ‪.‬‬

‫باه متا‬ ‫‪Key‬‬ ‫باه عنوا‬ ‫‪Connection String‬‬ ‫هماانطور کاه در کا بااال میبینیا ‪ ،‬ناام‬ ‫•‬

‫و یک ‪ Connection String‬را با اساات اد از این نام‬ ‫‪ GetConnectionString‬داد شاا‬


‫برمیگردان ‪.‬‬
‫لس از انجام مراحل باال بای این اکستنشن مت را در مت ‪ ConfigureServices‬رجیستر‬ ‫•‬

‫کنی ‪.‬‬
‫;)‪services.ConfigureSqlContext(Configuration‬‬

‫‪ Migration‬چیست؟‬
‫بع از انجام مراحل باال‪ ،‬نوبت به ایجاد دیتابیسمیرس ‪ .‬اما دیتابیس چطور ایجاد میشود؟‬

‫یک روش خوب برای ایجاد دیتابیس‪ ،‬وادار کرد ‪ EF‬به ساااخت دیتابیس اساات‪ .‬ساااد رین‬
‫رویکرد ‪ EF‬برای انجام این کار‪ ،‬است اد از ‪ Migration‬است‪.‬‬

‫‪ Migration‬را حلی برای ما یریات جا اول در دیتاابیس میبااشااا ‪ .‬باا ‪ Migration‬می وانیا با و‬
‫هیچگونه دردسری‪ ،‬لییرات را به ج اول دیتابیس اعمال نمایی ‪.‬‬

‫قبل از ایجاد ‪ Migration‬دو کار بای انجام دهی ‪:‬‬

‫ر ‪Migration‬‬ ‫چو ما ل دیتاابیس را در لروه ی ‪ Entities‬ایجااد کردیا و میخواهی فولا‬ ‫•‬

‫در لروه اصلی باش ‪ ،‬لس بای مت ‪ ConfigureSqlContext‬را کمی لییر دهی ‪.‬‬

‫‪53‬‬
public static void ConfigureSqlContext(this IServiceCollection services,
IConfiguration configuration) =>
services.AddDbContext<CompanyEmployeeDbContext>(opts =>
opts.UseSqlServer(configuration.GetConnectionString("sqlConnection"), b =>
b.MigrationsAssembly("CompanyEmployee.API")));

. ‫ بای لکی لایین را نصب کنی‬Migration ‫قبل از اجرای دستور‬ •

Install-Package Microsoft.EntityFrameworkCore.Tools -Version 5.0.2 -


ProjectName CompanyEmployee.API

‫ را در‬Add-Migration ‫و ولی ساااختار دیتابیس بای دسااتور‬ Migration ‫خب حاال برای ایجاد‬
. ‫ اجرا کنی‬Package Manager Console
Add-Migration Init

‫ در این فول ر‬.‫اسات‬ ‫ اضاافه شا‬Solution ‫ به‬Migrations ‫ یک فول ر به نام‬،‫با اجرای این دساتور‬
.‫کالسی وجود دارد که ک ایجاد دیتابیس و ج اولهای ما در آ قرار گرفته است‬

Migration ‫ ساااه روش برای اعمال ک های‬. ‫با این فایلها می وانی دیتابیس خود را ایجاد کنی‬
: ‫به دیتابیس وجود دارد‬

54
‫‪ )1‬اپلیکیشتن شتما میتواند در طول اجرا شتدن ‪ ،Startup‬دیتابیس را چک و‬
‫‪ Migrate‬کند‪.‬‬
‫‪ )2‬میتوانید یک اپلیکیشن مستقل برای ‪ Migrate‬دیتابیس داشته باشید‪.‬‬
‫‪ )3‬میتوانید از دستورات ‪ SQL‬برای ‪ Update‬دیتابیس استفاده کنید‪.‬‬

‫‪Update-Database‬‬ ‫ساااد رین روش‪ ،‬گوینه سااوم اساات‪ .‬شااما می وانی ‪ ،‬نها با نوشااتن‬
‫در ‪ ،Package Manager Console‬این ک ها را به دیتابیس اعمال نمایی ‪.‬‬

‫نکته!!‬

‫این دستتور برای اعمال کد ‪ Migration‬به دیتابیستی استت که در ‪ DbContext‬اپلیکیشتن به آن اشتاره‬


‫شده بود‪ .‬اجرای این دستور در دفعات بعد تنها دیتابیس شما را آپدیت مینماید‪.‬‬

‫خب حاال بیایی دیتابیس را با ه ببینی ‪.‬‬

‫‪ Seed Data‬چیست؟‬
‫در بسااایاری از مواقع با اجرای اللیکیشااان‪ ،‬نیاز اسااات ا برخی از ج وال دیتابیس با اطالعات‬
‫لی فرضی مق اردهی اولیه شون ‪ .‬را حل این مسأله ‪ Seed Data‬است‪.‬‬

‫‪55‬‬
‫ اطالعاا ی را باه جا اولی از‬،‫ این امکاا را باه ماا می دها اا در حین اجرای برنااماه‬Seed Data

. ‫دیتابیس اضافه نمایی‬

: ‫ به اپلیکیشن‬Seed Data ‫برای اعمال قابلیت‬

‫ایجااد و در آ دو کالس باا‬ Configuration ‫ فولا ری باا ناام‬Entities ‫ در لروه‬: ‫مرحلته اول‬
. ‫ اضافه نمایی‬EmployeeConfiguration ‫ و‬CompanyConfiguration ‫نامهای‬

: CompanyConfiguration ‫کالس‬
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;

namespace Entities.Configuration
{
public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
public void Configure(EntityTypeBuilder<Company> builder)
{
builder.HasData
(
new Company
{
Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "Raveshmand_Ltd",
Address = "Tehran,Tajrish",
Country = "Iran"
},
new Company
{
Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Name = "Geeks_Ltd",
Address = "London",
Country = "English"
}
);
}
}

56
: EmployeeConfiguration ‫کالس‬
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;

namespace Entities.Configuration
{
public class EmployeeConfiguration :
IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.HasData
(
new Employee
{
Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
Name = "Zahra Bayat",
Age = 26,
Position = "Backend developer",
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-
2d54a9991870")
},
new Employee
{
Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
Name = "Ali Bayat",
Age = 30,
Position = "Backend developer",
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-
2d54a9991870")
},
new Employee
{
Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
Name = "Sara Bayat",
Age = 35,
Position = "Frontend developer",
CompanyId = new Guid("3d490a70-94ce-4d15-9494-
5248280c2ce3")
}
);
}
57
}
}
‫ را‬CompanyEmployeeDbContext ‫ باایا کالس‬،‫ برای فراخوانی این لیکربنا ی‬: ‫مرحلته دوم‬
. ‫لییر دهی‬
using Entities.Configuration;
using Entities.Models;
using Microsoft.EntityFrameworkCore;

namespace Entities
{
public class CompanyEmployeeDbContext : DbContext
{

public CompanyEmployeeDbContext(DbContextOptions options):


base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.ApplyConfiguration(new CompanyConfiguration());
modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
}

public DbSet<Company> Companies { get; set; }


public DbSet<Employee> Employees { get; set; }

}
. ‫ ایجاد کنی‬Migration ‫ کرد دیتا در دیتابیس یک‬Seed ‫ برای‬: ‫مرحله سوم‬
Add-Migration SeedData

Update-Database

58
.‫با این کار مام داد ها از فایلهای لیکربن ی به ج اول انتقال یافت‬

Repository Pattern ‫پیادهسازی‬


CRUD ‫اا برای داشاااتن متا هاای‬ ‫ زماا آ فرارسااایا‬،‫لس از برقراری ار بااط باا دیتاابیس‬
. ‫ جنریک ایجاد کنی‬Repository ‫یک‬

‫ ایجااد‬IRepositoryBase ‫ یاک اینترفیس باه ناام‬Contracts / IServices ‫ابتا ا در مسااایر‬ •

. ‫کنی‬
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Contracts.IServices
{
public interface IRepositoryBase<T> where T : class
{
IQueryable<T> FindAll(bool trackChanges);
IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression,
bool trackChanges);
void Create(T entity);
void Update(T entity);
void Delete(T entity);
}
}

‫ این لروه‬. ‫ داری‬Repository ‫ نیااز باه یاک لروه جا یا باا ناام‬،‫بعا از ایجااد این اینترفیس‬ •

Class ‫ لس یک لروه از نو‬. ‫ رفرنس داشته باش‬Entities ‫ و‬Contracts ‫بای از دو لروه‬


. ‫ ایجاد کنی‬Repository ‫ با نام‬Library (.NET Core)

59
!!‫نکته‬
.‫فراموش نکنید که پروژه اصلی باید از این پروژه رفرنس بگیرد‬

‫در لروه‬ Repositories ‫ یاک فولا ر باا ناام‬،IRepositoryBase ‫برای لیااد سااااازی‬ •

RepositoryBase ‫ با نام‬Abstract ‫ در این فول ر بای یک کالس‬. ‫ ایجاد کنی‬Repository


. ‫ را اضافه نمایی‬EF Core ‫داشته باشی ؛ اما قبل از ایجاد این کالس بای لکی‬
Install-Package Microsoft.EntityFrameworkCore -Version 5.0.2 -
ProjectName Repository

: RepositoryBase ‫کدهای کالس‬


using Contracts.IServices;
using Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

60
using System.Linq.Expressions;

namespace Repository.Repositories
{
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T
: class
{
protected CompanyEmployeeDbContext _companyEmployeeDbContext;
public RepositoryBase(CompanyEmployeeDbContext
companyEmployeeDbContext)
{
_companyEmployeeDbContext = companyEmployeeDbContext;
}
public IQueryable<T> FindAll(bool trackChanges) =>
!trackChanges ?
_companyEmployeeDbContext.Set<T>()
.AsNoTracking() :
_companyEmployeeDbContext.Set<T>();

public IQueryable<T> FindByCondition(Expression<Func<T, bool>>


expression,
bool trackChanges) =>
!trackChanges ?
_companyEmployeeDbContext.Set<T>()
.Where(expression)
.AsNoTracking() :
_companyEmployeeDbContext.Set<T>()
.Where(expression);

public void Create(T entity) =>


_companyEmployeeDbContext.Set<T>().Add(entity);

public void Update(T entity) =>


_companyEmployeeDbContext.Set<T>().Update(entity);

public void Delete(T entity) =>


_companyEmployeeDbContext.Set<T>().Remove(entity);
}

61
‫}‬
‫بررسی کد ‪:‬‬

‫کالس ‪ RepositoryBase‬و اینترفیس ‪ IRepositoryBase‬باا نو جنریاک ‪ T‬کاار میکننا ‪.‬‬ ‫•‬

‫کنی کاه نو فیلا هاا‪ ،‬لاارامترهاا و‬ ‫جنریاک باه ماا امکاا میدها اا کالسهاایی عری‬
‫مت هایشا در زما است اد عیین شون ‪.‬‬
‫را مایبایانای ا ‪ .‬از ایان لاااراماتار بارای باااال رفاتان‬ ‫‪trackChanges‬‬ ‫در ک ا باااال لاااراماتار‬ ‫•‬

‫‪ Performance‬کوئریها است اد کردی ‪ .‬وقتی که ما ‪ AsNoTracking‬است اد کنی ‪ ،‬این‬


‫‪ EF‬اطال میدها کاه نیااز باه‬ ‫‪Core‬‬ ‫نظی میشاااود و کوئری باه‬ ‫‪false‬‬ ‫لاارامتر بر روی‬
‫سرعت یک کوئری میشود‪.‬‬ ‫ردیابی لییرات ‪Entitiy‬ها نیست‪ .‬این باعث افوای‬

‫پیادهسازی کالسهای ‪Repository‬‬


‫حاال نوبت ایجاد کالسهایی است که میخواهن از کالس ‪ RepositoryBase‬ارثبری کنن ‪.‬‬

‫قبال از ایجااد این کالسهاا باایا با انیا ‪ ،‬کاه هر کالس می وانا عالو بر ارثبری از کالس‬
‫‪ ،RepositoryBase‬یک اینترفیس مخصاوا به خود نیو داشاته باشا ‪ .‬بنابراین ما بای منطقی را‬
‫که برای همه ریپازیتوریهایما یکی است را از ریپازیتوری کالسهای کاربر ج ا کنی ‪.‬‬

‫(فولا ر ‪ )IServices‬دو اینترفیس باا ناامهاای‬ ‫‪Contracts‬‬ ‫خاب باا این اوصاااااف در لروه‬
‫‪ ICompanyRepository‬و ‪ IEmployeeRepository‬ایجاد کنی ‪.‬‬

‫اینترفیس ‪: ICompanyRepository‬‬
‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface ICompanyRepository‬‬
‫{‬
‫}‬
‫}‬

‫اینترفیس ‪: IEmployeeRepository‬‬
‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface IEmployeeRepository‬‬
‫{‬
‫}‬

‫‪62‬‬
}

.‫ است‬Repositories ‫ در فول ر‬Repository ‫حاال نوبت ایجاد کالسهای‬

: CompanyRepository ‫کدهای کالس‬


using Contracts.IServices;
using Entities;
using Entities.Models;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}
}

}
: EmployeeRepository ‫کدهای کالس‬
using Contracts.IServices;
using Entities;
using Entities.Models;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}
}

63
‫}‬
‫کمیل میشاود اما هنوز کارهای دیگری ه هسات که در‬ ‫‪Repository‬‬ ‫لس از این مرحله‪ ،‬ایجاد‬
‫ادامه با ه انجام میدهی ‪.‬‬

‫ایجاد یک ‪Repository Manager‬‬


‫همانطور که میدانی خروجی برخی از ‪API‬ها در اللیکیشااان‪ ،‬شاااامل رکیب داد های چن ین‬
‫‪ Resource‬است‪ .‬برای مثال ‪:‬‬

‫مام شرکتهایی است که کارمن ا باالی ‪ 30‬سال دارن ‪.‬‬ ‫‪API‬ی که خروجی آ‬

‫در چنین حاالتی باایا از هر دو ریپاازیتوری ‪ Instance‬داشاااتاه بااشااای و داد هاای خود را از‬
‫‪Resource‬ها ب ست آوری ‪.‬‬

‫شاای با دو کالس‪ ،‬مشاکل چن انی با این مسالله وجود ن اشاته باشا اما اگر منطق ما نیاز به‬
‫میشود‪.‬‬ ‫رکیب ‪ 5‬یا ‪ 6‬کالس داشته باش مطملنا این موضو خیلی لیچی‬

‫را حل این مشاااکل‪ ،‬یک کالس ‪ RepositoryManager‬اسااات که درو آ از مام کالسهای‬


‫‪ Repository‬یک ‪ Instance‬وجود داشته باش ‪.‬‬

‫این کالس دو قابلیت به اپلیکیشن ما اضافه میکند ‪:‬‬

‫‪ .1‬به هر ‪ Repository‬که نیاز داشته باشی به راحتی دسترسی داری ‪.‬‬


‫را در ‪ RepositoryBase‬داشاتی اما ا زمانی که‬ ‫‪Delete‬‬ ‫‪ .2‬ما مت های ‪ Create, Update‬و‬
‫مت ‪ SaveChanges‬صا ا زد نشاود این مت ها هیچ کاری در دیتابیس انجام نمیدهن ‪.‬‬
‫کالس ‪ RepositoryManager‬این کار را به خوبی هن ل میکن ‪.‬‬

‫خب برای اینکه به این قابلیت برسااای بای اینترفیس ج ی ی با نام ‪ IRepositoryManager‬در‬
‫لروه ‪( Contract‬فول ر ‪ )IServices‬ایجاد کنی ‪.‬‬
‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface IRepositoryManager‬‬
‫{‬
‫} ;‪ICompanyRepository Company { get‬‬
‫} ;‪IEmployeeRepository Employee { get‬‬

‫‪64‬‬
void Save();
}

}
RepositoryManager ‫) یک کالس ج ی با نام‬Repositories ‫ (فول ر‬Repository ‫حاال در لروه‬
. ‫ایجاد کنی‬
using Contracts.IServices;
using Entities;

namespace Repository.Repositories
{
public class RepositoryManager : IRepositoryManager
{
private CompanyEmployeeDbContext _repositoryContext;
private ICompanyRepository _companyRepository;
private IEmployeeRepository _employeeRepository;

public RepositoryManager(CompanyEmployeeDbContext
repositoryContext)
{
_repositoryContext = repositoryContext;
}
public ICompanyRepository Company
{
get
{
if (_companyRepository == null)
_companyRepository = new
CompanyRepository(_repositoryContext);

return _companyRepository;
}
}

public IEmployeeRepository Employee


{
get
{
if (_employeeRepository == null)

65
‫‪_employeeRepository = new‬‬
‫;)‪EmployeeRepository(_repositoryContext‬‬
‫;‪return _employeeRepository‬‬
‫}‬
‫}‬

‫;)(‪public void Save() => _repositoryContext.SaveChanges‬‬


‫}‬

‫}‬
‫بررسی کد ‪:‬‬

‫همانطور که میبین ما ‪Property‬هایی ایجاد کردی که ‪Repository‬های ما را در معرض‬ ‫•‬

‫دی قرار میدهن ‪.‬‬


‫همچنین برای اینکه بتوانی بع از ا مام لییرات در یک شااای‪ ،‬عمل ذخیر ساااازی را‬ ‫•‬

‫داشته باشی ؛ مت ‪ Save‬را در اینجا قرار دادی ‪.‬‬

‫این روش خوبی اساات زیرا می وانی چن کار را در یک اکشاان انجام دهی ‪ .‬به عنوان‬
‫مثال‪:‬‬

‫دو شارکت اضاافه کنی ساپس دو کارمن لییر دهی و یک شارکت حذف کنی و در‬
‫ماام لییرات اعماال شاااود‪ .‬در این صاااورت یاا هماه لییرات انجاام‬ ‫‪Save‬‬ ‫لاایاا باا یاک‬
‫میشود یا هیچ لییری اعمال نمیشود‪.‬‬

‫بع از این لییرات بای این کالس رجیساااتر شاااود ا بتوا این کالس را در کنترلرهایما (یا‬
‫داخل کالسهایی که منطق بیوینس ما را شکیل میدهن ) ‪ Inject‬کنی ‪.‬‬

‫این کالس‪ ،‬مت لایین را در کالس ‪ ServiceExtensions‬اضافه کنی ‪.‬‬ ‫خب برای رجیستر ش‬
‫;‪using Contracts.IServices‬‬
‫;‪using Entities‬‬
‫;‪using LoggerService‬‬
‫;‪using Microsoft.AspNetCore.Builder‬‬
‫;‪using Microsoft.EntityFrameworkCore‬‬
‫;‪using Microsoft.Extensions.Configuration‬‬
‫;‪using Microsoft.Extensions.DependencyInjection‬‬

‫‪66‬‬
using Repository.Repositories;

namespace CompanyEmployee.API.Infrastructure.Extensions
{
public static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection
services) =>
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});

public static void ConfigureIISIntegration(this IServiceCollection


services) =>
services.Configure<IISOptions>(options =>
{
});

public static void ConfigureLoggerService(this IServiceCollection


services) => services.AddScoped<ILoggerManager, LoggerManager>();

public static void ConfigureSqlContext(this IServiceCollection


services, IConfiguration configuration) =>
services.AddDbContext<CompanyEmployeeDbContext>(opts =>
opts.UseSqlServer(configuration.GetConnectionString("sqlConnectio
n")));

public static void ConfigureRepositoryManager(this


IServiceCollection services) =>
services.AddScoped<IRepositoryManager, RepositoryManager>();

}
‫خط کا‬ services.AddController ‫در بااالی‬ Startup ‫کالس‬ ConfigureServices ‫و در متا‬
. ‫لایین را اضافه کنی‬
67
services.ConfigureRepositoryManager();

‫ مام کاری که بای انجام دهی این اساات که ساارویس‬،Repository ‫حاال برای اساات اد از این‬
. ‫ کنی‬Inject ‫ را در کنترلر‬RepositoryManager
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
private ILoggerManager _logger;
private readonly IRepositoryManager _repository;

public WeatherForecastController(ILoggerManager
logger,IRepositoryManager repository)
{
_logger = logger;
_repository = repository;
}

[HttpGet]
public IEnumerable<string> Get()
{
// _repository.Company.AnyMethodFromCompanyRepository();
// _repository.Employee.AnyMethodFromEmployeeRepository();

_logger.LogInfo("Here is info message from our values


controller.");
_logger.LogDebug("Here is debug message from our values
controller.");
_logger.LogWarn("Here is warn message from our values
controller.");
_logger.LogError("Here is an error message from our values
controller.");
return new string[] { "value1", "value2" };
}
}

68
‫}‬
‫بررسی کد ‪:‬‬

‫کاردیا ‪ .‬ایان روش در‬ ‫‪Inject‬‬ ‫را در کاناتارلار‬ ‫‪Repository‬‬ ‫هاماااناطاور کااه مایبایانای ا مااا‬ ‫•‬

‫اللیکیشاانهای کوچک خوب اساات اما برای اللیکیشاانهایی با مقیاس بورگتر بای یک‬
‫الیااه بایاویاناس بایان کاناتارلارهااا و ماناطاق ریاپااازیاتاوری ایاجاااد کانایا و سااارویاس‬
‫‪ RepositoryManager‬درو الیه بیونس ‪ Inject‬شااود‪ .‬با این کار کنترلر ساابک و آزاد از‬
‫منطق ریپازیتوری میشود و همچنین ک ما قابل نگه اری و قابل است اد مج د خواه‬
‫ش ‪.‬‬

‫‪69‬‬
‫فصل چهارم ‪ :‬مسیریابی و ‪REST‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ کنترلرها و مسیریابی در ‪Web API‬‬

‫➢ قوانین نامگذاری مسیر اکشن متد‬


‫➢ ‪ REST‬چیست؟‬
‫➢ فرمت بازگشتی ‪REST‬‬
‫➢ ‪ HTTP Method‬چیست؟‬
‫‪HTTP‬‬ ‫ه ف همهی ما اضاااافه کرد منطق بیوینسااای به اللیکیشااان اسااات اما از آنجاییکه‬
‫‪ Web‬باازی میکننا بیااییا کمی درباار کالسهاای کنترلر و‬ ‫‪API‬‬ ‫مهمی در‬ ‫‪Request‬هاا نق‬
‫مسیریابی صوبت کنی ‪.‬‬

‫کنترلرها و مسیریابی در ‪Web API‬‬


‫کنترلر نقطهی عامل کاربر با اللیکیشن است که‪:‬‬

‫ریکوئست را دریافت میکند‪.‬‬ ‫•‬

‫بستتته به ماهیت ریکوئستتت دادههای درخواستتت شتتده را از مدل میگیرد یا‬ ‫•‬

‫دادههای مدل را آپدیت میکند‪.‬‬


‫در پایان ریسپانسی را به کالینت برمیگرداند‪.‬‬ ‫•‬

‫راساااات کلیاک و ساااپس‬ ‫‪Controllers‬‬ ‫برای ایجااد کنترلر‪ ،‬در لروه اصااالی بر روی فولا ر‬
‫‪ Add→Controller‬را انتخاب نمایی ‪.‬‬

‫حاال کالس ‪ API Controller-Empty‬را انتخاب و بر روی ‪ Add‬کلیک کنی ‪.‬‬

‫در کادر بع نام آ را ‪ CompaniesController‬بگذاری ‪.‬‬

‫‪71‬‬
. ‫کنترلر ما بای شبیه ک لایین باش‬
using Microsoft.AspNetCore.Mvc;

namespace CompanyEmployee.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CompaniesController : ControllerBase
{
}
}
: ‫بررسی کد‬

‫ارثبری‬ ControllerBase ‫ از کالس‬،‫باااال‬ Controller ‫همااانطور کااه میبینی ا کالس‬ •

‫را فراه‬ CompaniesController ‫ این کالس مام مت های ضروری برای کالس‬. ‫میکن‬
. ‫میکن‬
. ‫ را میبینی‬Route ‫در ک باال شما ا ربیوت‬ •

[Route("api/[controller]")]

72
‫‪Web‬‬ ‫این‪ ،‬ا ربیوت مسایریابی اسات که ریکوئساتهای ورودی را به اکشان مت های درو‬
‫‪ API Controller‬میرسااان ‪ .‬به موض ارسااال یک ‪ HTTP Request‬به اللیکشاان‪ ،‬فری‬
‫ورک ‪ MVC‬آ ریکوئساات را جویه و سااپس الش میکن ا آ را با یک اکشاان در‬
‫کنترلر مطابقت ده ‪.‬‬

‫دو روش برای تعریف مسیریابی وجود دارد ‪:‬‬

‫بر ‪Convention‬‬ ‫‪ )1‬مسیریابی مبتنی‬


‫بر ‪Attribute‬‬ ‫‪ )2‬مسیریابی مبتنی‬

‫مسیریابی مبتنی بر ‪ Convention‬قراردادهایی را برای مسیرهای ‪ URL‬ایجاد میکن ‪.‬‬

‫هنگامی که یک ‪ URL‬صاا ا زد میشااود‪ ،‬مو ور مساایریابی الش میکن ا متن ‪ URL‬را (در‬
‫در وب اللیکیشن مطابقت ده ‪.‬‬ ‫عری ش‬ ‫‪Controller‬‬ ‫مکا }‪ ){controller‬با یک‬

‫داد خواه شا ‪ .‬در غیر این‬ ‫اگر مو ور مسایریابی نتوان چیوی برای طبیق بیاب ‪ ،‬خطا نمای‬
‫صورت نتیجه مسیریابی‪ ،‬یک اکشنمت و ‪ Controller‬مر بط با آ خواه بود‪.‬‬

‫برش اول با نام کنترلر مپ میشود‪.‬‬ ‫•‬

‫برش دوم با نام اکشن مپ میشود‪.‬‬ ‫•‬

‫میکند که وارد کردن آن‬ ‫برش ستوم یک پارامتر اختیاری به نام ‪ id‬را مشتر‬ ‫•‬

‫اجباری نیستت اما در صتورت وجود‪ ،‬مستیریاب مقداری برای پارامتر ‪ id‬ضتبط‬
‫میکند‪.‬‬

‫ما می وانی این نو مسیریابی را در مت ‪ Configure‬کالس ‪ Startup‬ببینی ‪.‬‬

‫‪73‬‬
‫نکته!!‬

‫پارامترهای }‪ {controller‬و}‪ {action‬اجباری هستتتند و شتتما نمیتوانید یک پارامتر اختیاری را‬


‫قبل از یک پارامتر اجباری قرار دهید‪ ،‬زیرا مسیریاب از مقادیر اجباری جهت تصمیمگیری استفاده‬
‫میکند و هیچ راهی برای تعیین پارامتر اجباری وجود ندارد‪.‬‬

‫در روش بااال باایا ‪Controller‬هاا و اکشااانمتا هاای خود را عری کنیا اا ریکوئسااات دریاافتی‬
‫‪AttributeRouting‬‬ ‫مطابق با قرارداد حرکت و باالخر مستتیریابی موردنظر خود را بیاب ‪ .‬اما در‬
‫بای برای مپ کرد مسااایرها عبارت [‪ ]Route‬را بر روی کنترلرها یا اکشااانمت های خود قرار‬
‫دهی ‪.‬‬

‫‪Web API‬‬ ‫این روش‪ ،‬انعطتافپتذیری بیشاااتری نسااابات باه روش قبال دارد و برای زماانیکاه از‬
‫است اد میکنی بسیار کارآم است‪.‬‬

‫هماانطور کاه بااال ر دیا یا ‪ ،‬معموال مسااایر لاایاه را بااالی کالس کنترلر میگاذاری بناابراین برای‬
‫اکشن مت های خاا ه ‪ ،‬بای مسیرها را درست باالی آ ها قرار دهی ‪.‬‬

‫‪ Web‬کاار میکنیا ‪ ،‬لیشااانهااد ی ‪ ASP.NET Core‬این اسااات کاه از‬ ‫‪API‬‬ ‫زماانیکاه باا لروه‬
‫مسیریابی مبتنی بر ‪ Attribute‬است اد کنی ‪.‬‬

‫‪ REST‬چیست؟‬
‫‪HTTP‬‬ ‫‪ REST‬یک سابک معماری اسات که ار باط کامپیو رهای را دور را با اسات اد از لرو کل‬
‫برقرار میکن ‪.‬‬
‫‪74‬‬
‫اینترنت پر از نظرات این چنینی استت اما ‪ REST‬دقیقا چیستت و چه مشتکلی را حل‬
‫میکند؟‬

‫قبل از بررسی ‪ ،REST‬بیایی برخی صورات غلط را لاک کنی ‪.‬‬

‫اگر ‪ API‬از ‪Http Method‬هایی مثل ‪ GET‬یا ‪ POST‬است اد کرد‪ ،‬دلیل بر ‪ Rest‬بود‬ ‫•‬

‫آ نیست‪.‬‬
‫اگر یک ‪ API‬خروجی ‪ JSON‬برگردان ‪ ،‬دلیل بر ‪ REST‬بود آ نیست زیرا مت های‬ ‫•‬

‫غیر ‪ Rest‬ه می وانن ‪ JSON‬برگردانن ‪.‬‬


‫اگر یک ‪ API‬عملیات ‪ CRUD‬را لشتیبانی کرد‪ ،‬باز ه دلیل بر ‪ REST‬بود آ نیست‪.‬‬ ‫•‬

‫اما ‪ Restful API‬دقیقا باید چه ویژگی داشته باشد؟‬

‫‪Restful API .1‬ها باید از ‪HTTP method‬ها است اد کنن ‪.‬‬


‫‪ .2‬می وانن عملیات ‪ CURD‬را م ل نماین ‪.‬‬
‫‪ .3‬می وانن ‪ JSON‬ه برگردانن ‪.‬‬

‫اما ‪ REST‬چیست؟‬

‫‪ REST‬م ل کرد ‪Resource1‬هاست‪.‬‬

‫‪Status‬‬ ‫انجاام عملیاا ی بر روی ‪ Resource‬را‪ ،‬برای کالینات فراه و در نهاایات یاک‬ ‫‪REST‬‬

‫" ‪Representational State‬‬ ‫‪ Code‬باه کااربر برمیگردانا ‪ .‬بناابراین از همین جااسااات کاه مخ‬
‫‪ "Transfer‬میای ‪.‬‬

‫به طور مثال‪:‬‬

‫شما در یک ‪ RESTful API‬یک ‪ User‬اضافه میکنی و این ‪ API‬یک ک ‪ 201‬برمیگردان ‪.‬‬

‫‪ Resource 1‬آبجکتهایی هستن که در طراحی ‪ API‬است اد میکنی ‪ .‬مثل‪ User, Order :‬و‪...‬‬

‫‪75‬‬
‫‪ HTTP Method‬چیست؟‬
‫‪HTTP Method‬ها افعالی هسااتن که عملیات ‪Resource‬ها را مشااخش میکنن ‪ .‬احتماالً شااما‬
‫و احتماال با این افعال کار کرد باشاای ‪ .‬بیایی نگاهی‬ ‫‪ HTTP GET‬و ‪ HTTP POST‬را شاانی‬
‫دقیق ر به این افعال بین ازی ‪.‬‬

‫‪ GET : GET‬رای رین و سااد رین ‪HTTP Verb‬ی اسات که می وانی با اسات اد از آ‬ ‫•‬

‫‪ Resource‬موردنظر ا را واکشای کنی ‪ .‬این ‪ Verb‬به صاورت ‪ Read Only‬اسات و نبای‬


‫هیچ گونه ‪ State‬اللیکیشن را لییر ده ‪ .‬به طور مثال‪:‬‬
‫یک کالینت نمی وان با است اد از ‪ Get‬یک ‪ Resource‬را حذف یا آل یت نمای ‪.‬‬
‫است اد میشود‪ .‬اگر‬ ‫‪Resource‬‬ ‫برای ایجاد یا آل یت بخشی از‬ ‫‪POST‬‬ ‫‪:‬‬ ‫‪POST‬‬ ‫•‬

‫را آل یت میکن وگرنه یک‬ ‫‪Resource‬‬ ‫یک‬ ‫‪Verb‬‬ ‫باش این‬ ‫‪Id‬‬ ‫دارای‬ ‫‪Resource‬‬

‫‪ Resource‬ج ی را ایجاد خواه کرد‪.‬‬


‫‪ PUT : PUT‬ه مانن ‪ ،POST‬برای ایجاد یا آل یت ‪ Resource‬است اد میشود‪.‬‬ ‫•‬

‫اینجا یک ستوال ههن ما را درگیر میکند‪ .‬برای ایجاد یا آپدیت ‪ Resource‬باید‬


‫از ‪ POST‬استفاده کرد یا از ‪ PUT‬؟‬
‫برای لاسخ به این سوال بای معنی ‪ Idempotent‬بود مت را ب انی ‪.‬‬
‫‪ Idempotent‬چیست؟‬
‫‪ Idempotent‬یعنی اگر متا هر چنا باار ه صااا ا زد شاااود‪ ،‬نهاا یاک ااثیر بر روی‬
‫‪Side‬ی ن اشاااته باشا ا ‪.‬‬ ‫‪Effect‬‬ ‫‪ Resource‬بگذارد‪ .‬یا به عبار ی اجرای مت هیچ گونه‬
‫برای مثال ‪:‬‬
‫اگر یک مت ‪ Get‬را‪ ،‬صاا بار ه صاا ا بونی ؛ نها یک نتیجه را میگیری و خروجیها‬
‫هیچ فرقی با ه ن ارن ‪ .‬بنابراین مت ‪ Get‬به عنوا ‪ Idempotent‬شناخته میشود‪.‬‬
‫و ‪ POST‬چیست؟‬ ‫‪PUT‬‬ ‫حاال فر‬
‫ریکوئسااات ‪ Idempotent - POST‬نیسااات‪ .‬یعنی اگر برای ایجااد یاک ‪ User‬د باار‬
‫ریکوئست ‪ POST‬ارسال کنی ‪ ،‬د ‪ User‬ج ی اضافه خواه ش ‪.‬‬
‫‪User‬‬ ‫درحالیکه ریکوئساتهای ‪ Idempotent – PUT‬هساتن ‪ .‬یعنی اگر برای ایجاد یک‬
‫ارساال کنی و ساپس ‪ 9‬بار دیگر این ریکوئسات را کرار کنی ‪ ،‬این‬ ‫‪PUT‬‬ ‫یک ریکوئسات‬
‫‪76‬‬
‫ریکوئست نها یک اثیر را خواه داشت‪ ،‬چو در بار اول یک ‪ User‬ج ی اضافه خواه‬
‫ش و ‪ 9‬بار دیگر آ ‪ User‬آل یت میشود‪.‬‬
‫حرکت‬ ‫‪PUT‬‬ ‫همین مسالله باعث میشاود ما برای ایجاد یا آل یت ‪ Resource‬به سامت‬
‫کنی ‪ PUT .‬برخالف ‪ Idempotent – POST‬اسااات و باا اجرای مجا د یاک درخواسااات‬
‫ناموفق‪ ،‬یک ‪ Resource‬ج ی ایجاد نمیشود بلکه یک آل یت کامل انجام خواه ش ‪.‬‬
‫‪ PATCH :PATCH‬برای آل یت قساامتی از ‪ Resource‬اساات اد میشااود‪ PATCH .‬نیو‬ ‫•‬

‫‪ Idempotent‬نیسات زیرا اگر هموما چن ین کالینت با ه شارو به آل یت قسامتی از‬


‫‪ Resource‬کنن و آل یت هرک ام از آ ها مت اوت باشا ‪ ،‬کالینت و سارور از ‪ Sync‬بود‬
‫خارج میشون ‪ .‬به طور مثال ‪:‬‬

‫میکننا ‪ .‬کالینات اول‬ ‫فرض کنیا دو کالینات‪ ،‬هموماا اطالعاات یاک کاارمنا را ویرای‬
‫نام کارمن را لییر میده و کالینت دوم دلار ما کارمن را عوض میکن ‪.‬‬

‫حااال بعا از آلا یات‪ ،‬این دو کالینات اطالعاا ی کاه دریاافات میکننا باا اطالعاات اولیاه آ هاا‬
‫مت اوت خواه بود و کالینتها نمیدانن وضعیت واقعی ‪ ،Resource‬در سرور چیست‪.‬‬

‫این موضاو دقیقا اوت بین ‪ PATCH‬و ‪ PUT‬اسات‪ .‬در ‪ PUT‬اطالعات به صاورت کامل‬
‫آلا یات میشاااود و کالینات دقیقاا میدانا کاه اطالعاات این ‪ Resource‬در سااارور باه چاه‬
‫صورت است اما در ‪ Patch‬این طور نیست‪.‬‬

‫‪DELETE‬‬ ‫‪ ،DELETE : DELETE‬یک ‪ Resource‬را با است اد از یک ‪ ID‬حذف میکن ‪.‬‬ ‫•‬

‫ه ‪ Idempotent‬است زیرا هیچ گونه ‪Side Effect‬ی ن ارد‪.‬‬

‫مقایسه بین ‪PUT - POST - PATCH‬‬


‫از نظر فنی‪ POST ،‬برای آل یت کامل و یا ناقش‪ PUT ،‬برای آل یت کامل و‬ ‫•‬

‫‪ PATCH‬برای آل یت ناقش مورد است اد قرار میگیرد‪.‬‬


‫‪ POST‬و ‪ PATTCH‬هر دو ‪ Idempotent‬نیستن اما ‪ Idempotent-PUT‬است‪.‬‬ ‫•‬

‫را برای کالینت ارسال‬ ‫‪Response Body‬‬ ‫می وان‬ ‫‪POST‬‬ ‫در ‪ ،RESTful API‬فقط‬ ‫•‬

‫کن ‪ PUT .‬و ‪ PATCH‬فقط می وانن ه رهای ‪ Created 201‬یا ‪ No Content 204‬را‬
‫برگردانن ‪.‬‬
‫‪77‬‬
‫تفاوت بین این سه متد باعث ایجاد دو دیدگاه شده است ‪:‬‬

‫‪ .1‬دیتدگتاه اول استتتفتاده از هر ستته ‪ : Verb‬برای ایجااد از ‪ ،POST‬برای آلا یات‬


‫است اد کنی ‪.‬‬ ‫‪PATCH‬‬ ‫کامل از‪ PUT‬و برای آل یت ناقش از‬
‫این دی گا به عری ‪ HTTP‬نودیک ر اسااات اما یک نکته من ی دارد و آ این‬
‫اساات که بای مساایرهای زیادی در برنامه قرار دهی و واقعا در ‪API‬ها نیازی به‬
‫مایو بین آل یت کامل و ناقش نیست‪.‬‬
‫‪ .2‬دیدگاه دوم ستاده نگه داشتتن برنامه استت‪ :‬در این دی گا ‪ ،‬ه برای آل یت‬
‫و ه برای ایجاد ‪ Resource‬از ‪ POST‬است اد میشود‪ .‬این دی گا برای کالینت‬
‫ساااد ر اساات چو کالینت نیازی به صاامی گیری بین آل یت ناقش و کامل‬
‫ن ارد‪.‬‬

‫فرمت بازگشتی ‪REST‬‬


‫یاک ‪ RESTful API‬می وانا ‪ JSON ،HTML ،XML‬یاا هرچیوی را باه طور کاامال برگردانا ‪ .‬اماا‬
‫‪JSON‬‬ ‫‪ JSON‬یک فرمت موبوب برای ‪ Web API‬اسات‪ .‬بیایی نگاهی دقیق ر به موایا و معایب‬
‫بین ازی و در مورد چگونگی ایجاد یک ‪ RESTful API JSON‬صوبت کنی ‪.‬‬

‫یکی از بزرگترین دالیلی که باعث شد ‪ JSON‬نسبت به ‪ XML‬محبوبیت بیشتری‬ ‫•‬

‫بیابد این است که‪ ،‬خواندن آن برای انسان راحتتر است‪.‬‬


‫‪XML parsing‬‬ ‫خواندن ‪ JSON‬برای ماشین هم سادهتر است زیرا نیازی به‬ ‫•‬

‫‪ engine‬ندارد و خواندن و نوشتن ‪ JSON‬تقریباً در هر سیستم عاملی آسان‬


‫است‪.‬‬
‫برای تعریف ‪ JSON‬هیچ ‪ Schema‬وجود ندارد بنابراین سادهتر از ‪ XML‬است و‬ ‫•‬

‫راحتتر میتوان آن را تغییر داد‪ .‬البته همین مورد میتواند به عنوان یک عیب‬
‫هم تلقی شود چون هیچ ساختاری برای کالینت وجود ندارد‪.‬‬

‫قوانین نامگذاری مسیر اکشن متد‬


‫اسامی که در ‪ URI‬اسات اد میشاود نمایانگر ‪ Resource‬اسات و به کاربر کمک میکن ا ب هم‬
‫با چه ‪Resource‬ی کار میکن ‪.‬‬
‫‪78‬‬
‫نام ‪ Resource‬در ‪ ،URI‬همیشاه بای یک اسا باشا ‪ .‬این ب ا معناسات که به طور مثال‪ ،‬اگر‬
‫میخواهی اکشااان متا ی را‪ ،‬برای ب سااات آورد اطالعات شااارکتها ایجااد کنی بای آ را‬
‫‪ Companies‬بگذاری و از گذاشتن نام ‪ GetCompanies‬اجتناب کنی ‪.‬‬

‫مه دیگری که بای به آ وجه کنی ‪ ،‬ساالسااله مرا ب بین ‪Resource‬هاساات‪ .‬در این‬ ‫بخ‬
‫اللیکیشان‪ Company ،‬به عنوا ‪ Entity‬اصالی و ‪ Employee‬به عنوا یک ‪ Entity‬وابساته اسات‪.‬‬
‫وقتی میخواهی یک مسااایر برای ‪ Entity‬وابساااتاه ایجااد کنی ‪ ،‬بای قرارداد را کمی مت ااوت ر‬
‫دنبال کنی ‪.‬‬
‫‪/api/principalResource/{principalId}/dependentResource.‬‬

‫به طور مثال‪:‬‬

‫از آنجا که هیچ کارمن ی نمی وان ب و شاارکت باش ا لس مساایر برای ‪ Resource‬کارمن ا‬
‫بای به صورت لایین نوشته شود‪.‬‬

‫‪api/companies/{companyId}/employees/‬‬

‫با وجه به این وضیوات بیایی با ریکوئست ‪ Get‬شرو کنی ‪.‬‬

‫گرفتن اطالعات شرکتها از دیتابیس‬


‫اولین کااری کاه میخواهی انجاام دهی لییر مسااایر از ])"]‪ [Route("api/[controller‬باه‬
‫اسات‪ .‬با اینکه مسایر اول خیلی خوب کار میکن اما با مثال‬ ‫])"]‪[Route("api/[companies‬‬

‫‪CompaniesController‬‬ ‫دوم‪ ،‬به صاورت صاریح مشاخش میکنی که مسایریاب بای به کالس‬
‫اشار کن ‪.‬‬

‫اطالعات مام شرکتها اولین اکشنمت را بنویسی ‪.‬‬ ‫که برای برگردان‬ ‫حال زما آ رسی‬

‫گام اول ‪ :‬در اینترفیس ‪ ICompanyRepository‬مت ی به نام ‪ GetAllCompanies‬اضافه کنی ‪.‬‬

‫برای نوشتن این مت بای به لروه ‪ Contracts‬یک رفرنس از لروه ‪ Entities‬اضافه کنی ‪.‬‬

‫‪79‬‬
‫توجه!!‬

‫در اینجا میخواهم توجه شما را به یک نکته مهم جلب کنم‪.‬‬

‫و ‪ Entities‬رفرنس دارد‪ .‬از آنجتاییکته‬ ‫‪LoggerService ,Repository‬‬ ‫پروژه اصتتلی بته پروژههتای‬
‫‪ LoggerService‬و ‪ Repository‬یک رفرنس از پروژه ‪ Contracts‬دارند‪ ،‬پس پروژه اصتلی ما از طریق‬
‫‪ LoggerService‬و ‪ Repository‬به پروژه ‪ Entities‬دسترسی دارد‪.‬‬

‫بنابراین میتوانیم رفرنس پروژه ‪ Entities‬را از پروژه اصلی حذف کنیم‪.‬‬

‫اینترفیس ‪: ICompanyRepository‬‬
‫;‪using Entities.Models‬‬
‫;‪using System.Collections.Generic‬‬

‫‪namespace Contracts.IServices‬‬
‫{‬

‫‪80‬‬
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
}
}
. ‫ لیاد سازی کنی‬CompanyRepository ‫ حاال بای این اینترفیس را در کالس‬: ‫گام دوم‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using Repository.Repositories;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}

public IEnumerable<Company> GetAllCompanies(bool trackChanges) =>


FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToList();
}

}
‫ اطالعات شاارکتها را با اساات اد از مت‬CompaniesController ‫ در لایا بای در‬: ‫گام ستوم‬
. ‫ برگردانی‬GetAllCompanies
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using System;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
81
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger)
{
_repository = repository;
_logger = logger;
}

[HttpGet]
public IActionResult GetCompanies()
{
try
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

return Ok(companies);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong in the
{nameof(GetCompanies)} action {ex} ");

return StatusCode(500, "Internal server error");


}

}
}
}
: ‫بررسی کد‬

‫وریق‬ Constructor ‫را درو‬ RepositoryManager ‫ و‬Logger ‫ سااارویس‬،‫اول از هماه‬ •

. ‫کردی‬
‫ ا عملکرد‬، ‫ گذاشااتی‬GetCompanies ‫] را باالی اکشاان مت‬HttpGet[ ‫سااپس ا ربیوت‬ •

. ‫ مپ میکنی‬Get ‫ با این کار این اکشن مت را با ریکوئست‬. ‫این مت را مشخش کنی‬

82
‫درو با ناه اکشااان متا ‪ ،‬برای ‪ Log‬کرد لیاامهاا و گرفتن دیتاا از کالس ریپاازیتوری از‬ ‫•‬

‫است اد کردی ‪.‬‬ ‫سرویسهای ‪ Inject‬ش‬


‫ما اینترفیس ‪ IActionResult‬را در انوا اکشاان مت ها اساات اد میکنی ا اکشاان مت‬ ‫•‬

‫‪ Status‬را همرا باا نتیجاه متا برگردانا ‪ .‬در اینجاا متا ‪ OK‬اطالعاات‬ ‫‪Code‬‬ ‫بتوانا یاک‬
‫برمیگردان ‪.‬‬ ‫‪Status Code 200‬‬ ‫مام شرکتها را همرا با‬
‫میخواهی اگر یک اکساپشان رد ده ‪ Status Code 500 ،‬را( که نمایانگر خطای داخلی‬ ‫•‬

‫سرور است) برگردانی ‪ .‬لس در قسمت ‪ Catch‬این ‪ Status Code‬را ‪ Return‬میکنی ‪.‬‬
‫‪api/companies‬‬ ‫چو باالی اکشاان مت ‪ [Route] ،‬وجود ن ارد لس مساایر این مت برابر‬ ‫•‬

‫است‪ .‬این مسیر هما مسیری است که باالی کنترلر قرار گرفته است‪.‬‬

‫تست کردن اکشن متد ‪GetCompanies‬‬


‫‪ ،‬چک کنی که آیا اللیکیشان به آدرس‬ ‫کلی ‪ F5‬را بونی ا برنامه اجرا شاود‪ .‬بع از اجرا شا‬
‫‪ https:localhost:5001‬گوش میده یا خیر‪.‬‬

‫اگر اینگوناه نبااشااا احتمااال آ را در حاالات ‪ IIS‬اجرا کرد ایا ‪ .‬لس اللیکیشااان را از حاالات اجرا‬
‫خارج کنی و دوبار همانن صویر‪ ،‬در م ‪ CompanyEmployees‬اجرا کنی ‪.‬‬

‫اکنو می وانی از ‪ Postman‬برای ست نتیجه است اد کنی ‪.‬‬


‫‪https://localhost:5001/api/companies‬‬

‫‪83‬‬
‫عالی شد‪ .‬همه چیز مطابق با کدی که نوشتیم کار کرد‪.‬‬

‫کالسهای ‪ DTO‬در مقابل کالسهای ‪Entity Model‬‬


‫)‪ Data Transfer Object (DTO‬کالساای اساات که برای انتقال داد ‪ ،‬بین اللیکیشاان کالینت و‬
‫‪Backward‬‬ ‫ساارور از آ اساات اد میشااود‪ .‬این کالس به ما امکا میده ا‪ ،‬ساااختار داد ای‬
‫‪ Compatible‬و مطابق با نیاز کالینت ایجاد کنی ‪.‬‬

‫لییرکن ‪ API‬ما خراب نشااود؛ چو همیشااه‬ ‫‪Entity‬‬ ‫‪ Backward Compatibility‬یعنی اینکه اگر‬
‫دیتاهایی که کالینت برای ما میفرساات بای با م لی که ما از ورودی میگیری یکی باشاا ‪ .‬به‬
‫بیان دیگر‪:‬‬
‫اگر به هر دلیلی نیاز باش دیتابیس را لییر دهی ‪ ،‬بای لرالر یهای م ل ورودی ه لییر کن ‪.‬‬
‫همانطور که می دانی لییر م ل دیتابیس به این معنی نیسات که اللیکیشان سامت کالینت ه‬
‫بای لییر کن ‪ .‬بنابراین اگر ‪ DTO‬داشااته باشاای ‪ ،‬نتیجهای که کالینت بای داشااته باش ا ‪ ،‬مثل‬
‫گذشته میمان و نها ‪ Entity‬ما لییر میکن ‪.‬‬

‫‪84‬‬
‫ما در ک باال برای مپ کرد ریکوئسات به دیتابیس‪ ،‬مساتقیما از ‪ Company Entity‬اسات اد و‬
‫‪Bad Practice‬‬ ‫‪ Entity‬باه عنوا ‪ Response‬یاک‬ ‫نتیجاه را باه کالینات برمیگردانی ‪ .‬برگردانا‬
‫اساات چو هیچ ‪ Backward Compatibility‬ن اری و گاهی شااای نخواهی همه لرالر یها یک‬
‫‪ Entity‬را به عنوا نتیجه برگردانی ‪ .‬به عنوان مثال‪:‬‬

‫همانطور که میدانی ‪Entity‬های ما دارای ‪Navigation Property‬هایی هستن که شای نخواهی‬


‫این لرالر یها را در ورودی ب رستی ‪.‬‬

‫لس ا اینجا متوجه شا ی که اسات اد از ‪ DTO‬باعث میشاود‪ ،‬ک ما قابل نگه اری و بهتر باشا ‪.‬‬
‫خاب حااال بیااییا در لروه ‪ Entities‬یاک فولا ر باه ناام ‪ DataTransferObjects‬ایجااد و در آ یاک‬
‫کالس با نام ‪ CompanyDto‬اضافه کنی ‪.‬‬
‫;‪using System‬‬

‫‪namespace Entities.DataTransferObjects‬‬
‫{‬
‫‪public class CompanyDto‬‬
‫{‬
‫} ;‪public Guid Id { get; set‬‬
‫} ;‪public string Name { get; set‬‬
‫} ;‪public string FullAddress { get; set‬‬
‫}‬

‫}‬
‫بررسی کد ‪:‬‬

‫‪GetCompanies‬‬ ‫در این کالس لرالر ی ‪ Employees‬را حذف کردی چو در اکشن مت‬ ‫•‬

‫به آ احتیاجی ن اری ‪.‬‬


‫یک لرالر ی ‪ FullAddress‬در این کالس اضاافه کردی ا مق ار لرالر یهای ‪ Address‬و‬ ‫•‬

‫‪ Country‬موجود در کالس ‪ Company‬را به ه بچسبانی و در این لرالر ی بریوی ‪.‬‬


‫همچنین در این کالس‪ ،‬هیچ ا ربیوت اعتبارسااانجی اسااات اد نکردی چو این کالس‬ ‫•‬

‫‪ Response‬باه کالینات مورد اسااات ااد قرار میگیرد لس ا ربیوت‬ ‫فقط برای برگردانا‬
‫اعتبارسنجی الوامی نیست‪.‬‬

‫‪85‬‬
. ‫ را با ه لییر دهی‬GetCompanies ‫بیایی اکشن مت‬
using Contracts.IServices;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger)
{
_repository = repository;
_logger = logger;
}

[HttpGet]
public IActionResult GetCompanies()
{
try
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

var companiesDto = companies.Select(c => new CompanyDto


{
Id = c.Id,
Name = c.Name,
FullAddress = string.Join(' ', c.Address, c.Country)
}).ToList();

return Ok(companiesDto);
}

86
‫)‪catch (Exception ex‬‬
‫{‬
‫‪_logger.LogError($"Something went wrong in the‬‬
‫;)" }‪{nameof(GetCompanies)} action {ex‬‬

‫;)"‪return StatusCode(500, "Internal server error‬‬


‫}‬

‫}‬
‫}‬
‫}‬
‫حاال اللیکیشن را استارت و ریکوئست لایین را در ‪ Postman‬اجرا کنی ‪.‬‬
‫‪https://localhost:5001/api/companies‬‬

‫استفاده از ‪ AutoMapper‬در ‪ASP.Net Core‬‬


‫وجه کنی ‪ ،‬میبینی که عمل مپ کرد‬ ‫‪GetCompanies‬‬ ‫ک ‪ ،‬در اکشان مت‬ ‫اگر به مپ شا‬
‫لرالر یها را به صاورت دساتی انجام دادی ‪ .‬مطملنا این نو مپ کرد ‪ ،‬برای چن فیل مشاکل‬
‫ن ارد اما اگر ع اد این لرالر یها زیاد شود ک ی شلوف و آش ته خواهی داشت‪.‬‬

‫یک روش بهتر برای مپ کرد کالسها‪ ،‬است اد از کتابخانه ‪ Automapper‬است‪.‬‬

‫‪87‬‬
‫ باه ماا کماک میکنا اا آبجکاتهاا را در اللیکیشااان ماپ کنی و کا ی‬AutoMapper ‫کتاابخااناه‬
. ‫خوانا رو قابل نگه اری داشته باشی‬

‫ در لاروه‬،AutoMapper ‫بارای اسااااتا اااد از ایان کاتاااباخااانااه اولایان ق ا م نصااااب‬


‫ را باز کنی و دستور‬Package Manager Console ‫ است لس لنجر‬CompanyEmployee.API
. ‫لایین را اجرا نمایی‬
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection -
Version 8.1.0 -ProjectName CompanyEmployee.API

. ‫ رجیستر کنی‬ConfigureServices ‫ بای این کتابخانه را در مت‬،‫بع از نصب‬

: Startup ‫کدهای کالس‬


using CompanyEmployee.API.Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using NLog;
using System.IO;
using AutoMapper;

namespace CompanyEmployee.API
{
public class Startup
88
{
public Startup(IConfiguration configuration)
{
LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDire
ctory(),"/nlog.config"));
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.ConfigureCors();
services.ConfigureIISIntegration();
services.ConfigureLoggerService();
services.ConfigureSqlContext(Configuration);

services.ConfigureRepositoryManager();

services.AddAutoMapper(typeof(Startup));

services.AddControllers();

services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title =
"CompanyEmployee.API", Version = "v1" });
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment


env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json",
"CompanyEmployee.API v1"));
}

app.UseHttpsRedirection();
app.UseStaticFiles();
89
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});

app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

‫ ایجاد کنی ا در آنجا عمل مپ‬Profile ‫ بای یک کالس‬،‫این کتابخانه‬ ‫لس از رجیساااتر شاا‬
.‫آبجکت مب ا و مقص مشخش شود‬ ‫ش‬

‫(لااروه‬ Infrastructure ‫در فااولاا ر‬ MappingProfile ‫بااناااباارایاان یااک کااالس بااا نااام‬
. ‫) ایجاد کنی و ک های زیر را درو آ بنویسی‬CompanyEmployee.API
using AutoMapper;
using Entities.DataTransferObjects;
using Entities.Models;

namespace CompanyEmployee.API.Infrastructure
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Company, CompanyDto>()
.ForMember(c => c.FullAddress,
opt => opt.MapFrom(x => string.Join(' ', x.Address,
x.Country)));
}
}

90
}
: ‫بررسی کد‬

AutoMapper ‫موجود در کتاابخااناه‬ Profile ‫از کالس‬ MappingProfile ‫باایا کالس‬ •

. ‫ارثبری کن‬
‫ اسااات ااد کنی اا شااای منبع و‬CreateMap ‫ باایا از متا‬،‫ این کالس‬Constructor ‫در‬ •

.‫مشخش شود‬ ‫مقص را برای مپ ش‬


‫ داری و اطالعاات آ باایا شاااامال دو‬DTO ‫ در‬FullAddress ‫ازآنجااییکاه ماا یاک لرالر ی‬ •

‫ماپ را در متا‬ ‫ لس باایا قوانین اضاااافاه شااا‬، ‫بااشااا‬ Country ‫ و‬Address ‫لرالر ی‬
. ‫ مشخش کنی‬ForMember

. ‫ کنی‬Inject ‫حاال می وانی این سرویس را همانن سایر سرویسها در کنترلر‬


using AutoMapper;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;

91
}

[HttpGet]
public IActionResult GetCompanies()
{
try
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);

return Ok(companiesDto);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong in the
{nameof(GetCompanies)} action {ex} ");
return StatusCode(500, "Internal server error");
}

}
}
}
. ‫ست بونی‬ Postman ‫ بیایی دوبار این ریکوئست را در‬. ‫خیلی عالی ش‬
https://localhost:5001/api/companies

92
‫میببین که همه چیو مانن قبل‪ ،‬به درساتی کار میکن با این اوت که اال ک میو ر شا‬
‫است‪.‬‬

‫‪93‬‬
‫فصل پنجم ‪ :‬مدیریت ‪Exception‬ها و ‪Content Negotiation‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ مدیریت ‪Exception‬ها‬
‫➢ ارتباطات ‪ Parent/Child‬در ‪Web API‬‬
‫➢ ‪ Content Negotiation‬چیست؟‬
‫➢ تغییر پیکربندی ‪Response‬‬
‫➢ محدود کردن ‪Media Type‬‬

‫➢ ایجاد فرمت سفارشی‬


‫مدیریت ‪Exception‬ها‬
‫یکی از جنبتههتای مهم یاک اللیکیشااان‪ ،‬ما یریات ‪Exception‬هااسااات‪Exception .‬هاا واقعیات‬
‫زن گی برای مامی اللیکیشانها هساتن ‪ .‬حتی اگر ک های شاما بسایار عالی ه نوشاته شاود‪ ،‬به‬
‫موض اینکاه اللیکیشااان خود را ‪ Release‬و ‪ Deploy‬نمااییا ‪ ،‬کااربرا چاه عما ا و چاه ساااهوا‪،‬‬
‫باالخر راهی برای شکستن آ مییابن ‪.‬‬

‫بنابراین در ولی اللیکیشاان‪ ،‬بای برای اینگونه خطاها لاسااخ مناساابی به کاربر ارائه دهی و با‬
‫بهترین حالت‪ ،‬این مسالله را م یریت نمایی ‪ .‬این مسالله می وان باعث شاکسات اللیکیشان شاما‬
‫شود‪.‬‬

‫فلسا ا هی طراحی ‪ ASP.NET Core‬این اسااات که‪ ،‬هر ‪ Feature‬یک ‪ Option‬اسااات‪ .‬بنابراین‬
‫م یریت خطاها‪ ،‬یک ‪ Feature‬اساات که شااما بای به صااراحت آ را در اللیکیشاان خود فعال‬
‫نمایی ‪.‬‬

‫خطاهای مت او ی‪ ،‬می وانن در اللیکیشاان شااما رد دهن و را های بساایاری برای بررساای آ‬
‫وجود دارن ؛ اما در اینجا میخواه دو مورد از مرسومترین خطاها را به شما نشا ده ‪:‬‬

‫‪Exception‬ها‬ ‫•‬

‫‪Status code‬‬ ‫و خطاهای‬ ‫•‬

‫‪Exception‬ها‪Exception :‬ها زمانی ا اق میافتن که یک شارایط غیر منتظر یافت شاود‪.‬‬


‫‪ NullReferenceException‬نمونهای اسات که‪ ،‬ب و شاک همهی شاما آ را جربه کرد ای‬
‫و زمانی رد میده که الش برای دساترسای به آبجکتی داری که هنوز ‪ Initialized‬نشا‬
‫است‪.‬‬

‫اگر یک ‪ Exception‬در یک ‪ Middleware‬ا اق بی ت ‪ ،‬در ساراسار ‪ Pipeline‬منتشار میشاود‪ .‬اگر‬


‫‪ Pipeline‬آ را م یریت نکن ‪ ،‬وب سرور‪ Status code 500 ،‬را به کاربر برمیگردان ‪.‬‬

‫گاهی اوقات هیچ ‪Exception‬ی رد نمیده اما ‪ Middleware‬ممکن اساات ک خطایی را ایجاد‬
‫‪Handle‬‬ ‫باشاای ‪ ،‬زمانیساات که مساایر ریکوئساات‬ ‫کن ‪ .‬یکی از مواردی که ممکن اساات دی‬

‫‪95‬‬
‫نمیشاود‪ .‬در این وضاعیت ‪ Pipeline‬خطای ‪ 404‬را برمیگردان ‪ .‬در نتیجه صا وه خطای ‪ 404‬به‬
‫داد میشود‪.‬‬ ‫کاربر نمای‬

‫گرچه این رفتار‪ ،‬صتحیح استت اما تجربه خوبی برای کاربران اپلیکیشتن شتما نرواهند‬
‫بود‪.‬‬

‫الش میکن قبل از اینکه اللیکیشاان‬ ‫‪Error handling middleware‬‬ ‫برای حل این مشااکالت‪،‬‬
‫ده ‪ Response ،‬را لییر ده ‪.‬‬ ‫چیوی را به کاربر نمای‬

‫‪ Error‬جوئیات خطای رد داد را در یک صا ا وه کاربرلسااان نمای‬ ‫‪handling middleware‬‬

‫میده ‪.‬‬

‫با وجه به اینکه هرگونه خطا می وان اللیکیشان شاما را با شاکسات روبرو کن ‪ ،‬لس عجله کنی‬
‫و هرچه سریعتر ‪ Error handling middleware‬را به ‪ Middleware Pipeline‬اضافه نمایی ‪.‬‬

‫یاک ‪ Middleware‬داخلی باه ناام ‪ UseExceptionHandler‬وجود دارد کاه‬ ‫‪ASP.Net Core‬‬ ‫در‬
‫می وانی از آ ‪ ،‬برای مقابله با ‪ Exception‬است اد کنی ‪.‬‬

‫ابت ا در لروه ‪ Entities‬لکی لایین را نصب کنی ‪.‬‬


‫‪Install-Package Newtonsoft.Json -Version 12.0.3 -ProjectName Entities‬‬

‫‪ErrorDetails‬‬ ‫حاال در این لروه یک فول ر ج ی به نام ‪ ErrorModel‬ایجاد و در آ کالسی با نام‬


‫اضافه نمایی ‪.‬‬
‫;‪using Newtonsoft.Json‬‬

‫‪namespace Entities.ErrorModel‬‬
‫{‬
‫‪public class ErrorDetails‬‬
‫{‬
‫‪96‬‬
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString() =>
JsonConvert.SerializeObject(this);
}

}
‫ برای ادامه در‬.‫ اسااات ااد میشاااود‬، ‫این کالس برای نگها اری جوییاات لیاام خطاای بوجود آم‬
ExceptionMiddlewareExtensions.cs ‫ یک کالس ج ی به نام‬،‫ لروه اصلی‬Extensions ‫فول ر‬
. ‫اضافه کنی‬
using Contracts.IServices;
using Entities.ErrorModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using System.Net;

namespace CompanyEmployee.API.Infrastructure.Extensions
{
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this
IApplicationBuilder app, ILoggerManager logger)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode =
(int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature =
context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
logger.LogError($"Something went wrong:
{contextFeature.Error}");

97
await context.Response.WriteAsync(new
ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString());
}
});
});
}
}

}
: ‫بررسی کد‬

UseExceptionHandler ‫در این کالس یاک اکساااتنشااان متا ایجااد کردی کاه در آ‬ •

. ‫ را لر میکن‬Response ‫ و نو موتوای‬Status Code ‫رجیستر و سپس‬


. ‫ و ریسپانس را میفرست‬Log ‫در لایا لیلام خطا را‬ •

. ‫ اضافه کنی‬Startup ‫ کالس‬Configure ‫برای است اد از این اکستنشن مت بای ک لایین را در‬
using CompanyEmployee.API.Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Contracts.IServices;
using Microsoft.OpenApi.Models;
using NLog;
using System.IO;
using AutoMapper;

namespace CompanyEmployee.API
{
public class Startup
{

98
public Startup(IConfiguration configuration)
{
LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDire
ctory(),"/nlog.config"));
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.ConfigureCors();
services.ConfigureIISIntegration();
services.ConfigureLoggerService();
services.ConfigureSqlContext(Configuration);

services.ConfigureRepositoryManager();

services.AddAutoMapper(typeof(Startup));

services.AddControllers();

services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title =
"CompanyEmployee.API", Version = "v1" });
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment


env , ILoggerManager logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json",
"CompanyEmployee.API v1"));
}

app.ConfigureExceptionHandler(logger);

app.UseHttpsRedirection();

99
app.UseStaticFiles();

app.UseCors("CorsPolicy");

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

UseExceptionHandler ‫تست‬
‫ حاذف و ساااپس برای‬GetCompanies ‫ را از متا‬try-catch ‫ کا‬Middleware ‫برای سااات این‬
. ‫ اکسپشن لایین را به ک اضافه کنی‬،‫شبیه سازی یک لیام خطا‬
using AutoMapper;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

100
public CompaniesController(IRepositoryManager repository,
ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public IActionResult GetCompanies()
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);

throw new Exception("Exception");

return Ok(companiesDto);
}
}
}

. ‫ ریکوئست لایین را ست کنی‬Postman ‫حاال با‬


https://localhost:5001/api/companies

101
‫ از دیتابیس‬Resource ‫گرفتن اطالعات یک‬
‫ حاال میخواهی اکشان مت ی برای گرفتن اطالعات یک‬. ‫ آشانا شا ی‬GET ‫ا اینجا با ریکوئسات‬
. ‫شرکت بنویسی‬

‫ بیااییا اینترفیس‬.‫اساااات‬ CompanyRepository ‫اولین گاام اضااااافاه کرد یاک متا باه‬
. ‫ را همانن ک لایین لییر دهی‬ICompanyRepository
using Entities.Models;
using System.Collections.Generic;
using System;

namespace Contracts.IServices
{
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
}
}

. ‫ لیاد سازی کنی‬CompanyRepository ‫خب حاال بای این اینترفیس را در کالس‬


using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }

102
public IEnumerable<Company> GetAllCompanies(bool trackChanges) =>
FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToList();

public Company GetCompany(Guid companyId, bool trackChanges) =>


FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefault();
}

}
. ‫ اضافه کنی‬CompanyController ‫ را در‬GetCompany ‫در لایا بای اکشن مت‬
using AutoMapper;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public IActionResult GetCompanies()
{

103
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);

return Ok(companiesDto);
}

[HttpGet("{id}")]
public IActionResult GetCompany(Guid id)
{
var company = _repository.Company.GetCompany(id, trackChanges:
false);
if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");

return NotFound();
}
else
{
var companyDto = _mapper.Map<CompanyDto>(company);

return Ok(companyDto);
}
}

}
}
: ‫بررسی کد‬

‫ قسامتی از‬api/companies/ ‫ عبارت‬.‫ اسات‬api/companies/id/ ‫مسایر این اکشان مت‬ •

‫قسااامتی از ا ربیوت اکشااان متا‬ id ‫مسااایر ریشااااه اساااات (باااالی کنترلر) و‬


. ‫[) میباش‬HttpGet("{id}")](
‫ در دیتابیس جساااتجو‬،‫ ورودی‬Id ‫این اکشااان مت اطالعات یک شااارکت را با وجه به‬ •

‫ را‬404 ‫ کا‬NotFound ‫ اگر این شااارکات وجود نا اشااات باا اسااات ااد از متا‬. ‫میکنا‬

104
‫برمیگردانا در غیر این صاااورت اطالعاات شااارکات را باه ‪ CompanyDto‬ماپ میکنا و‬
‫همرا با ک ‪ 200‬به کالینت برمیگردان ‪.‬‬
‫ناام آ هاا می وانی‬ ‫‪ ،ASP.NET‬عا ادی متا وجود دارد کاه نهاا باا دیا‬ ‫‪Core‬‬ ‫در‬ ‫•‬

‫ب همی که چه کاری انجام میدهن ‪ .‬به طور مثال ‪:‬‬

‫را برمیگردانا ‪ .‬و یاا متا‬ ‫‪Status Code 200‬‬ ‫برای نتیجاه خوب اساااات و‬ ‫‪OK‬‬ ‫متا‬
‫و (‪ )Status Code 404‬را برمیگردان ‪.‬‬ ‫‪ NotFound‬برای نتیجهای است که لی ا نش‬

‫بیایی اینبار یک ریکوئسات معتبر و یک ریکوئسات نامعتبر را با ‪ Postman‬ارساال کنی ‪ ،‬ا نتیجه‬
‫هر ک ام از مت های ‪ ASP.NET Core‬را ببینی ‪.‬‬

‫ریکوئست معتبر ‪:‬‬


‫‪https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce3‬‬

‫ریکوئست نامعتبر ‪:‬‬


‫‪https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce2‬‬

‫‪105‬‬
Web API ‫ در‬Parent/Child ‫ارتباطات‬
، ‫ اما همانطور که میدانی‬. ‫ بود کار کردی‬Parent Entity ‫ که یک‬Company ‫ا اینجا فقط با م ل‬
‫ هر کارمن بای با یک شارکت‬. ‫هر شارکت ع ادی کارمن مر بط دارد که وابساته شارکت هساتن‬
. ‫های این کارمن ا از این طریق ایجاد شون‬URI ‫خاا در ار باط باش بنابراین بای‬

. ‫ ایجاد کنی‬EmployeesController ‫ یک کنترلر ج ی با نام‬Controllers ‫خب بیایی در فول ر‬


using AutoMapper;
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public EmployeesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
106
‫{‬
‫;‪_repository = repository‬‬
‫;‪_logger = logger‬‬
‫;‪_mapper = mapper‬‬
‫}‬
‫}‬

‫}‬
‫بررسی کد ‪:‬‬

‫همه شااما با این ک ها آشاانا هسااتی ‪ .‬نها نکته این ک ‪ Route ،‬باالی کنترلر اساات که‬ ‫•‬

‫کمی مت اوت میباشا ‪ .‬همانطور که گ تی یک کارمن نمی وان ب و شارکت باشا و‬


‫این دقیقا چیوی است که ما از طریق این ‪ URI‬نشا میدهی ‪.‬‬
‫برای واکشی یک یا چن کارمن از دیتابیس‪ ،‬بای لارامتر ‪ CompanyId‬را مشخش کنی ‪.‬‬ ‫•‬

‫همانطور که میدانی این چیوی اساات که در مام اکشاان مت ها مشااترک میباش ا به‬
‫همین دلیل ما این مسیر را به عنوا مسیر ریشه عیین میکنی ‪.‬‬

‫قبل از ایجاد یک اکشاان مت برای واکشاای کارمن ا هر شاارکت‪ ،‬بای لییرا ی در اینترفیس‬
‫‪ IEmployeeRepository‬دهی ‪.‬‬
‫;‪using Entities.Models‬‬
‫;‪using System‬‬
‫;‪using System.Collections.Generic‬‬

‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface IEmployeeRepository‬‬
‫{‬
‫‪IEnumerable<Employee> GetEmployees(Guid companyId, bool‬‬
‫;)‪trackChanges‬‬
‫}‬
‫}‬
‫حاال بای این اینترفیس را در کالس ‪ EmployeeRepository‬لیاد سازی کنی ‪.‬‬
‫;‪using Contracts.IServices‬‬
‫;‪using Entities‬‬
‫;‪using Entities.Models‬‬
‫;‪using System‬‬

‫‪107‬‬
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}

public IEnumerable<Employee> GetEmployees(Guid companyId, bool


trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId), trackChanges).OrderBy(e => e.Name);

}
. ‫ اضافه کنی‬EmployeesController ‫ را در‬GetEmployeesForCompany ‫در لایا بیایی مت‬
using AutoMapper;
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using System;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public EmployeesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;

108
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public IActionResult GetEmployeesForCompany(Guid companyId)
{
var company = _repository.Company.GetCompany(companyId,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb =
_repository.Employee.GetEmployees(companyId, trackChanges:
false);

return Ok(employeesFromDb);
}
}

}
: ‫بررسی کد‬

‫ اما دو نکته‬. ‫باشای‬ ‫این مت بسایار سااد اسات و در آ ک ی نیسات که اکنو ن ی‬ •

: ‫وجود دارد که بهتر است با ه بررسی کنی‬


‫ از‬،‫ از ورودی میگیرد کاه این لاارامتر‬companyId ‫این اکشااان متا یاک لاارامتر‬ ▪

]HttpGet[ ‫ به همین دلیل ما این لارامتر را در ا ربیوت‬.‫مسایر اصالی مپ میشاود‬


. ‫قرار ن ادی‬
‫ بهتر اسات خروجی‬، ‫ اسات و همانطور که قبال گ تی‬Entity ‫خروجی این مت یک‬ ▪

‫ بناابراین یاک کالس باا ناام‬. ‫ بااشااا‬DTO ‫میشاااود یاک‬ ‫کاه باه کالینات برگردانا‬
. ‫ اضافه کنی‬DataTransferObjects ‫ در فول ر‬EmployeeDto
109
using System;

namespace Entities.DataTransferObjects
{
public class EmployeeDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
}
}
. ‫ اضافه کنی‬MappingProfile ‫ در کالس‬Mapping ‫حاال بیایی یک‬
CreateMap<Employee, EmployeeDto>();

. ‫در لایا بای اکشن خود را لییر دهی‬


using AutoMapper;
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using Entities.DataTransferObjects;
using System.Collections.Generic;
using System;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public EmployeesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;

110
_mapper = mapper;
}

[HttpGet]
public IActionResult GetEmployeesForCompany(Guid companyId)
{
var company = _repository.Company.GetCompany(companyId,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb =
_repository.Employee.GetEmployees(companyId, trackChanges:
false);

var employeesDTO =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDTO);
}
}

}
. ‫ معتبر ارسال کنی‬CompanyId ‫این کارها می وانی ریکوئستی با یک‬ ‫بع از انجام ش‬
https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees

111
. ‫ نا معتبر این را ست بونی‬CompanyId ‫حاال با یک‬
https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-
2d54a9991873/employees

112
‫واکشی اطالعات کارمند‬
‫ را‬IEmployeeRepository ‫قبلی ابت ا اینترفیس‬ ‫ بای همانن بخ‬، ‫برای نوشاتن این اکشان مت‬
. ‫لییر دهی‬
using Entities.Models;
using System;
using System.Collections.Generic;

namespace Contracts.IServices
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges);

Employee GetEmployee(Guid companyId, Guid id, bool trackChanges);


}
}
. ‫ لیاد سازی کنی‬EmployeeRepository ‫سپس این مت را در‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }

public IEnumerable<Employee> GetEmployees(Guid companyId, bool


trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId), trackChanges).OrderBy(e => e.Name);

113
public Employee GetEmployee(Guid companyId, Guid id, bool
trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId) && e.Id.Equals(id),
trackChanges).SingleOrDefault();

}
. ‫ اضافه نمایی‬EmployeesController ‫ را در‬GetEmployeeForCompany ‫در لایا اکشن مت‬
using AutoMapper;
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using Entities.DataTransferObjects;
using System.Collections.Generic;
using System;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;
public EmployeesController(IRepositoryManager repository,
ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public IActionResult GetEmployeesForCompany(Guid companyId)
{
var company = _repository.Company.GetCompany(companyId,
trackChanges: false);

if (company == null)

114
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

return NotFound();
}

var employeesFromDb =
_repository.Employee.GetEmployees(companyId, trackChanges:
false);

var employeesDTO =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDTO);
}

[HttpGet("{id}")]
public IActionResult GetEmployeeForCompany(Guid companyId, Guid id)
{
var company = _repository.Company.GetCompany(companyId,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");
return NotFound();
}

var employeeDb = _repository.Employee.GetEmployee(companyId,


id, trackChanges: false);

if (employeeDb == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

return NotFound();
}

115
var employee = _mapper.Map<EmployeeDto>(employeeDb);

return Ok(employee);
}
}

}
.‫عالی شد‬

. ‫ست کنی‬ Postman ‫ریکوئست لایین را در‬


https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees/80abbca8-664d-4b20-b5de-024705497d4a

. ‫سپس این اکشن مت را با ریکوئست نامعتبر ست کنی‬


https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees/86dba8c0-d17841e7-938c-ed49778fb52c

116
‫اا اال ‪Response‬هاایی باا فرمات ‪ JSON‬از ‪ API‬گرفتی ‪ ،‬اماا اگر بخواهی از فرمات دیگری مااننا‬
‫‪ XML‬لشتیبانی کنی چه بای کرد؟‬

‫لاسخ این سوال را ‪ Content Negotiation‬میده ‪ .‬با ما همرا باشی ا در این بار بیشتر ب انی‬

‫‪ Content Negotiation‬چیست؟‬
‫اساااات کاه باا آ می وانی فرمات ‪ Response‬را‬ ‫‪HTTP‬‬ ‫یاک ویژگی‬ ‫‪Content Negotiation‬‬

‫مشاخش کنی ‪ .‬به عبار ی‪ ،‬لروساهی انتخاب فرمت ‪ Response‬در زما هایی که چن ین فرمت‬
‫در اختیار داری ‪ ،‬را ‪ Content Negotiation‬میگوین ‪.‬‬

‫به طور لی فرض ‪ ASP.NET Core Web API‬فرمت ‪ JSON‬را به عنوا نتیجه برمیگردان ‪.‬‬

‫ریسپانس صویر لایین‪ ،‬این موضو را متوجه شوی ‪.‬‬ ‫ما می وانی با دی‬
‫‪https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-‬‬
‫‪2d54a9991870/employees/80abbca8-664d-4b20-b5de-024705497d4a‬‬

‫‪117‬‬
‫نظی کردی ‪ ،‬اا سااارور را مجبور‬ ‫‪Text /XML‬‬ ‫هماانطور کاه میبینیا ‪ ،‬ها ر ‪ Accept‬را بر روی‬
‫کنی خروجی ‪ XML/Text‬را برگردان ؛ اما باز ه نتیجه ‪ Json‬را میبینی ‪ .‬این به این دلیل است‬
‫که بای فرمتهای سرور را لیکربن ی کنی ا بتوانی فرمت ‪ Response‬را لییر دهی ‪.‬‬

‫تغییر پیکربندی ‪Response‬‬


‫در یک ساارور نمی وا به صااراحت فرمت ریسااپانس ‪ JSON‬را لییر داد اما می وانی از طریق‬
‫مت ‪ AddControllers‬گوینههای لیکربن ی را ‪ Override‬کنی ‪.‬‬

‫اول از همه بای به یک سرور بگویی که ه ر ‪ Accept‬را قبول کن ‪.‬‬ ‫•‬

‫سپس مت ‪ AddXmlDataContractSerializerFormatters‬را اضافه کنی ا از فرمتهای‬ ‫•‬

‫‪ XML‬لشتیبانی کن ‪.‬‬

‫خب مت ‪ AddControllers‬را در همانن ک لایین لییر دهی ‪.‬‬


‫{ >= ‪services.AddControllers(config‬‬
‫;‪config.RespectBrowserAcceptHeader = true‬‬
‫;)(‪}).AddXmlDataContractSerializerFormatters‬‬

‫‪118‬‬
‫حاال که سرور خود را لیکربن ی کردی ‪ ،‬اجاز دهی یکبار دیگر ‪ Content Negotiation‬را ست‬
‫کنی ‪.‬‬

‫تست ‪Content Negotiation‬‬


‫بیایی ببینی اگر درخواست لایین را از طریق ‪ Postman‬انجام دهی چه ا اقی میافت ‪.‬‬
‫‪https://localhost:5001/api/companies‬‬

‫همانطور که میبینی ‪ Response‬ما ‪ XML‬است‪.‬‬

‫حااال باا لییر ها ر ‪ Accept‬از ‪ text/xml‬باه ‪ text/json‬می وانی ریساااپاانسااای باا فرمات مت ااوت‬
‫بگیری ‪.‬‬

‫خب به نظر شتتما اگر کالینت نوعی را درخواستتت کند که ستترور نتواند آن فرمت را‬
‫نمایش دهد چه اتفاقی میافتد؟‬

‫لاسخ این سوال را ‪ Media Type‬میده ‪ .‬با ما همرا باشی ا در این بار بیشتر ب انی ‪.‬‬

‫‪119‬‬
‫محدود کردن ‪Media Type‬‬
‫با یال میکنا ‪ .‬اماا ماا‬ ‫‪JSON‬‬ ‫در حاال حااضااار‪ ،‬سااارور باه طورلی فرض ‪ Response‬را باه نو‬
‫می وانی با اضافه کرد یک خط به نظیمات‪ ،‬این رفتار را مو ود کنی ‪.‬‬
‫{ >= ‪services.AddControllers(config‬‬
‫;‪config.RespectBrowserAcceptHeader = true‬‬
‫;‪config.ReturnHttpNotAcceptable = true‬‬
‫;)(‪}).AddXmlDataContractSerializerFormatters‬‬

‫بررسی کد ‪:‬‬

‫= ‪ ReturnHttpNotAcceptable‬را باه نظیماات ‪ AddControllers‬اضاااافاه‬ ‫‪true‬‬ ‫ماا گویناه‬ ‫•‬

‫کردی ا به سارور بگوی که اگرکالینت‪ ،‬فرمتی که سارور لشاتیبانی نمیکن را خواسات‬


‫بای ‪ Not Acceptable 406‬را برگردانی‪.‬‬
‫این باعث میشااود برنامه ما مو ود ر شااود و کالینت فقط انواعی که ساارور لشااتیبانی‬ ‫•‬

‫است‪.‬‬ ‫میکن را درخواست نمای ‪ .‬ک ‪ 406‬برای این منظور ایجاد ش‬

‫نظی کنی و ‪ API‬لایین را ص ا بونی ‪.‬‬ ‫‪text/css‬‬ ‫حاال برای ست‪ ،‬ه ر ‪ Accept‬را بر روی‬
‫‪https://localhost:5001/api/companies‬‬

‫‪406‬‬ ‫همانطور که انتظار میرفت‪ ،‬هیچ ‪ Response Body‬وجود ن ارد و نها چیوی که میگیری‬
‫‪ Not Acceptable‬است‪.‬‬

‫‪120‬‬
‫ایجاد فرمت سفارشی‬
‫اگر برواهیم ‪ ،API‬فرمتی که در کادر نیست را هم پشتیبانی کند چه باید کرد؟‬

‫خوشابختانه ‪ ASP.NET Core‬از ایجاد فرمتهای سا ارشای لشتیبانی میکن ‪ .‬برای اینکه بتوانی‬
‫فرمت س ارشی داشته باشی می وانی از مت های لایین است اد کنی ‪.‬‬

‫بااای ا ی اک کاالس ایاجاااد کانای ا کااه از کاالس‬ ‫‪Input Formatter‬‬ ‫بارای ایاجاااد‬ ‫•‬

‫‪ TextInputformatter‬ارثبری کنا ‪ .‬و برای ایجااد ‪ Output Formatter‬باایا یاک کالس‬


‫ایجاد کنی که از کالس ‪ TextOutputFormatter‬ارثبری کن ‪.‬‬
‫ساپس به هما روشای که برای فرمت ‪ XML‬انجام دادی بای این کالسها را به مجموعه‬ ‫•‬

‫‪ InputFormatters‬و ‪ OutputFormatters‬اضافه کنی ‪.‬‬

‫حاال بیایی یک قالب ‪ CSV‬س ارشی برای مثالما آماد کنی ‪.‬‬

‫لیساات شاارکتها در قالب ‪ CSV‬ایجاد‬ ‫خب میخواهی یک فرمت ‪ ،Response‬برای برگردان‬


‫کنی بنابراین برای لیاد ساازی‪ ،‬به یک کالس نیاز داری که از ‪ TextOutputFormatter‬ارثبری‬
‫کن ‪.‬‬

‫بیایی یک کالس ‪ CsvOutputFormatter‬در فول ر ‪ Infrastructure‬لروه اصلی اضافه کنی ‪.‬‬


‫;‪using Entities.DataTransferObjects‬‬
‫;‪using Microsoft.AspNetCore.Http‬‬
‫;‪using Microsoft.AspNetCore.Mvc.Formatters‬‬
‫;‪using Microsoft.Net.Http.Headers‬‬
‫;‪using System‬‬
‫;‪using System.Collections.Generic‬‬
‫;‪using System.Text‬‬
‫;‪using System.Threading.Tasks‬‬

‫‪namespace CompanyEmployee.API.Infrastructure‬‬
‫{‬
‫‪public class CsvOutputFormatter : TextOutputFormatter‬‬
‫{‬
‫)(‪public CsvOutputFormatter‬‬
‫{‬

‫‪121‬‬
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type type)
{
if (typeof(CompanyDto).IsAssignableFrom(type) ||
typeof(IEnumerable<CompanyDto>).IsAssignableFrom(type))
{
return base.CanWriteType(type);
}

return false;
}

public override async Task


WriteResponseBodyAsync(OutputFormatterWriteContext
context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var buffer = new StringBuilder();

if (context.Object is IEnumerable<CompanyDto>)
{
foreach (var company in
(IEnumerable<CompanyDto>)context.Object)
{
FormatCsv(buffer, company);
}
}
else
{
FormatCsv(buffer, (CompanyDto)context.Object);
}

await response.WriteAsync(buffer.ToString());
}
private static void FormatCsv(StringBuilder buffer, CompanyDto
company)
{

buffer.AppendLine($"{company.Id},\"{company.Name},\"{company.Ful
lAddress}\"");
}

122
}

}
: ‫بررسی کد‬

‫ را‬MediaType ‫ مشاااخش کردی کاه این فرمات باایا کا ام‬،‫ این کالس‬Constructor ‫در‬ •

. ‫ کن‬Encode ‫ و‬Parse
‫ را می وا با‬CompanyDto ‫ مت یسات که مشاخش میکن آیا نو‬،CanWriteType ‫مت‬ •

‫ نوشت یا نه؟‬Serializer
. ‫ ریسپانس را ایجاد میکن‬WriteResponseBodyAsync ‫مت‬ •

‫ را درسااات‬Response ‫ میبینی کاه فرماتهاای‬FormatCsv ‫ متا ی باه ناام‬، ‫و در لاایاا‬ •

. ‫میکن‬

‫ لس اکساتنشان‬. ‫ اضاافه کنی‬OutputFormatters ‫حاال فقط بای فرمت ج ی را به لیسات‬


‫ اضافه نمایی‬ServicesExtensions ‫مت لایین را در کالس‬
public static IMvcBuilder AddCustomCSVFormatter(this IMvcBuilder builder)
=>
builder.AddMvcOptions(config => config.OutputFormatters.Add(new
CsvOutputFormatter()));

. ‫ ص ا بونی‬AddControllers ‫حاال این مت را در‬


services.AddControllers(config => {
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();

. ‫ قرار دهی‬Accept ‫ را به عنوا مق ار ه ر‬text/csv ‫برنامه را اجرا و‬


https://localhost:5001/api/companies

123
‫خیلی عالی ش ‪ ،‬همه چیو خیلی خوب کار میکن ‪.‬‬

‫در این فصال ریکوئساتهای ‪ GET‬را اضاافه کردی ‪ .‬در فصال بع ریکوئساتهای ‪ PUT ،POST‬و‬
‫‪ DELETE‬را بررسی میکنی ‪.‬‬

‫‪124‬‬
‫‪ POST , PUT‬و ‪DELETE‬‬ ‫فصل ششم ‪:‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ ایجاد‪ ،‬حذف و آپدیت ‪Resource‬‬

‫➢ ‪ Model Binding‬چیست؟‬
‫مدیریت ‪Post Request‬‬
‫‪Resource‬‬ ‫‪ Post‬یکی از ‪HTTP Method‬های ‪ Rest‬اسااات که برای ایجاد یا آل یت بخشااای از‬
‫اساات اد میشااود‪ .‬اگر ‪ Resource‬دارای ‪ Id‬باشاا ‪ ،‬این فعل یک ‪ Resource‬را آل یت میکن‬
‫وگرنه یک ‪ Resource‬ج ی را ایجاد خواه کرد‪.‬‬

‫را مااننا کا لاایین‬ ‫‪CompaniesController‬‬ ‫گتام اول ‪ :‬ا ربیوت اکشااان متا ‪ GetCompany‬در‬
‫اصالح کنی ‪ ،‬چو در ادامه به این ک نیاز داری ‪.‬‬
‫])"‪[HttpGet("{id}", Name = "CompanyById‬‬

‫گام دوم ‪ :‬یک کالس با نام ‪ CompanyForCreationDto‬در فول ر ‪ DataTransferObjects‬ایجاد‬


‫کنی ‪.‬‬
‫‪namespace Entities.DataTransferObjects‬‬
‫{‬
‫‪public class CompanyForCreationDto‬‬
‫{‬
‫} ;‪public string Name { get; set‬‬
‫} ;‪public string Address { get; set‬‬
‫} ;‪public string Country { get; set‬‬
‫}‬

‫}‬
‫همانطور که میبینی این کالس قریبا مشابه کالس ‪ Company‬است با این اوت که ‪ Id‬ن ارد‪.‬‬

‫در برخی لروه ها‪ ،‬کالس ‪ DTO‬ورودی و خروجی یکی اساات؛ اما وصاایه میکنی این دو را از‬
‫ه جا ا کنیا اا نگها اری و ری کتور کا ‪ ،‬سااااد ر شاااود‪ .‬عالو بر این‪ ،‬وقتی صاااوبات از‬
‫اعتبارسانجی را شارو کنی ‪ ،‬نیازی به اعتبارسانجی آبجکت خروجی نیسات اما آبجکت ورودی‬
‫بای اعتبارسنجی شود‪.‬‬

‫خب حاال بای مت ‪ CreateCompany‬را در اینترفیس ‪ ICompanyRepository‬اضافه کنی ‪.‬‬


‫;‪using Entities.Models‬‬
‫;‪using System.Collections.Generic‬‬
‫;‪using System‬‬

‫‪namespace Contracts.IServices‬‬

‫‪126‬‬
{
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
void CreateCompany(Company company);
}
}

‫ لیاد ساااازی‬CompanyRepository ‫ حاال ما این اینترفیس را در کالس‬،‫بع از لییر اینترفیس‬


. ‫کنی‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }

public IEnumerable<Company> GetAllCompanies(bool


trackChanges) =>
FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToList();

public Company GetCompany(Guid companyId, bool trackChanges) =>


FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefault();

127
public void CreateCompany(Company company) => Create(company);

}
!!‫نکته‬

‫ تمام کاری که‬.‫ انجام می شتود‬EF Core ‫ را برای شترکت خود ایجاد نمی کنیم این کار توستط‬Id ‫ما‬
.‫ قرار دهیم‬Added ‫ شرکت را بر روی‬State ‫انجام می دهیم این است‬

‫ دیگر‬Rule ‫اضاافه کنی بای یک‬ CompaniesController ‫قبل از اینکه یک اکشان مت ج ی در‬
‫ بنابراین ک لایین‬. ‫ بنویساای‬CompanyForCreationDto ‫ و‬Company ‫برای مپ کرد آبجکت‬
. ‫ اضافه نمایی‬MappingProfile ‫را در کالس‬
CreateMap<CompanyForCreationDto, Company>();

.‫ ایجاد شود‬CreateCompany ‫خب حاال بای اکشن مت‬


[HttpPost]
public IActionResult CreateCompany([FromBody] CompanyForCreationDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from client
is null.");
return BadRequest("CompanyForCreationDto object is null");
}

var companyEntity = _mapper.Map<Company>(company);


_repository.Company.CreateCompany(companyEntity);
_repository.Save();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id


},
companyToReturn);
}

128
: CompaniesController ‫کدهای‬
using AutoMapper;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;
using Entities.Models;
using System;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public IActionResult GetCompanies()
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);

var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);

return Ok(companiesDto);
}

[HttpGet("{id}", Name = "CompanyById")]


129
public IActionResult GetCompany(Guid id)
{
var company = _repository.Company.GetCompany(id, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");

return NotFound();
}
else
{
var companyDto = _mapper.Map<CompanyDto>(company);

return Ok(companyDto);
}
}

[HttpPost]
public IActionResult CreateCompany([FromBody]
CompanyForCreationDto company)
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from
client is null.");

return BadRequest("CompanyForCreationDto object is null");


}
var companyEntity = _mapper.Map<Company>(company);
_repository.Company.CreateCompany(companyEntity);
_repository.Save();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

return CreatedAtRoute("CompanyById", new { id =


companyToReturn.Id },
companyToReturn);

130
‫}‬

‫}‬
‫}‬
‫ست کنی ‪.‬‬ ‫‪Postman‬‬ ‫قبل از وضیح ب نه ک بیایی یکبار این اکشن مت را با‬
‫‪https://localhost:5001/api/companies‬‬
‫‪Body:‬‬

‫{‬
‫‪"name": "Fara_Ltd",‬‬
‫‪"address": "Tehran, Vanak Iran",‬‬
‫"‪"country":"Iran‬‬
‫}‬

‫بررسی کد ‪:‬‬

‫بگذاری در مورد ک های این اکشن مت و این ریکوئست کمی بیشتر صوبت کنی ‪.‬‬

‫اکشاان مت ‪ CreateCompany‬یک ا ربیوت [‪ ]HttpPost‬دارد که باعث میشااود‪ ،‬نها با‬ ‫•‬

‫ریکوئست ‪ POST‬بتوانی آ را ص ا بونی ‪.‬‬


‫لارامتر ‪ company‬از سمت کالینت میآی لس نیاز به ا ربیوت [‪ ]FromBody‬داری ‪.‬‬ ‫•‬

‫‪131‬‬
‫چو لاارامتر ورودی از ‪ URI‬گرفتاه نمیشاااود لس باایا لاارامتر را از ‪ Body‬ریکوئسااات‬ ‫•‬

‫بگیری ‪.‬‬
‫از آنجاییکه لارامتر ‪ company‬از سامت کالینت میای لس ممکن اسات مقادیر معتبری‬ ‫•‬

‫ن اشاته باشا و نتوا آ را ‪ Deserialized‬کرد‪ .‬در نتیجه بای این لارامتر را اعتبارسانجی‬
‫کنی ا مق ار آ ‪ null‬نباش ‪.‬‬
‫بعا از اعتباار سااانجی باایا این لاارامتر را برای ایجااد یاک ‪ Company‬ماپ کنی و متا‬ ‫•‬

‫‪ CreateCompany‬را ص ا بونی ‪.‬‬


‫برای ذخیر ‪ Entity‬در دیتابیس‪ ،‬مت ‪ Save‬را است اد میکنی ‪.‬‬ ‫•‬

‫لس از ذخیر ‪ Company‬در دیتاابیس‪ ،‬باایا آبجکات ‪ company‬را باه ‪ CompanyDto‬ماپ‬ ‫•‬

‫کنی و به کالینت برگردانی ‪.‬‬


‫‪CreatedAtRoute‬‬ ‫آخرین موردی که بای به آ اشار کن مت ‪ CreatedAtRoute‬است‪.‬‬ ‫•‬

‫یک ‪ Status Code 201‬را برمیگردان ‪.‬‬


‫لر میکنا و ساااپس در‬ ‫‪Company‬‬ ‫را باا یاک آبجکات‬ ‫‪Response Body‬‬ ‫این متا ‪،‬‬
‫‪Headers‬‬ ‫لرالر ی ‪ Location‬ها ر‪ ،‬مکاا ‪ Get‬را برمیگردانا ‪ .‬اگر ‪ Response‬مربوط باه‬
‫است‪.‬‬ ‫را بررسی کنی ‪ ،‬لینکی برای واکشی اطالعات شرکت ایجاد ش‬

‫همانطور که قبال گ ت ‪ Idempotent - Post‬نیسات بنابراین وقتی ریکوئسات ‪ POST‬را ارساال‬


‫اسات‪ .‬حاال اگر این ریکوئسات‬ ‫کنی ‪ ،‬میبینی که یک ‪ Resource‬ج ی در دیتابیس ایجاد شا‬
‫را چنا ین باار کرار کنیا ‪ ،‬مطملناا برای هر ریکوئسااات یاک آبجکات جا یا دریاافات خواهیا کرد‬
‫که هر ک ام ‪ Id‬مت اوت دارن ‪.‬‬

‫خب حاال بیایی ‪ Child Resource‬ایجاد کنی ‪.‬‬

‫‪132‬‬
Child Resource ‫ایجاد‬
‫ نیاز داشاتی حاال برای‬DTO ‫ برای ایجاد یک شارکت به یک‬،CreateCompany ‫در اکشان مت‬
. ‫ایجاد یک کارمن ه ما بای همین کار را انجام دهی‬

. ‫ ایجاد کنی‬EmployeeForCreationDto ‫ یک کالس با نام‬DataTransferObjects ‫در فول ر‬


namespace Entities.DataTransferObjects
{
public class EmployeeForCreationDto
{
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
}

}
CompanyId ‫ را در سمت سرور ایجاد و‬Id ‫ ن اری چو میخواهی‬CompanyId ‫ و‬Id ‫ما لرالر ی‬
. ‫را از طریق مسیر ب رستی‬
[Route("api/companies/{companyId}/employees")]

.‫ است‬IEmployeeRepository ‫مرحله بع ی ایجاد لییرا ی در اینترفیس‬


using Entities.Models;
using System;
using System.Collections.Generic;

namespace Contracts.IServices
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges);

Employee GetEmployee(Guid companyId, Guid id, bool trackChanges);

void CreateEmployeeForCompany(Guid companyId, Employee employee);


}
}
. ‫خب حاال بای این اینترفیس را لیاد سازی کنی‬

133
using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext) : base(companyEmployeeDbContext)
{
}

public IEnumerable<Employee> GetEmployees(Guid companyId, bool


trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId), trackChanges).OrderBy(e => e.Name);

public Employee GetEmployee(Guid companyId, Guid id, bool


trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId) && e.Id.Equals(id),
trackChanges).SingleOrDefault();

public void CreateEmployeeForCompany(Guid companyId, Employee


employee)
{
employee.CompanyId = companyId;
Create(employee);
}
}

}
،MappingProfile ‫ را در اکشان بگیری لس در کالس‬EmployeeDTO ‫چو میخواهی آبجکت‬
. ‫ نیاز داری‬Mapping Rule ‫به یک‬
CreateMap<EmployeeForCreationDto, Employee>();

134
.‫ است‬EmployeesController ‫حاال نوبت به ایجاد یک اکشن مت ج ی در‬

. ‫ لایین را در این کنترلر اضافه کنی‬Namespace ‫ابت ا‬


using Entities.Models;

: CreateEmployeeForCompany ‫اکشن متد‬


[HttpPost]
public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody]
EmployeeForCreationDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForCreationDto object sent from client is
null.");

return BadRequest("EmployeeForCreationDto object is null");


}

var company = _repository.Company.GetCompany(companyId, trackChanges:


false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

var employeeEntity = _mapper.Map<Employee>(employee);


_repository.Employee.CreateEmployeeForCompany(companyId,
employeeEntity);
_repository.Save();

var employeeToReturn = _mapper.Map<EmployeeDto>(employeeEntity);

return
CreatedAtRoute("GetEmployeeForCompany",
new
{
135
companyId,
id = employeeToReturn.Id
},
employeeToReturn);
}
: ‫بررسی کد‬

: ‫ تفاوتهایی دارد‬CreateCompany ‫این اکشن متد با‬

.‫خیر‬ ‫اولین اینکه بای بررسی کنی آیا شرکت موردنظر در دیتابیس وجود دارد یا‬ •

. ‫ است که اال بای دو لارامتر برگردان‬return ‫اوت دوم عبارت‬ •

‫به‬ [HttpGet] ‫را از‬ GetEmployeeForCompany ‫قبل از ست بای ا ربیوت باالی اکشن مت‬
. ‫لییر دهی‬ [HttpGet(Name = "GetEmployeeForCompany")]
[HttpGet(Name = "GetEmployeeForCompany")]
. ‫حاال بیایی این مورد را با ه ست کنی‬
https://localhost:5001/api/companies/3d490a70-94ce-4d15-9494-
5248280c2ce3/employees
body:

{
"name": "Sima Ahmadi",
"age": "28",
"position":"Marketing"
}

136
Parent ‫ همراه با یک‬Child Resource ‫ایجاد‬
‫ایجاد‬ ‫های‬Child ‫ را همرا با‬Parent ‫در اللیکیشاانها موقعیتهایی هساات که بخواهی یک‬
: ‫ به طور مثال‬. ‫کنی‬

‫ می وانی در‬، ‫ها اسات اد کنی‬Child ‫به جای اینکه از ریکوئساتهای متع د برای درج ک ک‬
. ‫ها را ه درج کنی‬Child ،Parent ‫هما ریکوئست درج‬

CompanyForCreationDto ‫لس اولین کااری کاه باایا انجاام دهی این اسااات کاه در کالس‬
.‫لییرا ی انجام شود‬
using System.Collections.Generic;

namespace Entities.DataTransferObjects
{
public class CompanyForCreationDto
{
public string Name { get; set; }
public string Address { get; set; }
public string Country { get; set; }
public IEnumerable<EmployeeForCreationDto> Employees { get; set; }
}

}
‫نیازی نیسات که منطق اکشان مت داخل کنترلر و منطق ریپازیتوری را لییر دهی لس بیایی‬
. ‫با ه این را ه ست کنی‬
https://localhost:5001/api/companies
body:

{
"name": "ُ
Pardazesh",
"address": "Tehran,Tohid",
"country":"Iran",
"employees":[
{
"name":"Sana Sadeghi",
"age": 28,
"position": "IT"
},
{
"name":"Reza Zargham",
"age": 20,

137
‫"‪"position": "IT‬‬
‫}‬
‫]‬
‫}‬

‫همانطور که میبینی این شرکت با موفقیت ایجاد ش ‪.‬‬

‫حاال می وانی لینک ‪ Location‬را از برگه ‪ Headers‬کپی کرد و ساپس قسامت ‪ /employees‬را‬
‫به این لینک اضافه و در ب دیگر ‪ Postman‬لیست کنی ‪.‬‬

‫‪https://localhost:5001/api/companies/085c2ed0-04bd-4980-d5cc-‬‬
‫‪08d893759d60/employees‬‬
‫‪138‬‬
‫ها‬Resource ‫ایجاد مجموعهای از‬
‫ها‬Resource ‫ امتا برای ایجتاد مجموعته از‬.‫ ایجتاد کنیم‬Resource ‫تتا حتاال توانستتتیم یتک‬
.‫چه باید کرد؟ در این قسمت میخواهیم این موضوع را پیادهسازی کنیم‬

return ‫ میبینی که‬، ‫ نگا کنی‬CompaniesController ‫ در‬CreateCompany ‫اگر به اکشن مت‬


.)GetCompany ‫ اشار دارد (اکشن مت‬CompanyById ‫به مسیر‬
[HttpPost]
public IActionResult CreateCompany([FromBody] CompanyForCreationDto
company)
{
//...
return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id
},
companyToReturn);
}

139
‫در دیتابیس را با اساات اد از‬ ‫ می وانی اطالعات درج شاا‬Company ‫ بع از ایجاد‬،‫این یعنی‬
‫را‬ GetCompanyCollection ‫ بناابراین قبال از اینکاه کا هاای‬. ‫برگردانی‬ GetCompany ‫متا‬
. ‫ بهتر است اکشن مت ی برای واکشی اطالعات شرکتها داشته باشی‬، ‫بنویسی‬

. ‫ اضافه کنی‬ICompanyRepository ‫ را به اینترفیس‬GetByIds ‫اول از همه بای مت‬


using Entities.Models;
using System.Collections.Generic;
using System;

namespace Contracts.IServices
{
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
void CreateCompany(Company company);
IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool
trackChanges);

}
}

.‫حاال نوبت به لیاد سازی این اینترفیس است‬


using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
140
}

public IEnumerable<Company> GetAllCompanies(bool


trackChanges) =>
FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToList();

public Company GetCompany(Guid companyId, bool trackChanges) =>


FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefault();

public void CreateCompany(Company company) => Create(company);

public IEnumerable<Company> GetByIds(IEnumerable<Guid> ids,


bool trackChanges) =>
FindByCondition(x => ids.Contains(x.Id), trackChanges)
.ToList();

}
{
. ‫ اضافه نمایی‬CompaniesController ‫خب یک اکشن مت لایین را به‬

. ‫ لایین را قبل از نوشتن اکشن مت در کنترلر اضافه کنی‬Namespace


using System.Linq;

: GetCompanyCollection ‫اکشن متد‬


[HttpGet("collection/({ids})", Name = "CompanyCollection")]
public IActionResult GetCompanyCollection(IEnumerable<Guid> ids)
{
if (ids == null)
{
_logger.LogError("Parameter ids is null");
return BadRequest("Parameter ids is null");
}

141
var companyEntities = _repository.Company.GetByIds(ids, trackChanges:
false);

if (ids.Count() != companyEntities.Count())
{
_logger.LogError("Some ids are not valid in a collection");

return NotFound();
}

var companiesToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);

return Ok(companiesToReturn);
}

‫ را ایجاد‬CreateCompanyCollection ‫ حاال بیایی اکشاان‬.‫این اکشاان مت بساایار ساااد اساات‬


. ‫کنی‬
[HttpPost("collection")]
public IActionResult CreateCompanyCollection([FromBody]
IEnumerable<CompanyForCreationDto> companyCollection)
{
if (companyCollection == null)
{
_logger.LogError("Company collection sent from client is null.");

return BadRequest("Company collection is null");


}

var companyEntities =
_mapper.Map<IEnumerable<Company>>(companyCollection);

foreach (var company in companyEntities)


{
_repository.Company.CreateCompany(company);
}

_repository.Save();

var companyCollectionToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);

142
var ids = string.Join(",", companyCollectionToReturn.Select(c => c.Id));

return CreatedAtRoute("CompanyCollection", new { ids },


companyCollectionToReturn);
}

: ‫بررسی کد‬

‫ اساات یا‬Null ‫برابر‬ CompanyCollection ‫در این اکشاان مت بررساای میکنی که آیا‬ •

‫خیر؟‬
. ‫ برمیگردانی‬BadRequest ‫ باش یک‬Null ‫اگر‬ •

‫ را ماپ و هماه المناتهاای آ را در‬CompanyCollection ‫اگر اینگوناه نبااشااا ماا این‬ •

. ‫دیتابیس ذخیر میکنی‬


‫ (که با‬String ‫ها را در یک‬Id ‫در لایا برای واکشاای شاارکتهایی که ایجاد کردی مام‬ •

. ‫ میفرستی‬Get ‫را) میریوی و به اکشن مت‬ ‫کاما ج ا ش‬

. ‫حاال می وانی این اکشن مت را ست کنی‬


https://localhost:5001/api/companies/collection
body:

[
{
"name": "ُ
Golrang",
"address": "Tehran, Tajrish",
"country":"Iran"
},
{
"name": "ُ
Sader",
"address": "Tehran, SaadatAbad",
"country":"Iran"
}

143
‫خیلی عالی ش اجاز دهی ب ‪ Headers‬را با ه چک کنی ‪.‬‬

‫همانطور که میبینی اینجا یک لینک داری که با آ می وا شااارکتهایی که ج ی ا اضاااافه‬


‫را ببینی ‪.‬‬ ‫ش‬

‫‪144‬‬
‫ چو‬.‫ اسات‬415 Unsupported Media Type ‫اما همانطور که میبینی نتیجهی این اکشان مت‬
. ‫ مپ کن‬IEnumerable<Guid> ‫ نمی وان نو رشته را به آرگوما‬API

. ‫ س ارشی حل میکنی‬Model Binding ‫خب در قسمت بع ی ما این مشکل را با‬

‫ چیست؟‬Model Binding
. ‫ داد ها را از ریکوئست میگیرد و به لارامترهای اکشن مت انتقال میده‬Model Binding

‫ آیا مقادیر اتصتالی معتبر‬: ‫ همیشاه قبل از اکشانمت اجرا و بررسای میکن‬Model Binding
‫است یا خیر؟‬

ModelBinders ‫ یک فول ر‬Infrastructure ‫ ابت ا در فول ر‬،‫ سا ارشای‬Model Binding ‫برای ایجاد‬
. ‫ اضافه نمایی‬ArrayModelBinder ‫ایجاد و در آ یک کالس ج ی با نام‬
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Infrastructure.ModelBinders
{
public class ArrayModelBinder : IModelBinder

145
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!bindingContext.ModelMetadata.IsEnumerableType)
{
bindingContext.Result = ModelBindingResult.Failed();

return Task.CompletedTask;
}

var providedValue = bindingContext.ValueProvider


.GetValue(bindingContext.ModelName)
.ToString();

if (string.IsNullOrEmpty(providedValue))
{
bindingContext.Result = ModelBindingResult.Success(null);

return Task.CompletedTask;
}

var genericType =
bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0
];

var converter = TypeDescriptor.GetConverter(genericType);


var objectArray = providedValue.Split(new[] { "," },
StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();

var guidArray = Array.CreateInstance(genericType,


objectArray.Length);
objectArray.CopyTo(guidArray, 0);
bindingContext.Model = guidArray;

bindingContext.Result =
ModelBindingResult.Success(bindingContext.Model);

return Task.CompletedTask;

146
‫}‬
‫}‬

‫}‬
‫بررسی کد ‪:‬‬

‫ایجااد میکنی ‪ ،‬لس باایا‬ ‫‪Model Binder‬‬ ‫داری یاک‬ ‫‪IEnumerable‬‬ ‫چو برای نو‬ ‫•‬

‫بررسی کنی که لارامتر ما از نو ‪ IEnumerable‬است یا خیر؟‬


‫در مرحله بع با است اد از مت ‪ ،GetValue‬مق ار رشته ‪ GUID‬را واکشی میکنی ‪.‬‬ ‫•‬

‫‪ Null‬یا ‪ Empty‬باشا ‪ ،‬بای ‪ Null‬را برگردانی در غیر‬ ‫حاال در صاور یکه مق ار ب سات آم‬ ‫•‬

‫اینصورت‪ ،‬ادامه میدهی ‪.‬‬


‫را‪ ،‬در متلیر‬ ‫خب حاال با کمک ‪ ،Reflection‬نوعی که ‪ IEnumerable‬از آ شاکیل شا‬ ‫•‬

‫‪ genericType‬میریوی ‪.‬‬
‫‪Converter‬‬ ‫ساپس بررسای میکنی که نو متلیر ‪ genericType‬چیسات و برای آ نو‬ ‫•‬

‫را ایجاد میکنی ‪.‬‬


‫لس از آ بای آرایهای از نو ‪ objectArray‬که شاامل مقادیر ‪( GUID‬که به ‪ API‬رسای )‬ ‫•‬

‫است را ایجاد کنی ‪.‬‬


‫حااال آرایاهای از نو ‪ GuidArray‬ایجااد و ساااپس ماام مقاادیر را از ‪ objectArray‬باه‬ ‫•‬

‫‪ GuidArray‬کپی میکنی ‪.‬‬


‫در لایا بای این مق ار را به ‪ bindingContext‬اختصاا دهی ‪.‬‬ ‫•‬

‫ایاجاااد کانایا‬ ‫‪GetCompanyCollection‬‬ ‫حاااال بااای ا ی اک الایایار جاوئای در اکشاااان‬


‫ا ‪ArrayModelBinder‬قبل از اجرای اکشنمت شرو به کار کن ‪.‬‬

‫ابت ا ‪ Namespace‬لایین را در ‪ CompaniesController‬اضافه کنی ‪.‬‬


‫;‪using CompanyEmployee.API.Infrastructure.ModelBinders‬‬

‫سپس اکشن مت لایین را لییر دهی ‪.‬‬


‫])"‪[HttpGet("collection/({ids})", Name = "CompanyCollection‬‬
‫= ‪public IActionResult GetCompanyCollection([ModelBinder(BinderType‬‬
‫)‪typeof(ArrayModelBinder))]IEnumerable<Guid> ids‬‬
‫{‬

‫‪147‬‬
if (ids == null)
{
_logger.LogError("Parameter ids is null");

return BadRequest("Parameter ids is null");


}

var companyEntities = _repository.Company.GetByIds(ids, trackChanges:


false);

if (ids.Count() != companyEntities.Count())
{
_logger.LogError("Some ids are not valid in a collection");

return NotFound();
}

var companiesToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);

return Ok(companiesToReturn);
}
‫ب یل و ساپس‬ IEnumerable <Guid> ‫ را به نو‬API ‫به‬ ‫ رشاته ارساال شا‬،ModelBinder ‫این‬
.‫این اکشنمت اجرا میشود‬

. ‫ اجرا کنی‬Postman ‫یکبار دیگر ریکوئست قبلی را با‬

148
Delete ‫ایجاد اکشن متد‬
. ‫ را لیاد سازی کنی‬Delete ‫ اکشن‬Child Resource ‫بیایی با حذف یک‬

. ‫ اضافه کنی‬IEmployeeRepository ‫ را به اینترفیس‬DeleteEmployee ‫مت‬ •

using Entities.Models;
using System;
using System.Collections.Generic;

namespace Contracts.IServices
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges);

Employee GetEmployee(Guid companyId, Guid id, bool trackChanges);

void CreateEmployeeForCompany(Guid companyId, Employee employee);

void DeleteEmployee(Employee employee);


}
}
.‫ است‬EmployeeRepository ‫گام بع ی لیاد سازی این اینترفیس در کالس‬ •

using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext) : base(companyEmployeeDbContext)
{
}

149
public IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId), trackChanges).OrderBy(e => e.Name);

public Employee GetEmployee(Guid companyId, Guid id, bool


trackChanges) => FindByCondition(e =>
e.CompanyId.Equals(companyId) && e.Id.Equals(id),
trackChanges).SingleOrDefault();

public void CreateEmployeeForCompany(Guid companyId, Employee


employee)
{
employee.CompanyId = companyId;
Create(employee);
}

public void DeleteEmployee(Employee employee)


{
Delete(employee);
}

}
. ‫اضافه کنی‬ EmployeesController ‫ را به‬Delete ‫در مرحله لایانی بای اکشن مت‬ •

[HttpDelete("{id}")]
public IActionResult DeleteEmployeeForCompany(Guid companyId, Guid id)
{
var company = _repository.Company.GetCompany(companyId,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

150
var employeeForCompany =
_repository.Employee.GetEmployee(companyId, id, trackChanges:
false);

if (employeeForCompany == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}

_repository.Employee.DeleteEmployee(employeeForCompany);
_repository.Save();

return NoContent();
}

: ‫بررسی کد‬

id ‫ را از مسااایر اصااالی و‬companyId ‫ ما‬.‫چیو ج ی ی در این اکشااانمت وجود ن ارد‬ •

. ‫میگیری‬ ‫کارمن را از آرگوما که لاس داد ش‬


. ‫در اینجا بای چک کنی که شرکت و کارمن مورد نظر وجود داشته باش‬ •

Status Code 204 ‫حااوی‬ ‫ را کاه‬NoContent ‫در لاایاا کاارمنا موردنظر را حاذف و متا‬ •

. ‫است بر میگردانی‬

. ‫بیایی این اکشن مت را ست کنی‬


https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees/80abbca8-664d-4b20-b5de-024705497d4a

151
‫نکته!!‬

‫مقتدار ‪ companyId‬و ‪ id‬کته در این تستتت میبینیتدبتا مقتادیری کته در دیتتابیس شتتمتا وجود دارد‬
‫متفاوت استت؛ چون این مقادیر ‪ GUID‬هستتند و هر بار یک مقدار متفاوت دارند‪ .‬پس برای تستت‬
‫این گونه مثال ها از مقادیر معتبر استفاده کنید‪.‬‬

‫خواه ش ‪.‬‬ ‫حاال اگر بخواهی که این کارمن را از دیتابیس بگیری مطملنا ‪ 404‬برگردان‬
‫‪https://localhost:5001/api/companies/0AD5B971-FF51-414D-AF01-‬‬
‫‪34187E407557/employees/DE662003-ACC3-‬‬
‫‪4F9F-9D82-0A74F64594C1‬‬

‫همانطور که میبینی این ریکوئست را هر چن بار دیگر ه ارسال کنی هما ‪ 404‬را میگیری ‪.‬‬
‫در نتیجه ‪ DELETE Request‬همیشه ‪ Idempotent‬است‪.‬‬

‫حذف یک ‪ Parent Resource‬همراه با ‪Child‬هایش‬


‫‪Entity Framework‬‬ ‫همرا باا ‪Child‬هاای ‪ ،‬می وانی از‬ ‫‪Parent Resource‬‬ ‫برای حاذف یاک‬
‫‪ Cascade‬دارد کاه باا آ‬ ‫‪deleting‬‬ ‫‪ Core‬کماک بگیری ‪ .‬این ‪ ORM‬یاک نظیماات لاایاه باه ناام‬
‫می وا مشااخش کنی که با حذف یک ‪ ،Parent‬به صااورت ا وما یک مام ‪Child‬ها ه حذف‬
‫شون ‪.‬‬

‫‪152‬‬
. ‫ را همانن لایین لییر دهی‬EmployeeConfiguration ‫ ک های کالس‬،‫برای اعمال این قابلیت‬
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;

namespace Entities.Configuration
{
public class EmployeeConfiguration :
IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.HasOne("Entities.Models.Company", "Company")
.WithMany("Employees")
.HasForeignKey("CompanyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();

builder.HasData
(
new Employee
{
Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
Name = "Zahra Bayat",
Age = 26,
Position = "Backend developer",
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-
2d54a9991870")
},
new Employee
{
Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
Name = "Ali Bayat",

153
Age = 30,
Position = "Backend developer",
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-
2d54a9991870")
},
new Employee
{
Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
Name = "Sara Bayat",
Age = 35,
Position = "Frontend developer",
CompanyId = new Guid("3d490a70-94ce-4d15-9494-
5248280c2ce3")
}
);
}

}
}
‫ لس‬.‫ اسات‬Parent Resource ‫حاال نها کاری که بای انجام دهی ایجاد یک منطق برای حذف‬
. ‫ اضافه کنی‬ICompanyRepository ‫ به اینترفیس‬Delete ‫ یک مت‬،‫مانن مثال قبل‬
using Entities.Models;
using System.Collections.Generic;
using System;

namespace Contracts.IServices
{
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
void CreateCompany(Company company);
IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool
trackChanges);
void DeleteCompany(Company company);
}
}

154
. ‫ را لییر دهی‬CompanyRepository ‫سپس کالس‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }

public IEnumerable<Company> GetAllCompanies(bool trackChanges)


=>
FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToList();

public Company GetCompany(Guid companyId, bool trackChanges) =>


FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefault();

public void CreateCompany(Company company) => Create(company);

public IEnumerable<Company> GetByIds(IEnumerable<Guid> ids,


bool trackChanges) =>
FindByCondition(x => ids.Contains(x.Id), trackChanges)
.ToList();

public void DeleteCompany(Company company)


{
Delete(company);
}
155
}
}
. ‫ اضافه نمایی‬CompaniesController ‫را به‬ DeleteCompany ‫در لایا اکشن مت‬
[HttpDelete("{id}")]
public IActionResult DeleteCompany(Guid id)
{
var company = _repository.Company.GetCompany(id, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in the
database.");

return NotFound();
}

_repository.Company.DeleteCompany(company);
_repository.Save();

return NoContent();
}

. ‫خب حاال بیایی این اکشن مت را ست کنی‬


https://localhost:5001/api/companies/79B87894-E747-4B5E-8BDC-08D891D8177E

156
‫همانطور که میبینی این اکشاانمت ه به خوبی کار می کن ‪ .‬حاال در دیتابیس بررساای کنی‬
‫است یا خیر؟‬ ‫حذف ش‬ ‫که این شرکت همرا با کارمن ان‬

‫مام ش و بیایی ‪ PUT request‬را بررسی کنی ‪.‬‬ ‫‪DELETE Request‬‬ ‫خب ست‬

‫آپدیت ‪Employee‬‬
‫قبال ابتا ا اینترفیس و کالس ‪ Repository‬را لییر دادی ‪ .‬ساااپس متا ‪ Delete‬را باه‬ ‫در بخ‬
‫کالس کنترلر اضافه کردی ‪ .‬اما برای آل یت این مراحل کمی فرق دارد‪.‬‬

‫بیایی مرحله به مرحله این کار را انجام دهی ‪.‬‬

‫اولین کاری که بای انجام دهی این اسات که در فول ر ‪ DataTransferObjects‬یک کالس با نام‬
‫‪ EmployeeForUpdateDto‬ایجاد کنی ‪.‬‬
‫‪namespace Entities.DataTransferObjects‬‬
‫{‬
‫‪public class EmployeeForUpdateDto‬‬
‫{‬
‫} ;‪public string Name { get; set‬‬
‫} ;‪public int Age { get; set‬‬
‫} ;‪public string Position { get; set‬‬
‫}‬

‫}‬
‫‪URI‬‬ ‫باه لرالر ی ‪ Id‬نیااز نا اری چو در ‪ Update‬ه مااننا ‪ ،DELETE‬این لرالر ی باایا از طریق‬
‫گرفته شاود‪ .‬همچنین این ‪ DTO‬مشاابه ‪ EmployeeForCreationDto‬اسات‪ .‬نها اوت این دو‬
‫کالس در م هوم آ هاست یکی از کالسها برای ایجاد است و دیگری برای آل یت کاربرد دارد‪.‬‬

‫‪157‬‬
‫ اضاافه کردی لس بای عمل مپ کرد را ه انجام دهی بنابراین ک‬DTO ‫چو ما یک کالس‬
. ‫ اضافه کنی‬MappingProfile ‫لایین را به کالس‬
CreateMap<EmployeeForUpdateDto, Employee>();
EmployeesController ‫را باه‬ UpdateEmployeeForCompany ‫خاب حااال باایا اکشااان متا‬
. ‫اضافه کنی‬
[HttpPut("{id}")]
public IActionResult UpdateEmployeeForCompany(Guid companyId, Guid id,
[FromBody] EmployeeForUpdateDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForUpdateDto object sent from client is
null.");

return BadRequest("EmployeeForUpdateDto object is null");


}

var company = _repository.Company.GetCompany(companyId, trackChanges:


false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

var employeeEntity = _repository.Employee.GetEmployee(companyId, id,


trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}

_mapper.Map(employee, employeeEntity);

158
‫;)(‪_repository.Save‬‬

‫;)(‪return NoContent‬‬
‫}‬

‫بررسی کد ‪:‬‬

‫در این اکشاان مت از ا ربیوت ‪ PUT‬با لارامتر ‪ Id‬اساات اد کردی ‪ .‬این یعنی ‪ :‬مساایر ما‬ ‫•‬

‫برای این اکشن مت برابر }‪ api/companies/{companyId}/employees/{id‬میباش ‪.‬‬


‫در ک باال‪ ،‬سه جا عمل چک کرد داری که مطملنا برای همه شما آشنا است‪.‬‬ ‫•‬

‫نها ک ی که کمی مت اوت اسات نوو واکشای ‪ company‬و ‪ employeeEntity‬میباشا ‪.‬‬ ‫•‬

‫ا هر لرالر ی‬ ‫نظی شا‬ ‫‪true‬‬ ‫برای ‪ ،employeeEntity‬بر روی‬ ‫‪trackChanges‬‬ ‫لارامتر‬


‫لییر ده ‪.‬‬ ‫‪Modified‬‬ ‫وضعیت آ را به‬ ‫‪EF Core‬‬ ‫لییر کرد‪،‬‬ ‫‪Entity‬‬ ‫این‬
‫در لاایاا این کا میبینیا ‪ ،‬آبجکات ‪ employee‬را باه ‪ employeeEntity‬ماپ کردی‬ ‫•‬

‫لییر میاب ‪.‬‬ ‫‪Modified‬‬ ‫بنابراین ‪ state‬آبجکت ‪ employeeEntity‬به‬


‫حاال با ص ا زد ‪ Entity ،Save‬در دیتابیس آل یت میشود‪.‬‬ ‫•‬

‫میشود‪.‬‬ ‫در نهایت ‪ NoContent‬برگردان‬ ‫•‬

‫حاال می وانی این اکشن را ست بونی ‪.‬‬


‫‪https://localhost:5001/api/Companies/3D490A70-94CE-4D15-9494-‬‬
‫‪5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863‬‬
‫‪body:‬‬

‫{‬
‫‪"name": "Sima Ahmadi",‬‬
‫‪"age": 25,‬‬
‫"‪"position": "Marketing‬‬
‫}‬

‫‪159‬‬
‫در ریکوئست باال لرالر ی ‪ Age‬از ‪ 28‬به ‪ 25‬لییر کرد‪.‬‬

‫همانطور که میبینی این اکشن مت کار میکن و ما ‪ No Content‬میگیری ‪.‬‬

‫نامعتبر ارسااال کنی بای ریسااپانس‬ ‫‪EmployeeId‬‬ ‫اگر همین ریکوئساات را با ‪ CompanyId‬یا‬
‫‪ 404‬دریافت کنی ‪.‬‬

‫نکته‪:‬‬

‫همانطور که دیدید ما میخواستتیم فقط پراپرتی ‪ Age‬را تغییر دهیم اما تمام پراپرتیها با مقادیری‬
‫که در دیتابیس داشتند را‪ ،‬هم ارسال کردیم‪.‬‬

‫در اینجا نکتهای هست که باید بدانید‪.‬‬

‫پراپرتی ‪Age‬‬ ‫‪ PUT‬یک ریکوئستتت برای آپدیت کامل استتت بنابراین اگر ریکوئستتت باال را تنها با‬
‫(بدون باقی پراپرتیها ) هم می فرستتادیم‪ ،‬باز تمام پراپرتیها روی مقادیر پیش فرضتشتان تنظیم‬
‫میشدند‪ .‬بنابراین در ریکوئست ‪ PUT‬نیاز به ارسال پارامترهای اضافه نیست‪.‬‬

‫انواع روشهای ‪Update‬‬


‫‪RepositoryBase‬‬ ‫احتماال این سااوال در ذهن شااما ه هساات که چرا از مت ‪ Update‬کالس‬
‫است اد نمیکنی ؟‬

‫‪160‬‬
‫قبل از لاسخ به این سوال‪ ،‬بگذاری به دو روش ‪ Update‬اطالعات اشار ای داشته باش ‪:‬‬

‫‪object‬‬ ‫‪ :Connected‬یعنی برای واکشتتی و آپتدیتت ‪ ،Entity‬از یتک‬ ‫‪Update‬‬ ‫•‬

‫‪ Context‬مشترک استفاده شود‪.‬‬


‫‪object‬‬ ‫‪ :Disconnected‬یتعتنتی بترای واکشتتی و آپتدیتت ‪ ،Entity‬از‬ ‫‪Update‬‬ ‫•‬

‫‪Context‬های مرتلف استفاده نمود‪.‬‬

‫‪Update‬‬ ‫چو شرایط اکشنمت ‪ UpdateEmployeeForCompany‬طوری بود که بای به صورت‬


‫‪ Connected‬آ را لیاد میکردی ‪ ،‬بنابراین از مت ‪ Update‬کالس ‪ RepositoryBase‬اسااات اد‬
‫نکردی ‪ .‬البته گاهی نیاز است که از ‪ Disconnected Update‬است اد شود‪ .‬به طور مثال ‪:‬‬

‫یاک آبجکات همرا باا ‪ ،Id‬از کالینات دریاافات کردی و نیاازی باه واکشااای اطالعاات از دیتاابیس‬
‫ن اری ‪ .‬در این شارایط‪ ،‬نها کاری که بای انجام دهی این اسات که به ‪ EF Core‬اطال دهی ا‬
‫لییرات موجود در ‪ Entity‬را ‪ Track‬و ‪ State‬آ را ‪ Modified‬کن ‪ .‬بنابراین می وانی هر دو عمل‬
‫را با است اد از مت ‪ Update‬کالس ‪ RepositoryBase‬انجام دهی ‪.‬‬

‫ایجاد ‪ Resource‬در هنگام آپدیت یک ‪Resource‬‬


‫یت ‪Parent‬‬ ‫ما می وانی ب و نوشاتن هیچ گونه ک اضاافهای‪ Child Resource ،‬را در هنگام آل‬
‫‪ Resource‬ایجاد کنی ‪ EF Core .‬در این لروسه به ما کمک بورگی میکن ‪.‬‬

‫بیایی ببینی این کار چطور انجام میشود‪.‬‬

‫اولین کااری کاه باایا انجاام دهی ‪ ،‬ایجااد یاک کالس ‪ DTO‬برای آلا یات اسااات‪ .‬بناابراین در فولا ر‬
‫‪ DataTransferObjects‬یک کالس با نام ‪ CompanyForUpdateDto‬ایجاد نمایی ‪.‬‬
‫;‪using System.Collections.Generic‬‬

‫‪namespace Entities.DataTransferObjects‬‬
‫{‬
‫‪public class CompanyForUpdateDto‬‬
‫{‬
‫} ;‪public string Name { get; set‬‬
‫} ;‪public string Address { get; set‬‬
‫} ;‪public string Country { get; set‬‬
‫} ;‪public IEnumerable<EmployeeForCreationDto> Employees { get; set‬‬
‫‪161‬‬
}

}
. ‫ اضافه کنی‬MappingProfile ‫ ج ی در کالس‬Mapping ‫بع از این مرحله یک‬
CreateMap<CompanyForUpdateDto, Company>();

. ‫ را ایجاد کنی‬UpdateCompany ‫ اکشن مت‬CompaniesController ‫خب حاال بای در‬


[HttpPut("{id}")]
public IActionResult UpdateCompany(Guid id, [FromBody] CompanyForUpdateDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForUpdateDto object sent from client is
null.");

return BadRequest("CompanyForUpdateDto object is null");


}

var companyEntity = _repository.Company.GetCompany(id, trackChanges:


true);

if (companyEntity == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in the
database.");

return NotFound();
}

_mapper.Map(company, companyEntity);
_repository.Save();

return NoContent();
}

‫ لس بیایی‬.‫ است‬UpdateEmployeeForCompany ‫ مشابه‬، ‫همانطور که میبینی این اکشانمت‬


. ‫این مورد را ه ست کنی‬
https://localhost:5001/api/companies/3D490A70 -94CE-4D15-9494-5248280C2CE3
body:

162
‫‪"name": "IDP_Ltd",‬‬
‫‪"address": "Tehran, Vanak",‬‬
‫‪"country":"Iran",‬‬
‫[‪"employees":‬‬
‫{‬
‫‪"name": "Sara Mahdavi",‬‬
‫‪"age": 25,‬‬
‫"‪"position": "IT‬‬
‫}‬
‫]‬

‫}‬

‫در ریکوئسات باال‪ ،‬نام شارکت را لییر و یک کارمن را نیو ‪ Attach‬کردی ‪ .‬در نتیجه همانطور که‬
‫است‪.‬‬ ‫میشود؛ یعنی اینکه ‪ Entity‬ما آل یت ش‬ ‫میبینی ‪ 204‬برگردان‬

‫آیا با ریکوئست باال کارمند جدید اضافه شد؟ بیایید کوئری خود را بررسی کنیم‪.‬‬

‫‪163‬‬
‫اساات چو ما ‪ Company Entity‬را‬ ‫همانطور که میبینی این کارمن در دیتابیس ایجاد شاا‬
‫‪ Track‬کردی ‪ .‬بنابراین ‪ EF Core‬به موض اینکه ‪ Mapping‬انجام شاود‪ Company → state ،‬بر‬
‫نظی و برای ‪ Employees‬ه ‪ State‬را بر روی ‪ Added‬میگاذارد‪ .‬حااال بعا از‬ ‫‪Modified‬‬ ‫روی‬
‫اینکه مت ‪ Save‬صا ا زد شاود‪ ،‬عالو بر آل یت شارکت‪ ،‬کارمن ج ی ه در دیتابیس اضاافه‬
‫میشود‪.‬‬

‫کار ما با ‪PUT Request‬ها مام شا ‪ .‬در قسامت بع ی میخواهی ‪ PATCH Request‬را بررسای‬
‫کنی ‪.‬‬

‫‪164‬‬
‫فصل هفتم ‪ Patch Request :‬و اعتبارسنجی‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ کار کردن با ‪Patch Request‬‬

‫➢ اعتبارسنجی در زمان ایجاد و حذف ‪Resource‬‬


‫کار کردن با ‪Patch Request‬‬
‫در فصال قبل با ‪ PUT Request‬کار کردی ‪ PUT Request .‬برای آل یت کامل اسات اد میشا‬
‫اما اگر بخواهی بخشی از ‪ Resource‬را آل یت کنی بای از ‪ PATCH Request‬است اد کنی ‪.‬‬

‫‪:‬‬ ‫‪ PUT‬و ‪PATCH‬‬ ‫تفاوت بین‬

‫در ‪ PUT‬همیشتته ‪Resource‬بته صتتورت کتامتل آپتدیتت میشتتود در حتالیکته در‬ ‫•‬

‫‪ PATCH‬قسمتی از ‪ Resource‬آپدیت خواهد شد‪.‬‬


‫‪PATCH Request :‬‬ ‫‪ Request‬نیز در هر کتدام متفتاوت استتت‪ .‬برای مثتال‬ ‫‪Body‬‬ ‫•‬

‫>‪[FromBody]JsonPatchDocument<Company‬‬ ‫بترای ‪ ،Company‬متطتابتق‬


‫نوشتته میشتود در حالیکه برای ‪ PUT‬باید از ‪ [FromBody]Company‬استتفاده‬
‫کنیم‪.‬‬
‫‪PATCH‬‬ ‫‪ PUT‬باید ‪ application/json‬باشتتد اما در‬ ‫‪Request‬‬ ‫‪ Media‬برای‬ ‫‪Type‬‬ ‫•‬

‫‪ Request‬باید از ‪ application/json-patch+json‬استفاده کرد‪.‬‬

‫نکته!!‬
‫‪ ASP.NET‬برای ‪ application/json - PATCH Request‬هم پذیرفته میشتتود‬ ‫‪Core‬‬ ‫در‬
‫اما استتانداردهای ‪ REST‬توصتیه میکنند از ‪ application/json-patch+json‬استتفاده‬
‫کنید‪.‬‬
‫بیایی ببینی ب نه ‪ PATCH Request‬چه شکلی است‪.‬‬
‫[‬
‫{‬
‫‪"op": "replace",‬‬
‫‪"path": "/name",‬‬
‫"‪"value": "new name‬‬
‫‪},‬‬
‫{‬
‫‪"op": "remove",‬‬
‫"‪"path": "/name‬‬
‫}‬
‫]‬

‫‪166‬‬
‫عالمات براکات‪ ،‬مجموعاهای از عملیاات را نشاااا میدها ‪ .‬هر عمال باایا بین آکوالد قرار‬ ‫•‬

‫‪op‬‬ ‫را داری کاه کاه باا لرالر ی‬ ‫‪Remove‬‬ ‫گیرد‪ .‬بناابراین در این مثاال دو عمال ‪ Replace‬و‬
‫مشخش ش ان ‪.‬‬
‫‪ path‬نمایانگر لرالر ی است که میخواهی آ را لییر دهی ‪.‬‬ ‫•‬

‫در این مثاال برای عمال ‪ Replace‬ماا مقا ار لرالر ی ‪ name‬را باا یاک مقا ار جا یا جاایگوین‬
‫فرض نظی‬ ‫میکنی و در دومی ماا لرالر ی ‪ name‬را حاذف و مقا ار آ را باه صاااورت لی‬
‫کرد‪.‬‬ ‫خواهی‬

‫شش ریکوئست مرتلف برای ‪ PATCH‬وجود دارد ‪:‬‬

‫‪OPERATION‬‬ ‫‪REQUEST BODY‬‬ ‫توضیحات‬


‫{‬
‫‪"op": "add",‬‬
‫به پراپرتی مقدار جدیدی داده‬
‫‪Add‬‬ ‫‪"path": "/name",‬‬
‫میشود‪.‬‬
‫"‪"value": "new value‬‬
‫}‬
‫{‬
‫‪"op": "remove",‬‬
‫برای پراپرتی مقتدار پیشفرض‬
‫‪Remove‬‬ ‫"‪"path": "/name‬‬
‫}‬
‫تنظیم میشود‪.‬‬
‫{‬
‫‪"op": "replace",‬‬
‫مقتدار یتک پراپرتی را بتا یتک‬
‫‪Replace‬‬ ‫‪"path": "/name",‬‬
‫مقدار جدید جایگزین میکند‪.‬‬
‫"‪"value": "new value‬‬
‫}‬
‫{‬
‫‪"op": "copy",‬‬
‫مقدار را از یک پراپرتی به یک‬
‫‪Copy‬‬ ‫‪"from": "/name",‬‬
‫پراپرتی دیگر کپی میکند‪.‬‬
‫"‪"path": "/title‬‬
‫}‬
‫{‬ ‫مقدار را از یک پراپرتی به‬
‫‪"op": "move",‬‬
‫‪Move‬‬ ‫‪"from": "/name",‬‬ ‫یک پراپرتی دیگر جابجا می‪-‬‬
‫"‪"path": "/title‬‬
‫}‬ ‫کند‪.‬‬
‫{‬
‫‪"op": "test",‬‬
‫تستتت میکنتد آیتا پراپرتی‬
‫‪Test‬‬ ‫‪"path": "/name",‬‬
‫شده را دارد؟‬ ‫مقدار مشر‬
‫"‪"value": "new value‬‬
‫}‬

‫خب حاال بیایی وارد ک نویسی شوی ‪.‬‬

‫‪167‬‬
Employee Entity ‫ به‬Patch ‫اضافه کردن‬
.‫قبل از اینکه کنترلر را تغییر دهیم باید دو پکیج نصب کنیم‬

JsonPatchDocument ‫ برای لشاااتیبانی از‬Microsoft.AspNetCore.JsonPatch ‫لکیح‬ •

.‫است اد میشود‬
Request Body ‫ برای با یال‬Microsoft.AspNetCore.Mvc.NewtonsoftJson ‫و لکی‬ •

.‫ (لس از ارسال ریکوئست) است‬PatchDocument ‫به‬


Install-Package Microsoft.AspNetCore.JsonPatch -Version 5.0.2 -
ProjectName CompanyEmployee.API

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 5.0.2 -


ProjectName CompanyEmployee.API

. ‫ اضافه کنی‬AddControllers ‫ را به‬NewtonsoftJsonconfiguration ‫ بای‬،‫لس از ا مام نصب‬


services.AddControllers(config => {
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
}).AddNewtonsoftJson()
.AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();

AddCustomCSVFormatter ‫ را قبال از‬AddNewtonsoftJson ‫وجاه داشاااتاه بااشااایا کاه متا‬


. ‫اضافه نمایی‬

168
‫ داشااته‬EmployeeForUpdateDto ‫ به‬Employee ‫ از نو‬Mapping ‫خب حاال نیاز اساات ا یک‬
‫از‬ Mapping ‫ میبینیا کاه یاک‬، ‫بینا ازیا‬ MappingProfile ‫ اگر نگااهی باه کالس‬. ‫بااشااای‬
Rule ‫ وجود دارد؛ لس دیگر نیاازی باه اضاااافاه کرد‬Employee ‫ باه‬EmployeeForUpdateDto
. ‫ است اد کنی‬ReverseMap ‫ فقط کافیست که از مت‬، ‫ج ی ن اری‬
CreateMap<EmployeeForUpdateDto, Employee>().ReverseMap();

.‫ است اد میشود‬Mapping ‫ برای معکوس کرد‬ReverseMap ‫مت‬

EmployeesController ‫را باه‬ PartiallyUpdateEmployeeForCompany ‫حااال می وانی متا‬


. ‫اضافه کنی‬

. ‫ اضافه کنی‬EmployeesController ‫ را در‬Namespace ‫ابت ا این‬


using Microsoft.AspNetCore.JsonPatch;

. ‫و سپس مت لایین را بنویسی‬


[HttpPatch("{id}")]
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId,
Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is null.");

return BadRequest("patchDoc object is null");


}

var company = _repository.Company.GetCompany(companyId, trackChanges:


false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

169
var employeeEntity = _repository.Employee.GetEmployee(companyId, id,
trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}

var employeeToPatch =
_mapper.Map<EmployeeForUpdateDto>(employeeEntity);
patchDoc.ApplyTo(employeeToPatch);
_mapper.Map(employeeToPatch, employeeEntity);
_repository.Save();

return NoContent();
}

: ‫بررسی کد‬

‫ ما‬.‫ مت اوت اساات‬PUT ‫ این اکشاان مت با اکشاان مت‬Signature ‫همانطور که میبینی‬ •

. ‫ میگیری‬Request Body ‫ را از‬JsonPatchDocument


BadRequest ‫ اسااات یاک‬null ‫ برابر‬patchDoc ‫لس از آ بررسااای میکنی کاه اگر‬ •

. ‫ب رست‬
‫ در دیتاابیس وجود‬employee ‫ و‬company ‫در خطهاای بعا ی باایا مطملن شاااوی کاه‬ •

.‫دارد یا خیر‬
‫ اعماال شاااود‬EmployeeForUpdateDto ‫ فقط می وانا باه نو‬patchDoc ‫چو متلیر‬ •

. ‫ مپ میکنی‬EmployeeForUpdateDto ‫ را به‬Employee ‫لس نو‬


‫ ماپ میکنی اا بتوانی‬employeeEntity ‫ را باه‬employeeToPatch ‫در لاایاا دوباار‬ •

. ‫لییرات را در دیتابیس ذخیر کنی‬

. ‫ دو ریکوئست لایین را ب رستی‬،‫حاال برای ست‬

. ‫ را ارسال کنی‬Replace ‫ابت ا عملیات‬

170
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

[
{
"op": "replace",
"path": "/age",
"value": "28"
}
]

‫لییر کرد‬ Employee ‫ حااال بیااییا‬. ‫ را گرفتی‬204 No Content ‫هماانطور کاه میبینیا ماا لیاام‬
. ‫را چک کنی‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863

171
‫ بیایی یک عمل حذف را در یک ریکوئست‬.‫لییر کرد است‬ Age ‫همانطور که میبینی لرالر ی‬
. ‫ب رستی‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

[
{
"op": "remove",
"path": "/age"
}
]

172
‫این عمل ه به درساتی کار کرد‪ .‬حال اگر کارمن خود را ‪ Get‬کنی ‪ ،‬لرالر ی ‪ Age‬در ع د صا ر‬
‫فرض برای نو ‪:)int‬‬ ‫نظی میشود (مق ار لی‬

‫در لایا بیایی برای لرالر ی ‪ Age‬مق ار ‪ 28‬را برگردانی ‪.‬‬


‫‪https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-‬‬
‫‪5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863‬‬
‫‪body:‬‬

‫[‬
‫{‬
‫‪"op": "add",‬‬
‫‪"path": "/age",‬‬
‫"‪"value": "28‬‬
‫}‬
‫]‬

‫‪173‬‬
‫بیایی با ه ‪ Employee‬را چک کنی ‪.‬‬

‫عالی ش همه چیو خیلی خوب کار کرد‪.‬‬

‫اعتبارسنجی چیست؟‬
‫داد ها قبل از ارساال بای اعتبارستنجی شاون و این موضاو خیلی مهمی در هخیرهستازی‬
‫اساات‪.‬‬ ‫اطالعات اساات‪ .‬برای حل این مساالله‪ ،‬اساات اد از ‪DataAnnotation‬ها مطرح شاا‬
‫‪DataAnnotation‬ها به شاااما این امکا را میدهن ا‪ ،‬قوانینی مشاااخش کنی و لرالر یها در‬
‫‪ Model‬مطابق با این قوانین عمل نماین ‪.‬‬

‫شایو ی کار ب ین صاورت است که‪DataAnnotation ،‬ها ‪Metadata‬هایی برای وصی داد آماد‬
‫میکنن و داد ها بای از قوانین این ‪Metadata‬ها لیروی نماین ‪.‬‬

‫به عنوان مثال‪:‬‬

‫به جای اینکه ‪ null‬بود داد ها به صاورت دساتی چک شاود‪ ،‬ا ربیوت ]‪ [Required‬را باالی‬
‫لرالر یهای خود بگذاری و دیگر خیالتا راحت باش که نمی وا داد ی ‪ Null‬وارد نمود‪.‬‬

‫برخی ‪DataAnnotation‬ها ‪:‬‬

‫]‪ : [ EmailAddress‬برای اعتبارسنجی فرمت ایمیل‪.‬‬ ‫•‬

‫‪174‬‬
‫])‪ : [MinLength(min‬بررسی حداقل کاراکتر وارد شده‪.‬‬ ‫•‬

‫]‪ : [Phone‬برای اعتبارسنجی فرمت تلفن‪.‬‬ ‫•‬

‫]‪ : [Required‬ورود مقدار برای پراپرتی اجباریست‪.‬‬ ‫•‬

‫])‪ : [Range(min, max‬بررسی مقدار بین حداقل و حداکثر‪.‬‬ ‫•‬

‫])"" = ‪ : [Display(Name‬نام سفارشی پراپرتی برای نمایش در ‪.View‬‬ ‫•‬

‫برای اعتبارسانجی در زما ایجاد یا آل یت ‪ Resource‬از ا ربیوتها اسات اد میشاود؛ یعنی این‬
‫اعتبارسنجیها در ریکوئستهای ‪ PUT ،POST‬و ‪ PATCH‬کاربرد دارن ‪.‬‬

‫اعتبارسانجی قبل از اجرای اکشانمت ‪ ،‬رد میده اما توجه داشتته باشتید که اکشانمت ها در‬
‫هر صاورت (چه اعتبارسانجی موفق باشا چه نباشا ) اجرا خواهن شا بنابراین م یریت داد های‬
‫معتبر بر عه ی اکشنمت است‪.‬‬

‫کالس ‪ControllerBase‬‬ ‫برای بررسای اعتبارسانجی‪ ،‬می وا از لرالر ی ‪ ModelState‬موجود در‬


‫اسااات ااد کنی ‪ ModelState .‬یاک دیکشااانری اسااات کاه حااوی لیساااتی از ماام خطااهاای‬
‫اعتبارسنجی میباش ‪.‬‬

‫با ‪ Data Annotation‬بررسای‬ ‫هنگامی که ریکوئسات خود را ارساال میکنی ‪ ،‬قوانین عری شا‬
‫باش ‪ ،‬لیام خطای مناسب بازگشت داد خواه ش ‪.‬‬ ‫می شون ‪ .‬اگر یکی از قوانین اعمال نش‬

‫‪175‬‬
‫ولیدیشن در زمان ایجاد ‪Resource‬‬
‫‪CreatEmployee‬‬ ‫قبل از بررساای ولی یشاان‪ ،‬بیایی ریکوئسااتی با ‪ Body‬نامعتبر‪ ،‬به اکشاانمت‬
‫ارسال کنی ‪.‬‬
‫‪https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-‬‬
‫‪5248280C2CE3/employees/‬‬
‫‪body:‬‬

‫{‬
‫‪"name":null,‬‬
‫‪"age":29,‬‬
‫‪"position":null‬‬
‫}‬

‫همانطور که میبینی ‪ Response‬این ریکوئساات‪ ،‬خطای ‪ 500 Internal Server‬اساات‪ .‬این خطا‬
‫زمانی داد میشااود که چیوی غیرقابل کنترل‪ ،‬در ک ا اق افتاد و ساارور خطایی ایجاد کرد‬
‫باش ‪.‬‬

‫همه شاما میدانی که نها دلیل این خطا‪ ،‬ارساال یک م ل اشاتبا به ‪ API‬اسات بنابراین لیام‬
‫خطا بای مت اوت باش ‪.‬‬

‫برای رفع این مشاااکال‪ ،‬باایا کالس ‪ EmployeeForCreationDto‬را لییر دهی ؛ زیرا این هماا‬
‫آبجکتی است که ما از ب نه ریکوئست ‪ Deserialize‬میکنی ‪.‬‬
‫‪176‬‬
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class EmployeeForCreationDto
{
[Required(ErrorMessage = "Employee name is a required field.")]
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30
characters.")]
public string Name { get; set; }

[Required(ErrorMessage = "Age is a required field.")]


public int Age { get; set; }

[Required(ErrorMessage = "Position is a required field.")]


[MaxLength(20, ErrorMessage = "Maximum length for the Position is
20 characters.")]
public string Position { get; set; }
}

}
. ‫ می وانی دوبار ریکوئست لایین را ارسال کنی‬،‫لس از اعمال قوانین باال‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:

{
"name":null,
"age":29,
"position":null
}

177
‫‪Bad‬‬ ‫‪ ASP.NET Core‬به موض گرفتن ریکوئسااات‪ ،‬م ل را اعتبارسااانجی و ‪- Status Code‬‬
‫‪ 400 Request‬را میفرست ‪.‬‬

‫این نتیجه قابل قبول اسات‪ ،‬اما همانطور که گ تی بهتر اسات ‪ Status Code‬ه ‪ ،‬با این شارایط‬
‫متناسااب باش ا ‪ .‬بهترین ‪ Status Code‬برای این شاارایط‪ 422 Unprocessable Entity ،‬اساات‬
‫‪ModelState‬‬ ‫بنابراین اولین کاری که بای انجام دهی ‪ ،‬این اساات که در صااورت نا معتبر بود‬
‫مانع خطای ‪ BadRequest‬شوی ‪.‬‬

‫گام اول اضافه کرد ک لایین به مت ‪ ConfigureService‬است‪.‬‬


‫>= ‪services.Configure<ApiBehaviorOptions>(options‬‬
‫{‬
‫;‪options.SuppressModelStateInvalidFilter = true‬‬
‫;)}‬

‫‪Startup‬‬ ‫کالس ‪ ApiBehaviorOptions‬در‪ Namespace‬لایین اسااات‪ ،‬لس این را ه در کالس‬


‫اضافه کنی ‪.‬‬
‫;‪using Microsoft.AspNetCore.Mvc‬‬

‫‪178‬‬
. ‫ را لییر دهی‬CreateEmployeeForCompany ‫حاال بای اکشن مت‬
[HttpPost]
public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody]
EmployeeForCreationDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForCreationDto object sent from client is
null.");

return BadRequest("EmployeeForCreationDto object is null");


}
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForCreationDto object");

return UnprocessableEntity(ModelState);
}

var company = _repository.Company.GetCompany(companyId, trackChanges:


false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

var employeeEntity = _mapper.Map<Employee>(employee);


_repository.Employee.CreateEmployeeForCompany(companyId,
employeeEntity);
_repository.Save();

var employeeToReturn = _mapper.Map<EmployeeDto>(employeeEntity);

return
CreatedAtRoute("GetEmployeeForCompany",
new
179
{
companyId,
id = employeeToReturn.Id
},
employeeToReturn);
}
. ‫بیایی یک بار دیگر ریکوئست را ارسال کنی‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:

{
"name":null,
"age":29,
"position":null
}

. ‫ ه یک ریکوئست نیاز داری‬Max Length ‫برای ست‬


https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:

{
"name":"Saman",

180
"age":29,
"position":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

. ‫عالی ش همانطور که انتظار داشتی این مورد ه کار میکن‬

. ‫ ه بای همین مراحل را انجام دهی‬CreateCompany ‫حاال برای اکشن مت‬

.‫ اضافه کنید‬CompanyForCreationDto ‫ را به کالس‬Data Annotation •

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class CompanyForCreationDto
{
[Required(ErrorMessage = "Company name is a required field.")]
[MaxLength(30, ErrorMessage = "Maximum length for the name is 30
characters.")]
public string Name { get; set; }

[Required(ErrorMessage = "Company address is a required field.")]


[MaxLength(100, ErrorMessage = "Maximum length for the address is
100 characters.")]
public string Address { get; set; }
181
[Required(ErrorMessage = "Company country is a required field.")]
[MaxLength(50, ErrorMessage = "Maximum length for the country is
50 characters.")]
public string Country { get; set; }

public IEnumerable<EmployeeForCreationDto> Employees { get; set; }


}

}
. ‫ را لییر دهی‬CreateCompany ‫اکشن مت‬
public IActionResult CreateCompany([FromBody] CompanyForCreationDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from client is
null.");

return BadRequest("CompanyForCreationDto object is null");


}

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForCreationDto object");

return UnprocessableEntity(ModelState);
}

var companyEntity = _mapper.Map<Company>(company);


_repository.Company.CreateCompany(companyEntity);
_repository.Save();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id },


companyToReturn);
}

182
int ‫اعتبارسنجی نوع‬
. ‫ن ارد را ارسال کنی‬ Age ‫ که لرالر ی‬Request Body ‫یک‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:

{
"name":"Saman",

"position":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

‫ ه هیچ لیام خطایی‬Response Body ‫اما در‬ ‫ ارساال نشا‬Age ‫ لرالر ی‬، ‫همانطور که میبینی‬
.‫نیست‬

‫ مق ار لی‬، ‫ اسات و اگر آ را ارساال نکنی‬int ‫این مشاکل به این دلیل اسات که سان از نو‬
.‫گرفته میشود‬ ‫ نادی‬Age ‫فرض ص ر برای آ نظی و اعتبارسنجی برای لرالر ی‬

. ‫ را اصالح کنی‬Age ‫ لرالر ی‬Data Annotation ‫برای رفع این مشکل بای‬
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class EmployeeForCreationDto
{
[Required(ErrorMessage = "Employee name is a required field.")]

183
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30
characters.")]
public string Name { get; set; }

[Required(ErrorMessage = "Age is a required field.")]


[Range(18, int.MaxValue, ErrorMessage = "Age is required and it
can't be lower than 18")]
public int Age { get; set; }

[Required(ErrorMessage = "Position is a required field.")]


[MaxLength(20, ErrorMessage = "Maximum length for the Position is
20 characters.")]
public string Position { get; set; }
}

}
. ‫اکنو بیایی یک بار دیگر ریکوئست لایین را ب رستی‬
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:

{
"name":"Saman",

"position":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

184
. ‫ را داری‬Age ‫ لیام خطای‬Response ‫حاال در‬

PUT Request ‫اعتبارسنجی برای‬


‫ اسات بیایی مرحله به مرحله لی‬POST Request ‫ ه شابیه‬PUT Request ‫اعتبارسانجی‬
. ‫بروی‬

. ‫ اضافه کنی‬EmployeeForUpdateDto ‫ها را به کالس‬Data Annotation ‫ابت ا‬ •

using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class EmployeeForUpdateDto
{
[Required(ErrorMessage = "Employee name is a required field.")]
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30
characters.")]
public string Name { get; set; }

[Range(18, int.MaxValue, ErrorMessage = "Age is required and it


can't be lower than 18")]
public int Age { get; set; }

[Required(ErrorMessage = "Position is a required field.")]


[MaxLength(20, ErrorMessage = "Maximum length for the Position is
20 characters.")]
public string Position { get; set; }
}

}
‫ مقایسااه کنی میبینی که ک هر‬EmployeeForCreationDto ‫خب اگر این کالس را با کالس‬
. ‫دو یکسا است و اینجاست که ما کرار ک داری‬

. ‫لس بیایی بعضی لییرات را اضافه کنی‬

‫بااا نااام‬ ‫یااک کااالس جاا یاا‬ DataTransferObjects ‫ در فااولاا ر‬:‫گتتام اول‬
. ‫ اضافه کنی‬EmployeeForManipulationDto
185
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public abstract class EmployeeForManipulationDto
{
[Required(ErrorMessage = "Employee name is a required field.")]
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30
characters.")]
public string Name { get; set; }

[Range(18, int.MaxValue, ErrorMessage = "Age is required and it


can't be lower than 18")]
public int Age { get; set; }

[Required(ErrorMessage = "Position is a required field.")]


[MaxLength(20, ErrorMessage = "Maximum length for the Position is
20 characters.")]
public string Position { get; set; }
}

}
‫ااا کااالس‬ ‫ایااجاااد ماایکااناای ا‬ Abstract ‫مااا ایاان کااالس را بااه عاانااوا یااک کااالس‬
. ‫از آ ارثبری کنن‬ EmployeeForCreationDto‫ و‬EmployeeForUpdateDto

namespace Entities.DataTransferObjects
{
public class EmployeeForUpdateDto: EmployeeForManipulationDto
{
}
}

namespace Entities.DataTransferObjects
{
public class EmployeeForCreationDto: EmployeeForManipulationDto
{
}

}
. ‫ را لییر دهی‬UpdateEmployeeForCompany ‫اکنو می وانی اکشن مت‬

186
[HttpPut("{id}")]
public IActionResult UpdateEmployeeForCompany(Guid companyId, Guid id,
[FromBody] EmployeeForUpdateDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForUpdateDto object sent from client is
null.");

return BadRequest("EmployeeForUpdateDto object is null");


}

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the EmployeeForUpdateDto
object");

return UnprocessableEntity(ModelState);
}

var company = _repository.Company.GetCompany(companyId, trackChanges:


false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

var employeeEntity = _repository.Employee.GetEmployee(companyId, id,


trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}

_mapper.Map(employee, employeeEntity);
_repository.Save();

187
return NoContent();
}
.‫حاال نوبت ست این اکشن مت است‬
https://localhost:5001/api/Companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

{
"name":null,

"age":29,
"position":null
}

PATCH Request ‫اعتبارسنجی برای‬


‫ در این اعتبارسنجی بای‬.‫ با ریکوئستهای قبلی کمی فرق دارد‬PATCH Request ‫اعتبارسانجی‬
. ‫ قرار دهی‬ApplyTo ‫ را در مت‬ModelState ‫لرالر ی‬
patchDoc.ApplyTo(employeeToPatch, ModelState);

‫خب حاال مانن ک زیر می وانی منطق اعتبارسنجی خود را به مت‬


. ‫ اضافه کنی‬PartiallyUpdateEmployeeForCompany
[HttpPatch("{id}")]

188
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId,
Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is null.");

return BadRequest("patchDoc object is null");


}
var company = _repository.Company.GetCompany(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}
var employeeEntity = _repository.Employee.GetEmployee(companyId, id,
trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}
var employeeToPatch = _mapper.Map<EmployeeForUpdateDto>(employeeEntity);
patchDoc.ApplyTo(employeeToPatch, ModelState);

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the patch document");

return UnprocessableEntity(ModelState);
}
_mapper.Map(employeeToPatch, employeeEntity);
_repository.Save();

return NoContent();
}
189
. ‫حاال بیایی این را با ه ست کنی‬
https://localhost:5001/api/Companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

[
{
"op": "remove",

"path":"/ageeeee"
}
]

‫ سااعی‬.‫ اما باز ه یک مشااکل کوچک وجود دارد‬،‫هما چیوی که انتظار داشااتی ا اق افتاد‬
. ‫کنی یک عملیات حذف برای ریکوئست لایین ب رستی‬
https://localhost:5001/api/Companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

[
{
"op": "remove",

"path":"/age"
}
]

190
‫این ریکوئسات با موفقیت انجام شا اما اگر یاد ا باشا گ تی که عملیات حذف‪ ،‬مق ار لرالر ی‬
‫را به مق ار لی فرض نظی میکن یعنی در اینجا بای به مق ار ص ر نظی کن ‪.‬‬

‫همانطور که میدانی ‪ ،‬ما در کالس ‪ EmployeeForUpdateDto‬یک ا ربیوت ‪ Range‬داشاتی که‬


‫اجاز نمیده مق ار لرالر ی‪ ،‬زیر ‪ 18‬باش در حالی که اال مق ار لرالر ی زیر ‪ 18‬نظی ش ‪.‬‬

‫مشکل کجاست؟‬

‫بیایی این مسلله را با ه بررسی کنی ‪.‬‬

‫‪191‬‬
‫ را‬employeeEntity ‫ را اعتبارسنجی میکنی اما‬patchDoc ‫ ما‬، ‫همانطور که در این ک میبینی‬
. ‫ در دیتابیس ذخیر میکنی‬،‫ب و اعتبارسنجی‬

‫ نیاز به اعتبارساانجی دیگری‬،‫بنابراین برای جلوگیری از ذخیر یک کارمن نامعتبر در دیتابیس‬


. ‫ه داری‬
[HttpPatch("{id}")]
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId,
Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is null.");

return BadRequest("patchDoc object is null");


}
var company = _repository.Company.GetCompany(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");

return NotFound();
}

var employeeEntity = _repository.Employee.GetEmployee(companyId, id,


trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");

return NotFound();
}

var employeeToPatch = _mapper.Map<EmployeeForUpdateDto>(employeeEntity);


patchDoc.ApplyTo(employeeToPatch, ModelState);
TryValidateModel(employeeToPatch);

192
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the patch document");

return UnprocessableEntity(ModelState);
}
_mapper.Map(employeeToPatch, employeeEntity);
_repository.Save();

return NoContent();
}

. ‫ اساات اد کردی‬employeeToPatch ‫ برای اعتبارساانجی‬TryValidateModel ‫در ک باال از مت‬


.‫ میشود‬ModelState ‫ ش‬invalid ‫ باعث‬،‫حاال هر خطایی‬

. ‫خب می وانی دوبار این ست را انجام دهی‬


https://localhost:5001/api/Companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/649AECC6-94FA-4C67-828A-08D8936F5863
body:

[
{
"op": "remove",

"path":"/age"
}
]

. ‫ مورد انتظار را دریافت کردی‬Status Code ‫همانطور که میبینی‬


193
‫ها‬Action Filter ‫ و‬Async ‫ برنامهنویسی‬: ‫فصل هشتم‬

:‫آنچه خواهید آموخت‬


‫ چیست؟‬Async ‫➢ برنامهنویسی‬
async ‫ و‬await ‫➢ کلمههای کلیدی‬
‫ چیست؟‬Action Filter ➢
Action Filter ‫➢ پیادهسازی‬
Action Filter ‫➢ اعتبارسنجی با‬

Action Filter ‫ در‬Dependency Injection ➢


‫برنامهنویسی ‪ Async‬چیست؟‬

‫برنامهنویسای ‪ ،Async‬کنیکی اسات که به شاما کمک میکن ا کار خود را از ‪ Thread‬اصالی‬


‫اللیکیشاان ج ا و در یک ‪ Thread‬دیگر انجام دهی ؛ سااپس زمانیکه کار مام شاا رد نتیجه‬
‫موفق یا ناموفق کار را به ‪ Thread‬اصلی اطال ده ‪.‬‬

‫و ا ح زیادی از‬ ‫با اساات اد از برنامهنویس ای ‪ Async‬می وا ‪ Scalability‬اللیکیشاان را افوای‬


‫لرفورمنس جلوگیری کنی ‪.‬‬ ‫کاه‬

‫اما برنامهنویسی ‪ Async‬چطور این اتفاقات خوب را به همرا دارد؟‬

‫در حالت ‪ Async‬وقتی ریکوئسااتی را به ‪ Thread‬اصاالی ارسااال میکنی ‪ ،‬این ‪ Thread‬کار را به‬
‫یک ‪ Background Thread‬واگذار میکن ؛ بنابراین برای یک ریکوئست دیگر آزاد است‪.‬‬

‫ماام شااا باایا نتیجاه را باه ‪ Thread‬اصااالی دها ‪ .‬در آخر‬ ‫‪Background Thread‬‬ ‫زماانیکاه کاار‬
‫برمیگردان ‪.‬‬ ‫‪ Thread‬اصلی نتیجه را به درخواست کنن‬

‫لس در نتیجه‪ ،‬چو ریکوئسات ما مساتقیما با ‪ Thread‬اصالی کار نمیکن لس ‪ Thread‬اصالی‬


‫منتظر هیچ ریسپانسی نمیمان و در حقیقت بالک نخواه ش ‪.‬‬

‫ریکوئست در حالت ‪ Async‬و‬ ‫یک موضو مهمی که بای ب انی این است که زما طول کشی‬
‫‪ Sync‬یک ان از است و ما نمی وانی در حالت ‪ Async‬آنرا سریعتر اجرا کنی ‪.‬‬

‫ریکوئسات‪،‬‬ ‫نها مویت ‪ Async‬نسابت به ‪ Sync‬این اسات که ‪ Thread‬اصالی در زما اجرا شا‬
‫بالک نمیشود بنابراین ‪ Thread‬اصلی می وان ریکوئستهای دیگر را لردازش کن ‪.‬‬

‫در لایین صویری از کار ‪ Async‬را میبینی ‪.‬‬

‫‪195‬‬
‫حاال که این موضاو برای شاما روشان شا بیایی نوو لیاد ساازی ک های ‪ Async‬در ‪.NET 5.0‬‬
‫را یاد بگیری ‪.‬‬

‫کلمههای کلیدی ‪ await‬و ‪async‬‬


‫مهمی در برنامهنویسای ‪ Asynchronous‬دارد‪ .‬با اسات اد از این کلمه‬ ‫کلمه کلی ی ‪ async‬نق‬
‫می وانی به راحتی مت های ‪ async‬بنویسی ‪.‬‬

‫)(‪async Task<IEnumerable<Company>> GetAllCompaniesAsync‬‬


‫نها بای کلمه کلی ی ‪ async‬را‬ ‫‪async‬‬ ‫همانطور که در ک باال میبینی ‪ ،‬برای نوشااتن مت های‬
‫در کنار ‪ Return Type‬مت اضافه کنی ‪.‬‬

‫وقتی از کلماه کلیا ی ‪ async‬اسااات ااد میکنی ‪ ،‬می وا کلماه کلیا ی ‪ await‬را در با ناه متا‬
‫اضافه و خروجی مت ه لییر کن ‪.‬‬
‫;)(‪await FindAllAsync‬‬
‫در برنامهنویسی ‪ Async‬سه نوع ‪ Return Type‬داریم ‪:‬‬
‫متا هاایی اساااات کاه مقا اری را‬ ‫‪async‬‬ ‫برای‬ ‫>‪Task<TResult> : Task<TResult‬‬ ‫•‬
‫باایا‬ ‫‪async‬‬ ‫برگردانا ‪ ،‬لس متا‬ ‫‪int‬‬ ‫ماا‬ ‫‪sync‬‬ ‫برمیگرداننا ‪ .‬این یعنی ‪ :‬اگر متا‬
‫<‪ Task<int‬را برگردانا و اگر متا ‪ sync‬ماا >‪ IEnumerable<string‬برگردانا لس متا‬
‫‪ async‬بای >>‪ Task<IEnumerable<string‬را برگردان ‪.‬‬

‫‪196‬‬
‫• ‪ Task : Task‬برای ‪ async‬متا هاایی کاه مقا اری را بر نمیگرداننا کااربرد دارد‪ .‬یعنی اگر‬
‫متا ‪ sync‬ماا هیچ مقا اری را برنگردانا ‪ ،‬بناابراین متا ‪ async‬باایا ‪ Task‬برگردانا و ماا‬
‫می وانی داخل مت ‪ ،‬از کلمه کلی ی ‪ await‬است اد کنی ‪ ،‬اما نیازی به ‪ return‬ن اری ‪.‬‬
‫اساااتا اااد کانایا ‪ .‬مااا فاقاط بارای‬ ‫‪Event Handler‬‬ ‫ها مایاوانایا بارای‬ ‫از‪void‬‬ ‫‪:‬‬ ‫‪void‬‬ ‫•‬
‫‪Asynchronous Event Handler‬ها که به نو برگشاتی ‪ void‬نیاز دارن ‪ ،‬از ‪ void‬اسات اد‬
‫میکنی ‪ .‬به غیر از این مورد ما همیشه بای ‪ Task‬برگردانی ‪.‬‬

‫نکته!!‬

‫گاهی اوقات کد ‪ async‬کندتر از کد ‪ sync‬عمل میکند به طور مثال‪:‬‬

‫دستتورات ‪ EF Core async‬زمان زیادی برای اجرا شتدن صترف میکند و این به دلیل کد اضتافهای‬
‫است که برای مدیریت ‪ Thread‬ها داریم‪ .‬بنابراین همیشه ‪ async‬انتراب مناسبی نیست‪.‬‬

‫بته طور کلی هر کجتا کته امکتان داردبتایتد از کتد ‪ async‬استتتفتاده کنیم امتا هر جتا کته کتد ‪ async‬متا‬
‫کندتر عمل کرد باید از حالت ‪ sync‬کمک بگیریم‪.‬‬

‫خب حاال بیایی ک های ‪ sync‬لروه را ری کتورکنی ‪.‬‬

‫ریفکتور ‪Repository‬‬
‫بیایی ری کتور را از ‪ ICompanyRepository‬و‪ CompanyRepository‬شاارو کنی لس ک های‬
‫اینترفیس ‪ ICompanyRepository‬را با ک های لایین لییر دهی ‪.‬‬
‫;‪using Entities.Models‬‬
‫;‪using System‬‬
‫;‪using System.Collections.Generic‬‬
‫;‪using System.Threading.Tasks‬‬

‫‪namespace Contracts.IServices‬‬
‫{‬
‫‪public interface ICompanyRepository‬‬
‫{‬
‫‪Task<IEnumerable<Company>> GetAllCompaniesAsync(bool‬‬
‫;)‪trackChanges‬‬
‫;)‪Task<Company> GetCompanyAsync(Guid companyId, bool trackChanges‬‬

‫‪197‬‬
void CreateCompany(Company company);
Task<IEnumerable<Company>> GetByIdsAsync(IEnumerable<Guid> ids,
bool trackChanges);
void DeleteCompany(Company company);
}
}

: ‫بررسی کد‬

‫ را‬state ‫ چو در این متا ها فقط‬، ‫لییری نکرد‬ Delete ‫ و‬Create ‫ متا های‬Signature •

. ‫لییر میدهی و لییری در دیتابیس ن اری‬ Deleted ‫ و‬Added ‫به‬

. ‫ ه لییر کن‬CompanyRepository ‫ بای کالس‬،‫حاال مطابق با لییرات اینترفیس‬


using Contracts.IServices;
using Entities;
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}

public async Task<IEnumerable<Company>> GetAllCompaniesAsync(bool


trackChanges) =>
await FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToListAsync();

198
public async Task<Company> GetCompanyAsync(Guid companyId, bool
trackChanges) =>
await FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefaultAsync();

public async Task<IEnumerable<Company>>


GetByIdsAsync(IEnumerable<Guid> ids, bool
trackChanges) =>
await FindByCondition(x => ids.Contains(x.Id), trackChanges)
.ToListAsync();

public void CreateCompany(Company company) => Create(company);

public void DeleteCompany(Company company)


{
Delete(company);
}

}
}
.‫ است‬RepositoryManager ‫ و‬IRepositoryManager ‫خب حاال نوبت لییر‬

‫ وجود دارد کاه‬Save ‫ باا ناام‬، ‫ را بااز کنیا میبینیا کاه یاک متا‬RepositoryManager ‫اگر کالس‬
. ‫ را ص ا میزن‬SaveChanges EF Core ‫مت‬

‫ را‬RepositoryManager ‫ و‬IRepositoryManager ‫ کنی لس‬async ‫ماا میخواهی این متا را‬


. ‫باز و این مت را لییر دهی‬
using System.Threading.Tasks;

namespace Contracts.IServices
{
public interface IRepositoryManager
{
ICompanyRepository Company { get; }
IEmployeeRepository Employee { get; }
Task SaveAsync();
}

199
}
: RepositoryManager ‫کالس‬
using Contracts.IServices;
using System.Threading.Tasks;
using Entities;

namespace Repository.Repositories
{
public class RepositoryManager : IRepositoryManager
{
private CompanyEmployeeDbContext _repositoryContext;
private ICompanyRepository _companyRepository;
private IEmployeeRepository _employeeRepository;

public RepositoryManager(CompanyEmployeeDbContext
repositoryContext)
{
_repositoryContext = repositoryContext;
}

public ICompanyRepository Company


{
get
{
if (_companyRepository == null)
_companyRepository = new
CompanyRepository(_repositoryContext);

return _companyRepository;
}
}

public IEmployeeRepository Employee


{
get
{
if (_employeeRepository == null)
_employeeRepository = new
EmployeeRepository(_repositoryContext);

return _employeeRepository;
200
}
}

public Task SaveAsync() => _repositoryContext.SaveChangesAsync();


}

}
: ‫بررسی کد‬

‫ هساااتنا لس ماا از‬awaitable ‫ متا هاای‬...‫ و‬ToListAsync ،SaveAsync ‫چو متا هاای‬ •

. ‫ است اد میکنی‬await ‫کلمه کلی ی‬

‫ مت‬، ‫ الوامی نیساات اما اگر از آ اساات اد نکنی‬،await ‫البته اساات اد از کلمه کلی ی‬
.‫ اجرا میشود و مطملنا این ه ف ما نیست‬sync ‫ به صورت‬Async

CompaniesController ‫ریفکتور‬
‫ لس اول از متا‬. ‫ کنی‬async ‫ را‬CompaniesController ‫در لاایاا کاار باایا ماام اکشااانهاای‬
. ‫ شرو میکنی‬GetCompanies
[HttpGet]
public async Task<IActionResult> GetCompaniesAsync()
{
var companies =await
_repository.Company.GetAllCompaniesAsync(trackChanges: false);

var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);

return Ok(companiesDto);
}
. ‫ لایین را در این کنترلر اضافه کنی‬Namespace
using System.Threading.Tasks;

: ‫بررسی کد‬

GetCompaniesAsync ‫ اسات نام این مت را‬Async ‫برای اینکه مشاخش شاود این مت‬ •

. ‫کردی‬
‫ متا‬Signature ‫ را باه‬async ‫ را لییر و کلماه کلیا ی‬Return Type ‫در این متا فقط‬ •

. ‫اضافه میکنی‬

201
‫ این هما کاری اسات‬. ‫ کردی‬await ‫ را‬GetAllCompaniesAsync ‫ مت‬، ‫در ب نه مت ه‬ •

. ‫که قریبا در مام اکشنها بای انجام دهی‬

. ‫خب بیایی باقی اکشن مت های را ه اصالح کنی‬

: CompaniesController ‫کدهای‬
using AutoMapper;
using CompanyEmployee.API.Infrastructure.ModelBinders;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Entities.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public CompaniesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet]
public async Task<IActionResult> GetCompaniesAsync()
{

202
var companies = await
_repository.Company.GetAllCompaniesAsync(trackChanges:
false);

var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);

return Ok(companiesDto);
}

[HttpGet("{id}", Name = "CompanyById")]


public async Task<IActionResult> GetCompanyAsync(Guid id)
{
var company = await _repository.Company.GetCompanyAsync(id,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");

return NotFound();
}
else
{
var companyDto = _mapper.Map<CompanyDto>(company);

return Ok(companyDto);
}
}

[HttpGet("collection/({ids})", Name = "CompanyCollection")]


public async Task<IActionResult>
GetCompanyCollectionAsync([ModelBinder(BinderType =
typeof(ArrayModelBinder))]IEnumerable<Guid> ids)
{
if (ids == null)
{
_logger.LogError("Parameter ids is null");

return BadRequest("Parameter ids is null");


}

var companyEntities = await


_repository.Company.GetByIdsAsync(ids, trackChanges:

203
false);

if (ids.Count() != companyEntities.Count())
{
_logger.LogError("Some ids are not valid in a
collection");

return NotFound();
}

var companiesToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);

return Ok(companiesToReturn);
}

[HttpPost]
public async Task<IActionResult>
CreateCompanyAsync([FromBody]CompanyForCreationDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from
client is null.");

return BadRequest("CompanyForCreationDto object is null");


}
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
CompanyForCreationDto object");

return UnprocessableEntity(ModelState);
}

var companyEntity = _mapper.Map<Company>(company);


_repository.Company.CreateCompany(companyEntity);
await _repository.SaveAsync();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

204
return CreatedAtRoute("CompanyById", new { id =
companyToReturn.Id },
companyToReturn);
}

[HttpPost("collection")]
public async Task<IActionResult>
CreateCompanyCollectionAsync([FromBody]
IEnumerable<CompanyForCreationDto> companyCollection)
{
if (companyCollection == null)
{
_logger.LogError("Company collection sent from client is
null.");

return BadRequest("Company collection is null");


}

var companyEntities =
_mapper.Map<IEnumerable<Company>>(companyCollection);

foreach (var company in companyEntities)


{
_repository.Company.CreateCompany(company);
}

await _repository.SaveAsync();
var companyCollectionToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);
var ids = string.Join(",", companyCollectionToReturn.Select(c
=> c.Id));

return CreatedAtRoute("CompanyCollection", new { ids },


companyCollectionToReturn);
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCompanyAsync(Guid id)
{
var company = await _repository.Company.GetCompanyAsync(id,
trackChanges: false);

205
if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");

return NotFound();
}

_repository.Company.DeleteCompany(company);
await _repository.SaveAsync();

return NoContent();
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateCompanyAsync(Guid id,
[FromBody] CompanyForUpdateDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForUpdateDto object sent from
client is null.");

return BadRequest("CompanyForUpdateDto object is null");


}
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
CompanyForUpdateDto object");

return UnprocessableEntity(ModelState);
}

var companyEntity = await


_repository.Company.GetCompanyAsync(id, trackChanges:
true);

if (companyEntity == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");
206
return NotFound();
}

_mapper.Map(company, companyEntity);
await _repository.SaveAsync();

return NoContent();
}

}
}
. ‫کرار میکنی‬ Entity ‫ مام مراحل باال را برای این‬.‫ است‬Employee ‫خب حاال نوبت‬

: IEmployeeRepository ‫کدهای‬
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Contracts.IServices
{
public interface IEmployeeRepository
{
Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, bool
trackChanges);
Task<Employee> GetEmployeeAsync(Guid companyId, Guid id, bool
trackChanges);
void CreateEmployeeForCompany(Guid companyId, Employee employee);
void DeleteEmployee(Employee employee);
}
}
: EmployeeRepository ‫کدهای‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using System;

207
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{

public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext) : base(companyEmployeeDbContext)
{
}

public async Task<IEnumerable<Employee>> GetEmployeesAsync (Guid


companyId, bool trackChanges) =>
await FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToListAsync();

public async Task<Employee> GetEmployeeAsync (Guid companyId,


Guid id, bool trackChanges) =>
await FindByCondition(e => e.CompanyId.Equals(companyId) &&
e.Id.Equals(id), trackChanges).SingleOrDefaultAsync();

public void CreateEmployeeForCompany(Guid companyId, Employee


employee)
{
employee.CompanyId = companyId;
Create(employee);
}

public void DeleteEmployee(Employee employee)


{
Delete(employee);
}

208
: EmployeesController
using AutoMapper;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Entities.Models;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Controllers
{
[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;

public EmployeesController(IRepositoryManager repository,


ILoggerManager logger,
IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}

[HttpGet(Name = "GetEmployeeForCompany")]
public async Task<IActionResult> GetEmployeesForCompanyAsync(Guid
companyId)
{
var company =await
_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)

209
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

return NotFound();
}

var employeesFromDb =await


_repository.Employee.GetEmployeesAsync(companyId,
trackChanges: false);

var employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDto);
}

[HttpGet("{id}")]
public async Task<IActionResult> GetEmployeeForCompanyAsync(Guid
companyId, Guid id)
{
var company =await
_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

return NotFound();
}

var employeeDb =await


_repository.Employee.GetEmployeeAsync(companyId, id,
trackChanges: false);

if (employeeDb == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

210
return NotFound();
}

var employee = _mapper.Map<EmployeeDto>(employeeDb);

return Ok(employee);
}

[HttpPost]
public async Task<IActionResult> CreateEmployeeForCompanyAsync(Guid
companyId, [FromBody]
EmployeeForCreationDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForCreationDto object sent from
client is null.");

return BadRequest("EmployeeForCreationDto object is


null");
}

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForCreationDto object");

return UnprocessableEntity(ModelState);
}

var company = await


_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

return NotFound();
}

211
var employeeEntity = _mapper.Map<Employee>(employee);

_repository.Employee.CreateEmployeeForCompany(companyId,
employeeEntity);
await _repository.SaveAsync();

var employeeToReturn =
_mapper.Map<EmployeeDto>(employeeEntity);

return
CreatedAtRoute("GetEmployeeForCompany",
new
{
companyId,
id = employeeToReturn.Id
},
employeeToReturn);
}

[HttpDelete("{id}")]
public async Task<IActionResult>
DeleteEmployeeForCompanyAsync(Guid companyId, Guid id)
{
var company = await
_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

return NotFound();
}

var employeeForCompany =await


_repository.Employee.GetEmployeeAsync(companyId, id,
trackChanges: false);

if (employeeForCompany == null)
{

212
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

return NotFound();
}

_repository.Employee.DeleteEmployee(employeeForCompany);
await _repository.SaveAsync();

return NoContent();
}

[HttpPut("{id}")]
public async Task<IActionResult>
UpdateEmployeeForCompanyAsync(Guid companyId, Guid id, [FromBody]
EmployeeForUpdateDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForUpdateDto object sent from
client is null.");

return BadRequest("EmployeeForUpdateDto object is null");


}

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForUpdateDto object");

return UnprocessableEntity(ModelState);
}

var company =await


_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

213
return NotFound();
}
var employeeEntity =await
_repository.Employee.GetEmployeeAsync(companyId, id,
trackChanges:
true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

return NotFound();
}

_mapper.Map(employee, employeeEntity);
await _repository.SaveAsync();

return NoContent();
}

[HttpPatch("{id}")]
public async Task<IActionResult>
PartiallyUpdateEmployeeForCompanyAsync(Guid companyId, Guid id,
[FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is
null.");

return BadRequest("patchDoc object is null");


}

var company =await


_repository.Company.GetCompanyAsync(companyId, trackChanges:
false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");

214
return NotFound();
}
var employeeEntity =await
_repository.Employee.GetEmployeeAsync(companyId, id,
trackChanges: true);

if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

return NotFound();
}

var employeeToPatch =
_mapper.Map<EmployeeForUpdateDto>(employeeEntity);

patchDoc.ApplyTo(employeeToPatch, ModelState);

TryValidateModel(employeeToPatch);

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the patch
document");

return UnprocessableEntity(ModelState);
}

_mapper.Map(employeeToPatch, employeeEntity);

await _repository.SaveAsync();

return NoContent();
}

215
‫‪ Action Filter‬چیست؟‬
‫سارور همیشاه‪ ،‬ریکوئسات را به کنترلر و اکشان مربوطه ه ایت میکن ؛ اما گاهی نیاز اسات که‬
‫قبل یا بع از اجرای اکشن مت ‪ ،‬ک ی اجرا شود‪.‬‬

‫فیلترهای موجود در ‪ ،.NET‬یک روش عالی برای حل این مسالله هساتن ‪ .‬فیلتر یک کالس اسات‬
‫که با آ می وانی ‪ ،‬ک ی را قبل یا بع از اجرای یک اکشن مت اجرا کنی ‪.‬‬

‫با فیلترها می وانی ‪ ،‬ک های قابل اسات اد مج د بنویسای و اکشانهای ما همیشاه میو و قابل‬
‫نگه اری باشن ‪.‬‬

‫انواع فیلترها ‪:‬‬

‫‪ :Authorization‬این فیلتر عیین میکنا کاه آیاا کااربر برای ریکوئسااات جااری‬ ‫‪Filter‬‬ ‫•‬

‫مجاز هست یا خیر‪.‬‬


‫‪ :Resource Filter‬این فیلتر بالفاصاله بع از ‪ Authorization Filter‬اجرا میشاود و برای‬ ‫•‬

‫‪ Caching‬و ‪ Performance‬بسیار کاربردی است‪.‬‬


‫‪ :Action Filter‬این فیلتر قبل و بع از اکشن مت اجرا میشود‪.‬‬ ‫•‬

‫‪Response‬‬ ‫‪ :Exception Filter‬این فیلتر برای م یریت اکسااپشاان‪ ،‬قبل از لر شاا‬ ‫•‬

‫‪ Body‬است اد میشود‪.‬‬
‫‪ :Result Filter‬این فیلتر قبل و بع از نتیجه اکشن مت اجرا میشود‪.‬‬ ‫•‬

‫در این فصل میخواهی در مورد ‪ Action Filter‬و نوو است اد از آ صوبت کنی ‪.‬‬

‫پیادهسازی ‪Action Filter‬‬


‫و یاا‬ ‫‪IActionFilter‬‬ ‫‪ ،Action‬بااایا کالسااای ایجاااد کنی کااه اینترفیس‬ ‫‪Filter‬‬ ‫برای ایجاااد‬
‫‪ IAsyncActionFilter‬را لیاد سازی و یا اینکه از کالس ‪ ActionFilterAttribute‬ارثبری کن ‪.‬‬

‫نکته!!‬
‫و چتنتد‬ ‫‪IAsyncActionFilter ،IActionFilter‬‬ ‫ایتنتتترفتیتس‬ ‫‪ActionFilterAttribute‬‬ ‫کتالس‬
‫اینترفیس دیگر را پیادهسازی کرده است‪.‬‬

‫‪216‬‬
public abstract class ActionFilterAttribute : Attribute,
IActionFilter,IFilterMetadata, IAsyncActionFilter, IResultFilter,
IAsyncResultFilter, IOrderedFilter

IActionFilter ‫یا‬ IAsyncActionFilter ‫ می وانی اینترفیس‬،Async ‫ و‬Sync ‫برای نوشتن ک های‬


. ‫را لیاد سازی کنی‬

OnActionExekuting ‫ لس بای ک های خود را در مت های‬، ‫ را لیاد سازی کنی‬IActionFilter ‫اگر‬


. ‫ بنویسی‬OnActionExecuted ‫و‬
using Microsoft.AspNetCore.Mvc.Filters;

namespace CompanyEmployee.API.Infrastructure.Filters
{
namespace ActionFilters.Filters
{
public class ActionFilterExample : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// our code before action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// our code after action executes
}
}
}

}
‫ لاس ک ا هااای خاود را بااای ا در مات ا‬، ‫را لایاااد سااااازی کانای ا‬ IAsyncActionFilter ‫اگار‬
. ‫ بنویسی‬OnActionExociationAsync
using Microsoft.AspNetCore.Mvc.Filters;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Infrastructure.Filters
{
namespace ActionFilters.Filters
{
public class AsyncActionFilterExample : IAsyncActionFilter

217
{
public async Task
OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
// execute any code before the action executes
var result = await next();
// execute any code after the action executes
}
}
}

Action Filter ‫سطح‬


: ‫ به طور مثال‬.‫ مانن سایر فیلترها می وان به سطوح مختل اضافه شود‬،‫ نیو‬Action Filter

.‫یا به صورت عمومی یا به اکشن و کنترلر اضافه میشود‬

AddControllers ‫ باایا آ را در متا‬، ‫اگر بخواهی از فیلتر در ساااطح عمومی اسااات ااد کنی‬
. ‫رجیستر کنی‬
services.AddControllers(config =>
{
config.Filters.Add(new GlobalFilterExample());
});

‫ به عنوا یک سارویس در ساطح اکشان مت یا کنترلر اسات اد‬،‫ را‬Action Filter ‫اما اگر بخواهی‬
. ‫ رجیستر کنی‬ConfigureServices ‫کنی ؛ بای آ را به عنوا یک سرویس در مت‬
services.AddScoped<ActionFilterExample>();
services.AddScoped<ControllerFilterExample>();

‫ در باالی اکشن مت یا کنترلر‬،ServiceType ‫در لایا برای است اد از فیلتر بای آ را به عنوا‬
. ‫قرار دهی‬
using CompanyEmployee.API.Infrastructure.Filters.ActionFilters.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{
218
[ServiceFilter(typeof(ControllerFilterExample))]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample))]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}

‫ها‬Filter ‫ترتیب اجرا شدن‬


: ‫ر یب اجرای فیلترها به شرح زیر است‬

. ‫ این ر یب را لییر دهی‬،‫ به ک لایین‬Order ‫البته می وانی با افوود‬


219
using CompanyEmployee.API.Infrastructure.Filters.ActionFilters.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace CompanyEmployee.API.Controllers
{

[ServiceFilter(typeof(ControllerFilterExample))]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample) , Order =1)]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}

}
. ‫یا چیوی شبیه به این در باالی اکشن مت بنویسی‬
[ServiceFilter(typeof(ActionFilterExample2) , Order =1)]
[ServiceFilter(typeof(ActionFilterExample) , Order =2)]

Action Filter ‫اعتبارسنجی با‬


‫میو ر و خوانا ر شا ن چو یک م یریت اکساپشان عمومی‬ try-catch ‫ ب و‬،‫اکشان مت های ما‬
.‫ اما باز ه می وا این اکشنها را بهینه ر کرد‬. ‫وجود دارد که این موضو را هن ل میکن‬

‫ از اکشان مت ها ج ا کنی و با این کار ک های میو و‬، ‫ما می وانی ک های اعتبارسانجی را ه‬
. ‫قابل نگه اری داشته باشی‬

‫ میبینیا کاه‬، ‫نگاا کنیا‬ CompaniesController ‫در‬ PUT ‫و‬ POST ‫اگر باه اکشااانهاای‬
.‫است‬ ‫ در هر دو کرار ش‬Company ‫اعتبارسنجی‬
[HttpPost]
public async Task<IActionResult>
CreateCompanyAsync([FromBody]CompanyForCreationDto
company)

220
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from client is
null.");

return BadRequest("CompanyForCreationDto object is null");


}

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the CompanyForCreationDto
object");

return UnprocessableEntity(ModelState);
}

var companyEntity = _mapper.Map<Company>(company);


_repository.Company.CreateCompany(companyEntity);
await _repository.SaveAsync();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id },


companyToReturn);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateCompanyAsync(Guid id, [FromBody]
CompanyForUpdateDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForUpdateDto object sent from client is
null.");

return BadRequest("CompanyForUpdateDto object is null");


}

221
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the CompanyForUpdateDto
object");

return UnprocessableEntity(ModelState);
}

var companyEntity = await _repository.Company.GetCompanyAsync(id,


trackChanges:
true);

if (companyEntity == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in the
database.");

return NotFound();
}

_mapper.Map(company, companyEntity);
await _repository.SaveAsync();

return NoContent();
}
‫ لس در‬. ‫ ببری‬Action Filter ‫خب می وانی این ک ها را از این اکشانمت ها ج ا و به یک کالس‬
‫ ایجااد و ساااپس یاک کالس باا ناام‬ActionFilters ‫ یاک فولا ر جا یا باا ناام‬،Infrastructure ‫فولا ر‬
. ‫ در آ اضافه نمایی‬ValidationFilterAttribute
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc.Filters;

namespace CompanyEmployee.API.Infrastructure.ActionFilters
{
public class ValidationFilterAttribute : IActionFilter
{
private readonly ILoggerManager _logger;
public ValidationFilterAttribute(ILoggerManager logger)
{
_logger = logger;

222
}

public void OnActionExecuting(ActionExecutingContext context)


{ }
public void OnActionExecuted(ActionExecutedContext context) {
}
}

. ‫ را لییر دهی‬OnActionExecuting ‫حاال می وانی مت‬


using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq;

namespace CompanyEmployee.API.Infrastructure. ActionFilters


{
public class ValidationFilterAttribute : IActionFilter
{
private readonly ILoggerManager _logger;
public ValidationFilterAttribute(ILoggerManager logger)
{
_logger = logger;
}

public void OnActionExecuting(ActionExecutingContext context)


{
var action = context.RouteData.Values["action"];
var controller = context.RouteData.Values["controller"];
var param = context.ActionArguments
.SingleOrDefault(x =>
x.Value.ToString().Contains("Dto")).Value;

if (param == null)
{
_logger.LogError($"Object sent from client is null.
Controller: {controller}, action: {action} ");

223
context.Result = new BadRequestObjectResult($"Object is
null. Controller:{ controller}, action: {action} ");

return;
}

if (!context.ModelState.IsValid)
{
_logger.LogError($"Invalid model state for the object.
Controller: {controller}, action: {action} ");

context.Result = new
UnprocessableEntityObjectResult(context.ModelState);
}
}

public void OnActionExecuted(ActionExecutedContext context) { }


}

. ‫ رجیستر کنی‬ConfigureServices ‫سپس این اکشن فیلتر را در مت‬

. ‫ اضافه کنی‬Startup ‫ لایین را در کالس‬Namespace ‫ابت ا‬


using CompanyEmployee.API.Infrastructure.ActionFilters;

. ‫ بنویسی‬ConfigureServices ‫ ک لایین را در مت‬، ‫و بع از آ‬


services.AddScoped<ValidationFilterAttribute>();

‫ بای ک اعتبارسانجی را از اکشان مت ها حذف و این اکشان فیلتر را به عنوا سارویس‬، ‫در لایا‬
. ‫است اد کنی‬

. ‫ اضافه کنی‬CompaniesController ‫ لایین را در‬Namespace ‫ابت ا‬


using CompanyEmployee.API.Infrastructure.ActionFilters;

. ‫سپس مت های لایین را ری کتور نمایی‬


[HttpPost]
[ServiceFilter(typeof(ValidationFilterAttribute))]

224
public async Task<IActionResult>
CreateCompanyAsync([FromBody]CompanyForCreationDto
company)
{
var companyEntity = _mapper.Map<Company>(company);
_repository.Company.CreateCompany(companyEntity);
await _repository.SaveAsync();

var companyToReturn = _mapper.Map<CompanyDto>(companyEntity);

return CreatedAtRoute("CompanyById", new { id = companyToReturn.Id },


companyToReturn);
}

[HttpPut("{id}")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult> UpdateCompanyAsync(Guid id, [FromBody]
CompanyForUpdateDto
company)
{
var companyEntity = await _repository.Company.GetCompanyAsync(id,
trackChanges:
true);

if (companyEntity == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in the
database.");

return NotFound();
}

_mapper.Map(company, companyEntity);
await _repository.SaveAsync();

return NoContent();
}

225
‫عالی شد‪.‬‬

‫حاال این اکشنمت ها (ب و ک های اعتبارسنجی) بسیار میو ر و خوانا ر ش ان ‪.‬‬

‫اگر به عنوا مثال یک ریکوئست ‪ POST‬با م ل نامعتبر ب رستی ‪ ،‬نتیجه موردنظر را میگیری ‪.‬‬
‫‪https://localhost:5001/api/companies‬‬
‫‪body:‬‬
‫{‬
‫‪"name": "Zahra",‬‬
‫‪"address":null‬‬
‫}‬

‫‪ Dependency Injection‬در ‪Action Filter‬‬


‫اگر اکشااانمت ‪ DeleteCompanyAsync‬و یا ‪ UpdateCompanyAsync‬را بررسااای کنی ‪ ،‬می‬
‫بینی که در آ ‪ Company‬را با ‪ Id‬از دیتابیس واکشای و ساپس چک میکن که آیا اطالعا ی از‬
‫یا خیر؟‬ ‫سمت دیتابیس بازگشت داد ش‬
‫)‪if (company == null‬‬
‫{‬
‫‪_logger.LogInfo($"Company with id: {id} doesn't exist in the‬‬
‫;)"‪database.‬‬

‫‪226‬‬
return NotFound();
}
‫ ا برخی‬، ‫ داشاته باشای‬Action Filter ‫ هما چیوی اسات که ما می وانی در کالس‬، ‫این که ک‬
. ‫اکشنمت ها بتوانن از آ است اد مج د کنن‬

Dependency ‫از‬ ‫ بای ریپازیتوری را با است اد‬،‫البته بای در نظر داشته باشی که در این کالس‬
. ‫وریق کنی‬ Injection

ValidateCompanyExistsAttribute ‫یک کالس دیگر با نام‬ ‫ اجاز دهی‬، ‫خب با گ تن این موضو‬
. ‫ ایجاد کنی‬ActionFilters ‫در فول ر‬
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Infrastructure.ActionFilters
{
public class ValidateCompanyExistsAttribute : IAsyncActionFilter
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;

public ValidateCompanyExistsAttribute(IRepositoryManager
repository,
ILoggerManager logger)
{
_repository = repository;
_logger = logger;
}

public async Task OnActionExecutionAsync(ActionExecutingContext


context, ActionExecutionDelegate next)
{
var trackChanges =
context.HttpContext.Request.Method.Equals("PUT");
var id = (Guid)context.ActionArguments["id"];

227
var company = await _repository.Company.GetCompanyAsync(id,
trackChanges);

if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in
the database.");
context.Result = new NotFoundResult();
}
else
{
context.HttpContext.Items.Add("company", company);
await next();
}
}
}

}
: ‫بررسی کد‬

async ‫ واکشای میکنی لس بای نساخه‬Async ‫ خود را به صاورت‬Entity ، ‫ما در این ک‬ •

. ‫وجه کنی‬ ‫ در اینجا دو نکته وجود دارد که بای به آ‬. ‫اکشن فیلتر را است اد کنی‬
‫ را با‬trackChanges ‫ باشا می وانی‬PUT ‫ اگر ریکوئسات برابر‬،‫اولین نکته این اسات که‬ •

. ‫نظی کنی‬ true

‫ ذخیر میکنی ؛‬HttpContext ‫ در دیتابیس باشا آ را در‬Entity ‫ اگر‬،‫نکته دوم این که‬ •

‫ احتیااج داری و نمیخواهی دو باار از دیتاابیس‬Entity ‫چو ماا در اکشااان متا هاا باه‬
. ‫کوئری بگیری‬

. ‫ رجیستر کنی‬Configuration ‫ را در مت‬ActionFilter ‫خب بیایی این‬


services.AddScoped<ValidateCompanyExistsAttribute>();

.‫حاال نوبت لییر اکشن مت ها است‬


[HttpDelete("{id}")]
[ServiceFilter(typeof(ValidateCompanyExistsAttribute))]
public async Task<IActionResult> DeleteCompanyAsync(Guid id)
{
var company = HttpContext.Items["company"] as Company;

228
_repository.Company.DeleteCompany(company);
await _repository.SaveAsync();

return NoContent();
}

[HttpPut("{id}")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
[ServiceFilter(typeof(ValidateCompanyExistsAttribute))]
public async Task<IActionResult> UpdateCompanyAsync(Guid id, [FromBody]
CompanyForUpdateDto
company)
{
var company = HttpContext.Items["company"] as Company;

_mapper.Map(company, companyEntity);
await _repository.SaveAsync();

return NoContent();
}
. ‫ اکشنمت ها ب و ک های کراری بسیار میو ر ش ان‬، ‫همانطور که میبینی‬

. ‫ست کنی‬ Put ‫ و‬Delete ‫ این اکشنمت ها را با ریکوئستهای‬Postman ‫می وانی در‬

‫ ه باایا کرار شاااود (باه جو‬EmployeesController ‫ماام مراحلی کاه در بااال انجاام دادی برای‬
.)‫برخی اختالفات در اجرای فیلتر‬

. ‫بیایی ببینی چطور این کار را انجام دهی‬

‫ یک کالس با نام‬ActionFilters ‫در فول ر‬ •

. ‫ ایجاد کنی‬ValidateEmployeeForCompanyExistsAttribute
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Infrastructure.ActionFilters
{
229
public class ValidateEmployeeForCompanyExistsAttribute :
IAsyncActionFilter
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;

public ValidateEmployeeForCompanyExistsAttribute(IRepositoryManager
repository, ILoggerManager logger)
{
_repository = repository;
_logger = logger;
}

public async Task OnActionExecutionAsync(ActionExecutingContext


context, ActionExecutionDelegate next)
{
var method = context.HttpContext.Request.Method;
var trackChanges = (method.Equals("PUT") ||
method.Equals("PATCH")) ? true : false;

var companyId = (Guid)context.ActionArguments["companyId"];


var company = await
_repository.Company.GetCompanyAsync(companyId, false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't
exist in the database.");
context.Result = new NotFoundResult();

return;
}

var id = (Guid)context.ActionArguments["id"];
var employee = await
_repository.Employee.GetEmployeeAsync(companyId, id,
trackChanges);

if (employee == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");

230
context.Result = new NotFoundResult();
}
else
{
context.HttpContext.Items.Add("employee", employee);
await next();
}
}
}

}
.‫ است‬Configuration ‫مرحله دوم رجیستر کرد این کالس در مت‬ •

services.AddScoped<ValidateEmployeeForCompanyExistsAttribute>();

EmployeesController ‫ را در‬Patch ‫ و‬Delete ،PUT ‫در لاایاا باایا کا هاای اکشااان متا هاای‬
. ‫ لایین را اضافه کنی‬Namespace ‫ اما قبل از انجام این لییرات حتما‬. ‫لییر دهی‬
using CompanyEmployee.API.Infrastructure.ActionFilters;

: Patch ‫ و‬Delete ،PUT ‫اکشن متدهای‬


[HttpDelete("{id}")]
[ServiceFilter(typeof(ValidateEmployeeForCompanyExistsAttribute))]
public async Task<IActionResult> DeleteEmployeeForCompanyAsync(Guid
companyId, Guid id)
{
var employeeForCompany = HttpContext.Items["employee"] as Employee;
_repository.Employee.DeleteEmployee(employeeForCompany);

await _repository.SaveAsync();

return NoContent();
}

[HttpPut("{id}")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
[ServiceFilter(typeof(ValidateEmployeeForCompanyExistsAttribute))]
public async Task<IActionResult> UpdateEmployeeForCompanyAsync(Guid
companyId, Guid id,[FromBody] EmployeeForUpdateDto employee)
{
var employeeEntity = HttpContext.Items["employee"] as Employee;

231
_mapper.Map(employee, employeeEntity);

await _repository.SaveAsync();

return NoContent();
}

[HttpPatch("{id}")]
[ServiceFilter(typeof(ValidateEmployeeForCompanyExistsAttribute))]
public async Task<IActionResult>
PartiallyUpdateEmployeeForCompanyAsync(Guid companyId, Guid id, [FromBody]
JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is null.");

return BadRequest("patchDoc object is null");


}

var employeeEntity = HttpContext.Items["employee"] as Employee;


var employeeToPatch =
_mapper.Map<EmployeeForUpdateDto>(employeeEntity);

patchDoc.ApplyTo(employeeToPatch, ModelState);
TryValidateModel(employeeToPatch);

if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the patch document");

return UnprocessableEntity(ModelState);
}

_mapper.Map(employeeToPatch, employeeEntity);
await _repository.SaveAsync();

return NoContent();
}

. ‫ست کنی‬ Postman ‫این لییرات را می وا با ریکوئستهای‬

232
Paging, Filtering, Searching : ‫فصل نهم‬

:‫آنچه خواهید آموخت‬


Paging, Filtering, Searching ‫➢ آشنایی و پیادهسازی‬
‫‪ Paging‬چیست؟‬
‫‪Web‬‬ ‫ا حاال ویژگیهای زیادی به لروه اضاافه کردی اما هنوز کارهایی وجود دارد که می وان‬
‫‪ API‬را بهتر کن ‪ .‬یکی از این کارها‪ ،‬اعمال ‪ Paging‬به ‪ API‬است‪.‬‬

‫‪ Paging‬یعنی‪ ،‬فقط قسمتی از نتیجه را از ‪ API‬بگیری ‪ .‬صور کنی میلیو ها رکورد در دیتابیس‬
‫وجود دارد و با یک ریکوئساات بخواهی همهی این اطالعات را به یکبار واکشاای کنی ‪ .‬به نظر‬
‫شما چه اتفاقی میافتد؟‬

‫واکشای مام اطالعات یک روش بسایار ناکارآم اسات و عالو بر مشاکالت لرفورمنسای و کن ی‬
‫‪ ،API‬می وان أثیرات ب ی بر اللیکیشان یا ساخت افواری که در آ اجرا میشاود داشاته باشا ‪.‬‬
‫همچنین منابع حافظهی هر کالینت‪ ،‬مو ود است بنابراین ع اد نتای ه بای مو ود شود‪.‬‬

‫خب حاال که متوجه صاورت مسالله شا ی ‪ ،‬بیایی با اعمال ‪ Paging‬از این لیام ها جلوگیری و‬
‫نها ع اد مشخصی از نتای را به کالینت برگردانی ‪.‬‬

‫پیادهسازی ‪Paging‬‬
‫اگر اکشااانمتا ‪ GetEmployeesForCompanyAsync‬را بررسااای کنیا ‪ ،‬میبینیا کاه ماام‬
‫میشاود‪ .‬در اینجا میخواهی ویژگی‬ ‫اطالعات کارمن ا یک شارکت‪ ،‬به صاورت یکجا برگردان‬
‫‪ Paging‬را به این اکشنمت اضافه کنی ا از مشکالت لرفورمنسی جلوگیری شود‪.‬‬

‫لییر کن و یا اینکه منطق‬ ‫‪Base Repository‬‬ ‫وجه داشاااته باشا ای که قرار نیسااات ک های‬
‫بیوینسی خاصی را در کنترلر لیاد سازی نمایی ‪.‬‬

‫ما نها کاری که بای انجام دهی این اسات که ع اد نتای و شامار صا وه را از ‪ URL‬بگیری و‬
‫متناسب با درخواست کالینت‪ ،‬ع ادی نتای را برگردانی ‪.‬‬
‫‪https://localhost:5001/api/companies/companyId/employees?pa‬‬
‫‪geNumber=3&pageSize=2‬‬

‫در ‪ URL‬باال‪ ،‬کالینت درخواساات میکن ا اللیکیشاان در صا وه سااوم‪ ،‬اطالعات دو کارمن را‬
‫برگردان ‪.‬‬

‫نکته!!‬

‫‪234‬‬
‫باید ‪ API‬را طوری محدود کنیم که‪ ،‬حتی در صتورتی که ریکوئستت پایین صتدا زده شتد‪ ،‬اطالعات‬
‫همه کارمندان برگردانده نشود‪.‬‬
‫‪https://localhost:5001/api/companies/companyId/employees‬‬

‫قبل از هر گونه لییر در اکشن مت ‪ ،‬چن کار بای انجام شود‪.‬‬

‫برای درخواستتت شتتماره صتتفحه و تعداد کارمندان‪ ،‬باید از پارامترهای کوئری‬ ‫•‬

‫استفاده کنیم؛ پس در اکشن متد از [‪ ]FromQuery‬استفاده نمایید‪.‬‬


‫‪ ،Employee‬بته یتک کتالس‬ ‫‪Entity‬‬ ‫بترای نتگتهتداری پتارامتتترهتای‬ ‫•‬

‫‪ EmployeeParameters‬نیاز داریم‪.‬‬

‫لس اولین گام ایجاد یک فول ر به نام ‪ RequestFeatures‬در لروه ‪ Entities‬است‪.‬‬

‫درو این فول ر یک کالس با نام ‪ RequestParameters‬اضافه نمایی ‪.‬‬


‫‪namespace Entities.RequestFeatures‬‬
‫{‬
‫‪public abstract class RequestParameters‬‬
‫{‬
‫;‪const int maxPageSize = 50‬‬
‫;‪public int PageNumber { get; set; } = 1‬‬
‫;‪private int _pageSize = 10‬‬
‫‪public int PageSize‬‬
‫{‬
‫‪get‬‬
‫{‬
‫;‪return _pageSize‬‬
‫}‬
‫‪set‬‬
‫{‬
‫;‪_pageSize = (value > maxPageSize) ? maxPageSize : value‬‬
‫}‬
‫}‬
‫}‬

‫‪235‬‬
public class EmployeeParameters : RequestParameters
{
}

:‫بررسی کد‬

‫ اسات که شاامل لرالر یهای مشاترک‬Abstract ‫ یک کالس‬RequestFeatures ‫کالس‬ •

. ‫ها میباش‬Entity ‫مام‬


.‫ را در خود نگاه میدارد‬Entity ‫ لاارامترهاای خااا‬، ‫ ه‬EmployeeParameters ‫کالس‬ •

. ‫البته اال لارامتری ن ارد و این لارامترها در طول کار اضافه خواه ش‬
‫ را‬API ‫ داری کاه‬maxPageSize ‫ باه ناام‬Constant ‫ یاک‬RequestFeatures ‫در کالس‬ •

. ‫ رکورد اطالعات میکن‬50 ‫ح اکثر‬ ‫مو ود به نمای‬


‫ داری که اگر وساط کالینت‬PageSize ‫ و‬PageNumber ‫ به نام‬public ‫ما دو لرالر ی‬ •

. ‫ نظی خواه ش‬10 ‫ و‬1 ‫ به ر یب بر روی‬، ‫باش‬ ‫نظی نش‬


‫کااالس‬ using ‫باارگااردیاا و‬ EmployeesController ‫حاااال ماای ااواناایاا بااه‬ •

. ‫ را اضافه کنی‬EmployeeParameters
using Entities.RequestFeatures;

،‫ لس همانن ک لایین‬. ‫ بای منطق ریپازیتوری را لیاد سااازی کنی‬،‫لس از این لییرات‬ •

‫و کاالس‬ IEmployeeRepository ‫را در ایاناتارفایاس‬ GetEmployeesAsync ‫مات ا‬


. ‫ اصالح کنی‬EmployeeRepository

: IEmployeeRepository ‫کدهای‬
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Entities.RequestFeatures;

namespace Contracts.IServices

236
{
public interface IEmployeeRepository
{
Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, bool
trackChanges);
Task<Employee> GetEmployeeAsync(Guid companyId, Guid id, bool
trackChanges);
Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId,
EmployeeParameters employeeParameters, bool trackChanges);
void CreateEmployeeForCompany(Guid companyId, Employee employee);
void DeleteEmployee(Employee employee);
}
}
: EmployeeRepository ‫کدهای‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using Entities.RequestFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{

public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext) : base(companyEmployeeDbContext)
{
}
public async Task<IEnumerable<Employee>> GetEmployeesAsync (Guid
companyId, bool trackChanges) =>
await FindAll(trackChanges)
.OrderBy(c => c.Name)

237
.ToListAsync();

public async Task<Employee> GetEmployeeAsync (Guid companyId, Guid


id, bool trackChanges) =>
await FindByCondition(e => e.CompanyId.Equals(companyId) &&
e.Id.Equals(id), trackChanges).SingleOrDefaultAsync();

public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid


companyId, EmployeeParameters employeeParameters, bool
trackChanges) =>
await FindByCondition(e => e.CompanyId.Equals(companyId),
trackChanges)
.OrderBy(e => e.Name)
.Skip((employeeParameters.PageNumber - 1) *
employeeParameters.PageSize)
.Take(employeeParameters.PageSize)
.ToListAsync();

public void CreateEmployeeForCompany(Guid companyId, Employee


employee)
{
employee.CompanyId = companyId;
Create(employee);
}

public void DeleteEmployee(Employee employee)


{
Delete(employee);
}

}
. ‫ را لییر دهی‬GetEmployeesForCompanyAsync ‫خب حاال بای ب نه اکشنمت‬ •

[HttpGet(Name = "GetEmployeeForCompany")]
public async Task<IActionResult> GetEmployeesForCompanyAsync(Guid
companyId, [FromQuery] EmployeeParameters employeeParameters)
238
{
var company = await _repository.Company.GetCompanyAsync(companyId,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb = await


_repository.Employee.GetEmployeesAsync(companyId,
employeeParameters, trackChanges: false);

var employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDto);
}

. ‫ حتما کارمن ا بیشتری اضافه کنی‬،API ‫قبل ست این‬


https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?pageNumber=1&pageSize=3

239
‫همانطور که میبینی در صا وه اول‪ 3 ،‬کارمن را درخواسات کردی و نتیجه باال بازگشات داد‬
‫ش ‪.‬‬

‫اگر این هما چیوی است که شما ه ب ست آورد ای لس در مسیر صویح هستی ‪.‬‬

‫بیایی نتیجه را در دیتابیس ه بررسی کنی ‪.‬‬

‫ارتقای ‪Paging‬‬
‫‪List‬‬ ‫اا اینجاا‪ ،‬فقط یاک ‪ List‬باه کالینات برمیگردانا ی ؛ اماا ممکن اسااات بخواهیا باه جاای یاک‬
‫ساد ‪ ،‬یک ‪ PagedList‬به کالینت برگردانی ‪.‬‬

‫‪Skip/Take‬‬ ‫ارثبری میکن و ما می وانی منطق‬ ‫‪List‬‬ ‫‪ PagedList‬کالسای اسات که‪ ،‬از کالس‬
‫را ه ‪ ،‬در آ داشته باشی ‪.‬‬

‫بنابراین بیایی در مسیر ‪ Entities/RequestFeatures‬یک کالس با نام ‪ MetaData‬ایجاد کنی ‪.‬‬


‫‪namespace Entities.RequestFeatures‬‬
‫{‬
‫‪public class MetaData‬‬
‫{‬
‫} ;‪public int CurrentPage { get; set‬‬
‫} ;‪public int TotalPages { get; set‬‬
‫} ;‪public int PageSize { get; set‬‬
‫} ;‪public int TotalCount { get; set‬‬
‫;‪public bool HasPrevious => CurrentPage > 1‬‬
‫;‪public bool HasNext => CurrentPage < TotalPages‬‬
‫‪240‬‬
}

. ‫ را اضافه کنی‬PagedList ‫ کالس‬،‫حاال در همین فول ر‬


using System;
using System.Collections.Generic;
using System.Linq;

namespace Entities.RequestFeatures
{
public class PagedList<T> : List<T>
{
public MetaData MetaData { get; set; }
public PagedList(List<T> items, int count, int pageNumber, int
pageSize)
{
MetaData = new MetaData
{
TotalCount = count,
PageSize = pageSize,
CurrentPage = pageNumber,
TotalPages = (int)Math.Ceiling(count / (double)pageSize)
};

AddRange(items);
}

public static PagedList<T> ToPagedList(IEnumerable<T> source, int


pageNumber, int pageSize)
{
var count = source.Count();

var items = source


.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();

return new PagedList<T>(items, count, pageNumber, pageSize);


}
}

241
:‫بررسی کد‬

. ‫ اضافه کردی‬ToPagedList ‫ را در‬Skip/Take ‫همانطور که میبینی منطق‬ •

‫ اا برای‬، ‫ لر کردی‬Constructor ‫ باا مقاادیر ورودی‬، ‫ را ه‬MetaData ‫لرالر یهاای کالس‬ •

.‫ی ریسپانس است اد شود‬Metadata


، ‫ باشا‬1 ‫بورگتر از‬ CurrentPage ‫ اگر‬،‫ نگا کنی میبینی که‬MetaData ‫اگر به کالس‬ •

‫ کوچکتر از ع اد کل‬CurrentPage ‫ و در صااور ی که‬true ‫ برابر‬HasPrevious ‫لرالر ی‬


. ‫ مواسبه خواه ش‬HasNext ‫ لرالر ی‬، ‫ص وات باش‬
‫ ب ساات میآی و‬Page Size ‫ با قس ای ع اد آیت ها به ع د‬، ‫ ه‬TotalPages ‫لرالر ی‬ •

. ‫سپس این ع د به ع د بورگتر گرد خواه ش‬

‫ را ه لییر‬EmployeesController ‫ و‬EmployeeRepository ‫خب حاال بیااییا به همین ر یاب‬


. ‫دهی‬

: IEmployeeRepository ‫کدهای‬
using Entities.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Entities.RequestFeatures;

namespace Contracts.IServices
{
public interface IEmployeeRepository
{
Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, bool
trackChanges);
Task<Employee> GetEmployeeAsync(Guid companyId, Guid id, bool
trackChanges);
Task<PagedList<Employee>> GetEmployeesAsync(Guid companyId,
EmployeeParameters employeeParameters, bool trackChanges);
void CreateEmployeeForCompany(Guid companyId, Employee employee);
void DeleteEmployee(Employee employee);
}

242
}
: EmployeeRepository ‫کدهای‬
using Contracts.IServices;
using Entities;
using Entities.Models;
using Microsoft.EntityFrameworkCore;
using Entities.RequestFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{

public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext) : base(companyEmployeeDbContext)
{
}
public async Task<IEnumerable<Employee>> GetEmployeesAsync (Guid
companyId, bool trackChanges) =>
await FindAll(trackChanges)
.OrderBy(c => c.Name)
.ToListAsync();

public async Task<Employee> GetEmployeeAsync (Guid companyId, Guid


id, bool trackChanges) =>
await FindByCondition(e => e.CompanyId.Equals(companyId) &&
e.Id.Equals(id), trackChanges).SingleOrDefaultAsync();

public async Task<PagedList<Employee>> GetEmployeesAsync(Guid


companyId,EmployeeParameters employeeParameters, bool
trackChanges)
{

243
var employees = await FindByCondition(e =>
e.CompanyId.Equals(companyId),
trackChanges)
.OrderBy(e => e.Name)
.ToListAsync();

return PagedList<Employee>
.ToPagedList(employees, employeeParameters.PageNumber,
employeeParameters.PageSize);
}

public void CreateEmployeeForCompany(Guid companyId,


Employee employee)
{
employee.CompanyId = companyId;
Create(employee);
}

public void DeleteEmployee(Employee employee)


{
Delete(employee);
}

}
. ‫ را لییر دهی‬GetEmployeesForCompanyAsync ‫خب حاال بای ب نه اکشنمت‬

. ‫ لایین را در کنترلر اضافه کنی‬Namespace ‫ابت ا‬


using Newtonsoft.Json;

. ‫سپس ب نه اکشن مت را لییر دهی‬


[HttpGet(Name = "GetEmployeeForCompany")]

public async Task<IActionResult> GetEmployeesForCompanyAsync(Guid


companyId, [FromQuery] EmployeeParameters employeeParameters)
{
var company = await _repository.Company.GetCompanyAsync(companyId,
trackChanges: false);
244
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb = await


_repository.Employee.GetEmployeesAsync(companyId,
employeeParameters, trackChanges: false);

Response.Headers.Add("X-Pagination",
JsonConvert.SerializeObject(employeesFromDb.MetaData));

var employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDto);
}

‫ میبینی که نتیجه با ریکوئساتی که باال ر انجام‬، ‫حال اگر ریکوئسات لایین را دوبار ارساال کنی‬
.‫دادی یکی است‬
https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?pageNumber=1&pageSize=3

245
‫‪X-‬‬ ‫اوت این ریکوئسااات با قبلی در این اسااات که‪ ،‬حاال اطالعات م ی ی در ه ر ریساااپانس‬
‫‪ Pagination‬داری ‪.‬‬

‫هماانطور کاه میبینیا ماام لرالر یهاای ‪ Metadata‬در ‪ X-Pagination‬وجود دارنا ‪ .‬ماا می وانی‬
‫از این اطالعات برای ایجاد لینک به ص وه قبلی و بع ی است اد کنی ‪.‬‬

‫‪ Filtering‬چیست؟‬
‫‪ Filtering‬مکانیویمی اسات که‪ ،‬نتای را با وجه به برخی معیارها واکشای میکن و ما می وانی‬
‫به اطالعات دقیق ر برسی ‪.‬‬

‫فیلترهاا را می وا باا وجاه باه نو لرالر ی‪ ،‬رن عا دی‪ ،‬موا ود ااریخ و یاا هر چیو دیگری‬
‫عری شاا‬ ‫نوشااات‪ .‬در هنگام اجرای فیلتر‪ ،‬همیشاااه مو ود به مجموعه گوینههای از لی‬
‫هستی که می وانی در ریکوئست خود آ ها را نظی کنی ‪.‬‬

‫‪ Filtering‬در سمت ‪ Frontend‬به صورت ‪ Radio Button ، Dropdown‬یا ‪ Checkbox‬لیاد سازی‬


‫میشود‪ .‬این نو لیاد سازی شما را به گوینههایی مو ود میکن ‪ .‬به عنوان مثال ‪:‬‬

‫یاک وب ساااایات فروش ا ومبیال را در نظر بگیریا ‪ .‬هنگاام فیلتر کرد ا ومبیالهاای مورد نظر‬
‫خود‪ ،‬می وانی گوینههای لایین را انتخاب کنی ‪:‬‬

‫‪Dropdown‬‬ ‫ماشین در یک لیست‬ ‫ولی کنن‬ ‫•‬

‫‪Dropdown‬‬ ‫م ل ماشین در یک لیست‬ ‫•‬

‫یک ‪ Radio Button‬برای مشخش کرد اینکه ماشین ج ی است یا خیر‪.‬‬ ‫•‬

‫‪TextBox‬‬ ‫قیمت خودرو در یک‬ ‫•‬

‫و‪..‬‬ ‫•‬

‫بنابراین ریکوئست برای این وب سایت‪ ،‬می وان چیوی شبیه به این است‪.‬‬

‫‪246‬‬
https://bestcarswebsite.com/sale?manufacturer=ford&model=expedition&
state=used&city=washington&price_from=30000&price_to=50000

. ‫یا شای ه شبیه لایین باش‬


https://bestcarswebsite.com/sale/filter?data[manufacturer]=ford&[mod
el]=expedition&[state]=used&[city]=washington&[price_from]=30000&[pr
ice_to]=50000

. ‫ را دانستی بیایی آ را لیاد سازی کنی‬Filtering ‫حاال که م هوم‬

ASP.NET Core ‫ در‬Filtering ‫پیادهسازی‬


‫ را‬Filtering ‫ وجود دارد که بر روی آ می وانی عمل‬Age ‫ لرالر ی به نام‬،Employee ‫در کالس‬
: ‫ به طور مثال‬. ‫لیاد کنی‬

.‫ سال‬29 ‫ ا‬26 ‫کارمن ا بین‬


https://localhost:5001/api/companies/companyId/employees?minAge=26&maxAge=2
9

. ‫ را داری‬Filtering ‫ را در کنترلر لیاد سازی کردی ؛ لس زیرساختهای الزم برای‬Paging ً‫قبال‬

‫ اضاافه کردی لس‬Paging ‫ لارامترهایی را برای ریکوئسات‬،EmployeeParameters ‫ما در کالس‬


. ‫ ه انجام دهی‬Filtering ‫بای همین کالس را برای عمل‬
public class EmployeeParameters : RequestParameters
{
public uint MinAge { get; set; }
public uint MaxAge { get; set; } = int.MaxValue;
public bool ValidAgeRange => MaxAge > MinAge;
}

: ‫بررسی کد‬

‫ اضاافه کردی ا ع د ساال‬MaxAge ‫ و‬MinAge ‫ با نامهای‬uint ‫در این کالس دو لرالر ی‬ •

.‫من ی وارد نشود‬


‫ لس نیازی نیسات مق ار لرالر یهای‬،‫ ع د صا ر اسات‬uint ‫فرض‬ ‫از آنجا که مق ار لی‬ •

. ‫آ را ص ر عری کنی‬

247
‫ بررسای این‬، ‫ داری که ه ف‬ValidAgeRange ‫ با نام‬، ‫در اینجا یک اعتبارسانجی سااد‬ •

‫ بای به کالینت اطال‬، ‫ اگر اینطور نباشا‬.‫ اسات‬MinAge ‫ بیشاتر از‬MaxAge ‫اسات که آیا‬
.‫دهی که مقادیرش اشتبا است‬

‫خب حاال که لارامترهای خود را آماد کردی بیایی یک اعتبارسانجی برای بررسای اکشان مت‬
. ‫ اضافه کنی‬GetEmployeesForCompanyAsync
[HttpGet(Name = "GetEmployeeForCompany")]
public async Task<IActionResult> GetEmployeesForCompanyAsync(Guid
companyId, [FromQuery] EmployeeParameters employeeParameters)
{
var company = await _repository.Company.GetCompanyAsync(companyId,
trackChanges: false);

if (!employeeParameters.ValidAgeRange)
return BadRequest("Max age can't be less than min age.");

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb = await


_repository.Employee.GetEmployeesAsync(companyId,
employeeParameters, trackChanges: false);

Response.Headers.Add("X-Pagination",
JsonConvert.SerializeObject(employeesFromDb.MetaData));

var employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(employeesDto);
}

‫ فقط اعتبارساانجی انجام دادی و نتیجه را با‬، ‫همانطور که میبینی چیو زیادی اضااافه نکردی‬
. ‫ به کالینت برگردان ی‬BadRequest ‫یک لیام‬

248
EmployeeRepository ‫ کالس‬GetEmployeesAsync ‫حااال باایا یاک لییر کوچاک در متا‬
. ‫ را ب ست آوری‬MinAge ‫ و‬MaxAge ‫ ا مام کارمن ا با سن بین‬، ‫انجام دهی‬
public async Task<PagedList<Employee>> GetEmployeesAsync(Guid
companyId,EmployeeParameters employeeParameters, bool trackChanges)
{
var employees = await FindByCondition(e =>
e.CompanyId.Equals(companyId) && (e.Age >= employeeParameters.MinAge
&& e.Age <= employeeParameters.MaxAge),
trackChanges)
.OrderBy(e => e.Name)
.ToListAsync();

return PagedList<Employee>
.ToPagedList(employees, employeeParameters.PageNumber,
employeeParameters.PageSize);
}

Filtering ‫ارسال و تست‬


. ‫ ارسال کنی‬MinAge ‫بیایی اولین ریکوئست را با یک لارامتر‬
https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?minAge=32

. ‫ ارسال کنی‬MaxAge ‫حاال بیایی با لارامتر‬

249
https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?maxAge=26

. ‫حاال رکیب هر دو لارامتر را است اد کنی‬


https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?minAge=26&maxAge=35

. ‫ست کنی‬ Paging ‫ را با‬Filtering ‫و در لایا می وانی‬

250
‫‪https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-‬‬
‫‪2d54a9991870/employees?pageNumber=1&pageSize=&3minAge=26&maxAge=35‬‬

‫عالی شد‪.‬‬

‫مام ش ‪ .‬حاال می وانی ‪ Searching‬را لیاد سازی کنی ‪.‬‬ ‫‪Filtering‬‬ ‫لیاد سازی‬

‫‪ Searching‬چیست؟‬
‫‪API‬‬ ‫جساتجو قریباً در هر وب ساایتی وجود دارد‪ .‬جساتجو یکی از ویژگیهایی اسات که می وان‬
‫را درست یا خراب کن ؛ و میوا دشواری آ ‪ ،‬بسته به مشخصات اللیکیشن دارد‪.‬‬

‫شاای در وب ساایتی که با سااختارش آشانا هساتی ‪ ،‬بتوانی هر چیوی را لی ا کنی ؛ اما اگر وارد‬
‫یاک ساااایات بورگ شاااویا مطملناا نمیدانیا کاه چاه مطاالبی وجود دارد و چاه چیوی را باایا‬
‫جستجو کنی ‪ .‬اینجاست که نیاز به جستجو داری ‪.‬‬

‫در این لروه میخواهی یک جساتجو سااد برای یافتن اطالعات یک کارمن داشاته باشای ‪ ،‬اما‬
‫قبل از لیاد سازی‪ ،‬بیایی کمی در مورد ‪ Searching‬ب انی ‪.‬‬

‫در ‪ Searching‬معموالً یاک ورودی داری کاه از آ برای جساااتجوی هر چیوی در وب‬ ‫•‬

‫ساایت اسات اد میشاود‪ .‬بنابراین شاما عبار ی را به ‪ API‬ارساال میکنی و ‪ API‬مسالول‬

‫‪251‬‬
‫اسات اد از آ عبارت برای یافتن نتیجهای اسات که با آ مطابقت داشاته باشا ‪ .‬به طور‬
‫مثال‪:‬‬

‫در وب سااایت ا ومبیل‪ ،‬ما از قساامت جسااتجو‪ ،‬م ل ا ومبیل را ایپ میکنی و مام‬
‫نتای مربوط به آ ا ومبیل به ما بازگشت داد میشود‪.‬‬

‫• ما می وانی لیاد ساااازی ‪ Searching‬را طوری انجام دهی که اگر کاربر عبارت را ب و‬
‫کو یشاان جسااتجو کرد‪ ،‬مام نتای مربوط به عبارت برگردان شااود‪ .‬و اگر عبارت را در‬
‫کو یشن نوشت‪ ،‬بای دقیقا نتای با عبارت مطابقت داشته باش ‪.‬‬

‫اسات اد از جساتجو به معنای این نیسات که‪ ،‬ما نمی وانی از ‪ Filtering‬اسات اد کنی ‪ .‬اسات اد از‬
‫‪ Filtering‬و جساتجو در کنار ه ‪ ،‬کامالً منطقی اسات بنابراین بای هنگام ک نویسای این موضاو‬
‫را در نظر بگیری ‪.‬‬

‫لوری کافی است بیایی کمی ک بونی ‪.‬‬

‫پیادهسازی جستجو در اپلیکیشن‬


‫چو قبال زیرسااااخات مورد نیااز برای ‪ Paging‬و ‪ Filtering‬اضاااافاه کردی ‪ ،‬لس لیااد ساااازی‬
‫نیساات‪ .‬نها کاری که بای انجام دهی این اساات که این زیر‬ ‫‪ Searching‬به هیچ وجه لیچی‬
‫ساخت را کمی گسترش دهی ‪.‬‬

‫چیوی که میخواهی به آ برسی شبیه به ‪ API‬لایین است‪.‬‬


‫‪https://localhost:5001/api/companies/companyId/employees?se‬‬
‫‪archTerm=Zahra‬‬

‫در این جساتجو میخواهی مام کساانی که نامشاا ‪ Zahra‬اسات را برگردانی ‪ .‬در نظر داشاته‬
‫باشی که این جستجو بای همرا با ‪ Paging‬و ‪ Filtering‬کار کن ‪.‬‬

‫اولین کاااری کااه بااای ا انجااام دهی اضااااافااه کرد یااک لااارامتر جساااتجو در کالس‬
‫‪ EmployeeParameters‬است‪.‬‬
‫‪public class EmployeeParameters : RequestParameters‬‬
‫{‬
‫} ;‪public uint MinAge { get; set‬‬

‫‪252‬‬
public uint MaxAge { get; set; } = int.MaxValue;
public bool ValidAgeRange => MaxAge > MinAge;
public string SearchTerm { get; set; }
}

.‫به همین سادگی‬

. ‫ را بنویسی‬searchTerm=”name” ‫حاال می وانی کوئری‬

‫ فول ری با‬Repository ‫ لس در لروه‬. ‫ بای مت ی داشاته باشای‬،‫خب حاال برای انجام جساتجو‬
. ‫ را در آ اضافه کنی‬RepositoryEmployeeExtensions ‫ ایجاد و سپس کالس‬Extensions ‫نام‬
using Entities.Models;
using System.Linq;

namespace Repository.Extensions
{
public static class RepositoryEmployeeExtensions
{
public static IQueryable<Employee> FilterEmployees(this
IQueryable<Employee> employees, uint minAge, uint maxAge) =>
employees.Where(e => (e.Age >= minAge && e.Age <= maxAge));

public static IQueryable<Employee> Search(this


IQueryable<Employee> employees, string searchTerm)
{
if (string.IsNullOrWhiteSpace(searchTerm))
return employees;

var lowerCaseTerm = searchTerm.Trim().ToLower();

return employees.Where(e =>


e.Name.ToLower().Contains(lowerCaseTerm));
}
}

}
. ‫ بود اکساتنشان مت ی نوشاتی‬EmployeeRepository ‫ا اینجا برای آل یت کوئریهایی که در‬
‫لاایین را باه کالس‬ Namespace ‫حااال نهاا کااری کاه باایا انجاام دهی این اساااات کاه‬
. ‫ را اصالح کنی‬GetEmployeesAsync ‫ اضافه و سپس مت‬EmployeeRepository

253
using Repository.Extensions;

: GetEmployeesAsync ‫متد‬
public async Task<PagedList<Employee>> GetEmployeesAsync(Guid
companyId,EmployeeParameters employeeParameters, bool trackChanges)
{
var employees = await FindByCondition(e =>
e.CompanyId.Equals(companyId),
trackChanges)
.FilterEmployees(employeeParameters.MinAge, employeeParameters.MaxAge)
.Search(employeeParameters.SearchTerm)
.OrderBy(e => e.Name)
.ToListAsync();

return PagedList<Employee>
.ToPagedList(employees, employeeParameters.PageNumber,
employeeParameters.PageSize);
}

Searching ‫تست پیادهسازی‬


. ‫ را برای جستجو ارسال کنی‬Zahra ‫بیایی اولین ریکوئست با مق ار‬
https://localhost:5001/api/Companies/C9D4C053-49B6-410C-BC78-
2D54A9991870/employees?searchTerm=Zahra&minAge=26&maxAge=35

254
‫فصل دهم ‪ Sorting :‬و ‪Data Shaping‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ آشنایی و پیادهسازی ‪ Sorting‬و ‪Data Shaping‬‬
‫‪ Sorting‬چیست؟‬
‫اطالعاات را ‪ Sorting‬میگوینا ‪ Sorting .‬مکاانیسااامی اسااات کاه معموالً در هر‬ ‫ر یاب نماای‬
‫‪ ،API‬بای آ را لیاد سااازی کرد‪ .‬البته در این فصاال درمورد الگوریت های ‪ Sorting‬صااوبت‬
‫نمیکنی و فقط به نوو لیاد سازی میلردازی ‪.‬‬

‫نتای ‪ ،‬به شااکلی باشاا که ما میخواهی ‪ .‬به طور‬ ‫ه ف ‪ Sorting‬این اساات که ر یب نمای‬
‫مثال ‪:‬‬

‫میخواهی نام کارمن ا را به ر یب با اسات اد از نام و ساپس سان به صاورت صاعودی مر ب‬


‫کنی ‪.‬‬

‫برای انجام این کار‪ ،‬ریکوئست ما بای چیوی شبیه به این باش ‪.‬‬
‫‪https://localhost:5001/api/companies/companyId/employees?or‬‬
‫‪derBy=name,age desc‬‬

‫‪ API‬باایا ماام لاارامترهاا را در نظر بگیرد و نتاای را طبق آ مراب کنا ‪ .‬در ریکوئسااات بااال باای‬
‫نتای را براسااس نام مر ب‪ ،‬ساپس اگر کارمن انی با نام مشابه وجود داشته باشن بای نتای را بر‬
‫اساس لرالر ی سن مر ب کن ‪.‬‬

‫پیادهسازی ‪ Sorting‬در ‪ASP.NET Core‬‬


‫‪EF‬‬ ‫لیاد سااازی ‪ Sorting‬در ‪ ASP.NET Core‬به دلیل انعطاف لذیری ‪ LINQ‬و ادغام خوب با‬
‫‪ ،Core‬کار سختی نیست‪.‬‬

‫برای لیاد ساااازی این قابلیت‪ ،‬بای همانن ساااایر ویژگیهایی که اکنو انجام دادی ‪ ،‬ابت ا در‬
‫کالس ‪ RequestParameters‬یک لرالر ی با نام ‪ OrderBy‬اضافه کنی ‪.‬‬
‫‪public abstract class RequestParameters‬‬
‫{‬
‫;‪const int maxPageSize = 50‬‬
‫;‪public int PageNumber { get; set; } = 1‬‬
‫;‪private int _pageSize = 10‬‬
‫‪public int PageSize‬‬
‫{‬
‫‪get‬‬

‫‪256‬‬
{
return _pageSize;
}
set
{
_pageSize = (value > maxPageSize) ? maxPageSize : value;
}
}

public string OrderBy { get; set; }


}

. ‫باشا‬ ‫ حتی اگر در ریکوئسات مشاخش نشا‬، ‫میخواهی نتای خود را بر اسااس نام مر ب کنی‬
‫ را لییر دهی اا در صاااور ی کاه حتی ناامی ه‬EmployeeParameters ‫لس بیااییا کالس‬
. ‫ فعال باش‬Employee ‫فرض برای‬ ‫ شرط مر ب سازی لی‬،‫بود‬ ‫مشخش نش‬
public class EmployeeParameters : RequestParameters
{
public EmployeeParameters()
{
OrderBy = "name";
}
public uint MinAge { get; set; }
public uint MaxAge { get; set; } = int.MaxValue;
public bool ValidAgeRange => MaxAge > MinAge;
public string SearchTerm { get; set; }
}

. ‫ را لیاد کنی‬Sorting ‫ بای مکانیس‬، ‫در مرحله بع‬

‫ نیااز باه لکی‬،‫ دایناامیاک در کوئری‬OrderBy ‫وجاه داشاااتاه بااشااایا کاه برای اضاااافاه کرد‬
. ‫ نصب کنی‬Repository ‫ داری لس آ را در لروه‬System.Linq.Dynamic.Core
Install-Package System.Linq.Dynamic.Core -Version 1.2.7 -ProjectName
Repository

257
. ‫ اضافه کنی‬RepositoryEmployeeExtensions ‫ لایین را در کالس‬Namespace ‫حاال‬

. ‫ بنویسی‬RepositoryEmployeeExtensions ‫ را در کالس‬Sort ‫خب بیایی اکستنشن مت‬


using Entities.Models;
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Linq.Dynamic.Core;

namespace Repository.Extensions
{
public static class RepositoryEmployeeExtensions
{
public static IQueryable<Employee> FilterEmployees(this
IQueryable<Employee> employees, uint minAge, uint maxAge) =>
employees.Where(e => (e.Age >= minAge && e.Age <= maxAge));

public static IQueryable<Employee> Search(this


IQueryable<Employee> employees, string searchTerm)
{
if (string.IsNullOrWhiteSpace(searchTerm))
return employees;

var lowerCaseTerm = searchTerm.Trim().ToLower();

return employees.Where(e =>


e.Name.ToLower().Contains(lowerCaseTerm));
}

public static IQueryable<Employee> Sort(this IQueryable<Employee>


employees, string orderByQueryString)

258
{
if (string.IsNullOrWhiteSpace(orderByQueryString))
return employees.OrderBy(e => e.Name);

var orderParams = orderByQueryString.Trim().Split(',');


var propertyInfos =
typeof(Employee).GetProperties(BindingFlags.Public |
BindingFlags.Instance);

var orderQueryBuilder = new StringBuilder();

foreach (var param in orderParams)


{
if (string.IsNullOrWhiteSpace(param))
continue;

var propertyFromQueryName = param.Split(" ")[0];


var objectProperty = propertyInfos.FirstOrDefault(pi =>

pi.Name.Equals(propertyFromQueryName,
StringComparison.InvariantCultureIgnoreCase));

if (objectProperty == null)
continue;

var direction = param.EndsWith(" desc") ? "descending" :


"ascending";

orderQueryBuilder.Append($"{objectProperty.Name.ToString(
)} {direction}, ");
}

var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', '


');

if (string.IsNullOrWhiteSpace(orderQuery))
return employees.OrderBy(e => e.Name);

return employees.OrderBy(orderQuery);
}
}

259
‫این متا خیلی کاارهاا را انجاام میدها لس بگاذاریا قا م باه قا م آ را بررسااای کنی اا ببینی‬
‫دقیقاً چه کاری انجام داد ای ‪.‬‬

‫بررسی کد ‪:‬‬

‫این متا دو آرگوماا میگیرد‪ ،‬یکی برای لیسااات کاارمنا ا و دیگری برای ریکوئسااات‬ ‫•‬

‫‪ Ordering‬است‪.‬‬
‫باا چاک کرد آرگوماا ‪ orderByQueryString‬شااارو کنی ‪ .‬اگر این آرگوماا ‪ null‬یاا‬ ‫•‬

‫خالی باش ‪ ،‬ما فقط مجموعه را مر ب میکنی و برمیگردانی ‪.‬‬


‫))‪if (string.IsNullOrWhiteSpace(orderByQueryString‬‬
‫;)‪return employees.OrderBy(e => e.Name‬‬

‫در غیر این صاورت‪ orderByQueryString ،‬را ‪ split‬کرد ا فیل های موردنظر را ب سات‬ ‫•‬

‫آوری ‪.‬‬
‫;]‪var propertyFromQueryName = param.Split(" ")[0‬‬

‫بااارای اینکاااه لیساااتی از لرالر ااایهاااای کاااالس ‪ Employee‬را ب سااات آوریا ا از‬ ‫•‬

‫‪ Reflection‬است اد کردی ‪ .‬با ایان لیسات مای اوا مطمالن شا کاه فیلا دریافات‬
‫از‪ ،Query String‬حتما در کالس ‪ Employee‬وجود داشته باش ‪.‬‬ ‫ش‬
‫| ‪var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public‬‬
‫;)‪BindingFlags.Instance‬‬

‫حااال در یاک حلقاه ‪ Foreach‬اک اک لاارامترهاا را بررسااای میکنی اا ببینی موتوای‬ ‫•‬

‫آ ها حاوی لرالر ی هست یا خیر‪.‬‬


‫))‪if (string.IsNullOrWhiteSpace(param‬‬
‫;‪continue‬‬

‫;]‪var propertyFromQueryName = param.Split(" ")[0‬‬


‫>= ‪var objectProperty = propertyInfos.FirstOrDefault(pi‬‬

‫‪pi.Name.Equals(propertyFromQueryName,‬‬
‫;))‪StringComparison.InvariantCultureIgnoreCase‬‬
‫اگر لرالر ی لی ا نکردی ‪ ،‬مرحله رفتن به لارامتر بع ی را بیخیال میشوی ‪.‬‬ ‫•‬

‫)‪if (objectProperty == null‬‬


‫;‪continue‬‬

‫‪260‬‬
‫ آ را برمیگردانی و سپس برای صمی گیری در مورد مر ب‬، ‫اگر لرالر ی را لی ا کنی‬ •

.‫ " هست یا خیر‬desc" ‫ لارامتر‬،‫ چک میکنی که در انتهای رشته‬،‫سازی‬


var direction = param.EndsWith(" desc") ? "descending" : "ascending";
orderQueryBuilder.Append($"{objectProperty.Name.ToString()}
{direction}, ");
. ‫ است اد میکنی‬StringBuilder ‫برای ساخت کوئری از‬ •

orderQueryBuilder.Append($"{objectProperty.Name.ToString()}
{direction}, ");

‫ بای کاماهای اضافی را حذف و‬، ‫ بررسی کردی‬Foreach ‫حاال که همهی لرالر یها را با‬ •

.‫ببینی آیا در ریکوئست واقعاً چیوی هست یا خیر‬


var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');

if (string.IsNullOrWhiteSpace(orderQuery))
return employees.OrderBy(e => e.Name);
. ‫در لایا می وانی کوئری خود را مر ب کنی‬ •

return employees.OrderBy(orderQuery);

“Name ascending, DateOfBirth ‫عبارت‬ ‫ بای شامل‬orderQuery ‫در این مرحله متلیر‬ •

‫به صورت صعودی و‬ Name ‫ این عبارت نتای ما را ابت ا براساس‬. ‫باش‬ descending”

. ‫ به صورت نوولی مر ب میکن‬DateOfBirth ‫سپس وسط‬

!!‫نکته‬

.‫ میتوانید به صورت پایین عمل کنید‬،‫ این عبارت‬LINQ ‫برای زدن کوئری‬
employees.OrderBy(e => e.Name).ThenByDescending(o => o.Age);

‫این کد ترفند کوچکی برای مرتبسازی کوئری است و برای زمانیکه نمیدانید چطور مرتب سازی‬
.‫کنید پرکاربرد است‬

‫ اصااالح‬EmployeeRepository ‫ را در‬GetEmployeesAsync ‫لس از نوشااتن این مت بای مت‬


. ‫کنی‬
public async Task<PagedList<Employee>> GetEmployeesAsync(Guid
companyId,EmployeeParameters employeeParameters, bool trackChanges)
{

261
var employees = await FindByCondition(e =>
e.CompanyId.Equals(companyId),
trackChanges)
.FilterEmployees(employeeParameters.MinAge,
employeeParameters.MaxAge)
.Search(employeeParameters.SearchTerm)
.Sort(employeeParameters.OrderBy)
.ToListAsync();

return PagedList<Employee>
.ToPagedList(employees, employeeParameters.PageNumber,
employeeParameters.PageSize);
}

. ‫حاال می وانی این ویژگی را ست کنی‬

Sorting ‫تست‬
. ‫ کوئری که باال ر گ تی را ست میکنی‬،‫ابت ا‬
https://localhost:5001/api/companies/ C9D4C053-49B6-410C-BC78-
2D54A9991870/employees?or
derBy=name,age desc

262
‫همانطور که میبینی لیست به صورت صعودی مر ب ش ‪.‬‬

‫‪ Data Shaping‬چیست؟‬
‫‪Data‬‬ ‫از ‪ API‬به کالینت اسات‪.‬‬ ‫رافیک ارساال شا‬ ‫‪ Data Shaping‬یک روش عالی برای کاه‬
‫‪ ،Query‬داد هاا‬ ‫‪String‬‬ ‫‪ Shaping‬باه کالینات این امکاا را میدها ‪ ،‬اا باا انتخااب فیلا هاا از طریق‬
‫را انتخاب و شکل ده ‪.‬‬

‫ما می وانی برای کالینت امکانی را فراه کنی ا فیل های مورد نیازش را به درساتی انتخاب و‬
‫دهی ‪ .‬البته وجه داشاته باشای که ‪ Data Shaping‬چیوی‬ ‫با این کار اساترس را در ‪ API‬کاه‬
‫نیساات که هر ‪API‬ی به آ نیاز داشااته باش ا ؛ چو با ‪ Reflection‬همرا اساات و همانطور که‬
‫میدانی ‪ Reflection‬مشااکال ی دارد و ‪ Performance‬ما را لایین میآورد‪ .‬بنابراین بای با دقت‬
‫صمی بگیری که آیا نیاز به لیاد سازی داری یا خیر‪.‬‬

‫‪ Data‬باا م ااهیمی ‪ Searching ،Filtering ،Paging‬و‬ ‫‪Shaping‬‬ ‫در لاایاا باایا مثال همیشاااه‬
‫‪ Sorting‬به خوبی کار کن ‪.‬‬

‫چطور ‪ Data Shaping‬را پیادهسازی کنیم؟‬


‫چو میخواهی لرالر ی ج ی ی را به ‪ Query String‬اضااافه کنی و این لرالر ی بای برای هر‬
‫‪ Entity‬وجود داشته باش ‪ ،‬لس ابت ا بای کالس ‪ RequestParameters‬را لییر دهی ‪.‬‬
‫‪public abstract class RequestParameters‬‬
‫{‬
‫;‪const int maxPageSize = 50‬‬
‫;‪public int PageNumber { get; set; } = 1‬‬
‫;‪private int _pageSize = 10‬‬
‫‪public int PageSize‬‬
‫{‬
‫‪get‬‬
‫{‬
‫;‪return _pageSize‬‬
‫}‬
‫‪set‬‬
‫{‬

‫‪263‬‬
_pageSize = (value > maxPageSize) ? maxPageSize : value;
}
}

public string OrderBy { get; set; }


public string Fields { get; set; }
}

Query ‫لارامتر‬ ‫ را اضااافه کردی و حاال می وانی از این لرالر ی به عنوا یک‬Fields ‫ما لرالر ی‬
. ‫ است اد کنی‬String

‫ این‬Contracts/ IServices ‫ در مساایر‬،IDataShaper ‫بیایی با ایجاد یک اینترفیس ج ی با نام‬


. ‫کار را ادامه دهی‬
using System.Collections.Generic;
using System.Dynamic;

namespace Contracts.IServices
{
public interface IDataShaper<T>
{
IEnumerable<ExpandoObject> ShapeData(IEnumerable<T> entities,
string
fieldsString);

ExpandoObject ShapeData(T entity, string fieldsString);


}

}
: ‫بررسی کد‬

‫هاا کاه‬Entity ‫ و دیگری برای مجموعاه‬Entity ‫این اینترفیس دو متا دارد؛ یکی برای یاک‬ •

‫ ناامگاذاری شااا انا اماا‬ShapeData ‫ هر دوی این متا هاا‬.‫باایا لیااد ساااازی شاااود‬
. ‫های مختل ی دارن‬Signature
‫ اا‬، ‫ اسااات ااد کردی‬Return Type ‫ باه عنوا‬ExpandoObject ‫وجاه کنیا کاه از کالس‬ •

. ‫داد هایما را به صور ی که میخواهی شکل دهی‬

264
DataShaping ‫ فول ر ج ی ی با نام‬Repository ‫ بای در لروه‬،‫برای لیاد سااازی این اینترفیس‬
. ‫ اضافه نمایی‬DataShaper ‫ایجاد و در آ کالسی با نام‬
using Contracts.IServices;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Repository.DataShaping
{
public class DataShaper<T> : IDataShaper<T> where T : class
{
public PropertyInfo[] Properties { get; set; }
public DataShaper()
{
Properties = typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.Instance);
}

public IEnumerable<ExpandoObject> ShapeData(IEnumerable<T>


entities, string fieldsString)
{
var requiredProperties = GetRequiredProperties(fieldsString);

return FetchData(entities, requiredProperties);


}

public ExpandoObject ShapeData(T entity, string fieldsString)


{
var requiredProperties = GetRequiredProperties(fieldsString);

return FetchDataForEntity(entity, requiredProperties);


}

private IEnumerable<PropertyInfo> GetRequiredProperties(string


fieldsString)
{
var requiredProperties = new List<PropertyInfo>();
if (!string.IsNullOrWhiteSpace(fieldsString))
{
var fields = fieldsString.Split(',',
StringSplitOptions.RemoveEmptyEntries);
265
foreach (var field in fields)
{
var property = Properties
.FirstOrDefault(pi => pi.Name.Equals(field.Trim(),
StringComparison.InvariantCultureIgnoreCase));

if (property == null)
continue;

requiredProperties.Add(property);
}
}
else
{
requiredProperties = Properties.ToList();
}

return requiredProperties;
}

private IEnumerable<ExpandoObject> FetchData(IEnumerable<T>


entities,
IEnumerable<PropertyInfo> requiredProperties)
{
var shapedData = new List<ExpandoObject>();

foreach (var entity in entities)


{
var shapedObject = FetchDataForEntity(entity,
requiredProperties);

shapedData.Add(shapedObject);
}

return shapedData;
}

private ExpandoObject FetchDataForEntity(T entity,


IEnumerable<PropertyInfo> requiredProperties)
{
var shapedObject = new ExpandoObject();

foreach (var property in requiredProperties)


{
var objectPropertyValue = property.GetValue(entity);

266
shapedObject.TryAdd(property.Name, objectPropertyValue);
}

return shapedObject;
}
}

}
. ‫ لس بیایی آ را بشکنی و با ه بررسی کنی‬،‫ک زیادی در اینجا وجود دارد‬

: ‫بررسی کد‬

‫ نو این لرالر ی آرایاه‬.‫ وجود دارد‬Properties ‫ باه ناام‬public ‫در این کالس یاک لرالر ی‬ •

.‫ اسات و نو ورودی از هر نوعی که باشا درو آ ریخته میشاود‬PropertyInfo’s ‫ای از‬


‫ (در لروه‬. ‫ بای همه لرالر یهای کالس ورودی را ب ست آوری‬Constructor ‫بنابراین در‬
).‫ است‬Employee ‫ یا‬Caompany ‫ما این نو ورودی‬
public PropertyInfo[] Properties { get; set; }
public DataShaper()
{
Properties = typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.Instance);
}
‫ این اسااات‬، ‫ وظی اهی هر دو متا‬. ‫ داری‬ShapeData ‫در قسااامات بعا دو متا باا ناام‬ •

‫جویه و فیل هایی که‬ GetRequiredProperties ‫ ورودی را با اسات اد از مت‬string ‫که‬


. ‫در این ورودی هست را واکشی کنن‬
public IEnumerable<ExpandoObject> ShapeData(IEnumerable<T> entities,
string fieldsString)
{
var requiredProperties = GetRequiredProperties(fieldsString);

return FetchData(entities, requiredProperties);


}

public ExpandoObject ShapeData(T entity, string fieldsString)


{
var requiredProperties = GetRequiredProperties(fieldsString);

return FetchDataForEntity(entity, requiredProperties);


}
267
‫ ورودی را جویاه و فقط لرالر یهاای مورد نیااز را باه‬GetRequiredProperties ‫متا‬ •

. ‫کنترلر برمیگردان‬
private IEnumerable<PropertyInfo> GetRequiredProperties(string
fieldsString)
{
var requiredProperties = new List<PropertyInfo>();
if (!string.IsNullOrWhiteSpace(fieldsString))
{
var fields = fieldsString.Split(',',
StringSplitOptions.RemoveEmptyEntries);

foreach (var field in fields)


{
var property = Properties
.FirstOrDefault(pi => pi.Name.Equals(field.Trim(),
StringComparison.InvariantCultureIgnoreCase));

if (property == null)
continue;

requiredProperties.Add(property);
}
}
else
{
requiredProperties = Properties.ToList();
}

return requiredProperties;
}
، ‫ خالی نباشا‬fieldsString ‫ اگر‬.‫همانطور که میبینی چیو خاصای در این ک وجود ن ارد‬ •

‫ مطابقت دارن یا‬Entity ‫ و سااپس چک میکنی که آیا فیل ها با لرالر یهای‬Split ‫آ را‬
.‫خیر‬
. ‫ آنها را به لیست لرالر یهای مورد نیاز اضافه میکنی‬، ‫اگر مطابق باشن‬ •

.‫ همهی لرالر یها موردنیاز است‬، ‫ خالی باش‬fieldsString ‫اما اگر‬ •

‫ هساااتنا کاه مقاادیر را از این‬private ‫ متا هاای‬،FetchDataForEntity ‫ و‬FetchData •

. ‫ ج ا و برای ما آماد میکنن‬،‫لرالر یها‬

268
. ‫ انجام میده‬Entity ‫ این عملیات را برای یک‬FetchDataForEntity ‫مت‬ •

private ExpandoObject FetchDataForEntity(T entity,


IEnumerable<PropertyInfo> requiredProperties)
{
var shapedObject = new ExpandoObject();

foreach (var property in requiredProperties)


{
var objectPropertyValue = property.GetValue(entity);

shapedObject.TryAdd(property.Name, objectPropertyValue);
}

return shapedObject;
}
‫ را اجرا و‬Foreach ‫ یک حلقه‬requiredProperties ‫همانطور که میبینی با اسااات اد از‬ •

. ‫ مقادیر را استخراج کردی‬Reflection ‫سپس با است اد از‬


. ‫ اضافه میکنی‬ExpandoObject ‫ این مقادیر را به آبجکت‬، ‫در لایا‬ •

. ‫را لیااد سااااازی میکنا‬ IDictionary<string,object> ‫یاک‬ ExpandoObject ‫کالس‬ •

‫ اساات اد کنی و به این‬TryAdd ‫ از مت‬،‫بنابراین می وانی برای اضااافه کرد لرالر یها‬
. ‫ به آبجکت اضافه شون‬،‫صورت به طور خودکار لرالر یهای موردنیاز‬
‫ اسااات باا این ااوت کاه برای‬FetchDataForEntity ‫ شااابیاه‬، ‫ه‬ FetchData ‫متا‬ •

. ‫ عمل واکشی اطالعات لرالر ی را انجام میده‬،‫ها‬Entity ‫مجموعهای از‬


private IEnumerable<ExpandoObject> FetchData(IEnumerable<T> entities,
IEnumerable<PropertyInfo> requiredProperties)
{
var shapedData = new List<ExpandoObject>();

foreach (var entity in entities)


{
var shapedObject = FetchDataForEntity(entity, requiredProperties);

shapedData.Add(shapedObject);
}

return shapedData;
}
. ‫ رجیستر کنی‬ConfigureServices ‫ را در مت‬DataShaper ‫ اجاز دهی کالس‬،‫برای ادامه‬
269
services.AddScoped <IDataShaper<EmployeeDto>, DataShaper<EmployeeDto>>();

. ‫ اضافه نمایی‬Startup ‫هایی لایین را به کالس‬Namespace ‫فراموش نکنی که حتما‬


using Repository.DataShaping;
using Entities.DataTransferObjects;

. ‫ را اصالح کنی‬EmployeesController ‫ کنترلر‬Constructor ‫در لایا بای‬


private readonly IDataShaper<EmployeeDto> _dataShaper;

public EmployeesController(IRepositoryManager repository, ILoggerManager


logger, IMapper mapper , IDataShaper<EmployeeDto> dataShaper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
_dataShaper = dataShaper;
}
‫ را همااننا کا لاایین لییر‬GetEmployeesForCompanyAsync ‫حااال خروجی اکشااان متا‬
. ‫دهی‬
[HttpGet(Name = "GetEmployeeForCompany")]
public async Task<IActionResult> GetEmployeesForCompanyAsync(Guid
companyId, [FromQuery] EmployeeParameters employeeParameters)
{
var company = await _repository.Company.GetCompanyAsync(companyId,
trackChanges: false);

if (!employeeParameters.ValidAgeRange)
return BadRequest("Max age can't be less than min age.");

if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist
in the database.");

return NotFound();
}

var employeesFromDb = await


_repository.Employee.GetEmployeesAsync(companyId,
employeeParameters, trackChanges: false);

270
Response.Headers.Add("X-Pagination",
JsonConvert.SerializeObject(employeesFromDb.MetaData));

var employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);

return Ok(_dataShaper.ShapeData(employeesDto,
employeeParameters.Fields));

}
. ‫اکنو می وانی این مت را ست کنی‬
https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?pageNumber=1&pageSize=&3minAge=26&maxAge=35&fi
elds=name,age

.‫همه چیز عالی کار میکند‬

271
‫فصل یازدهم ‪API Versioning :‬‬

‫آنچه خواهید آموخت‪:‬‬


‫➢ ‪ API Versioning‬چیست؟‬
‫➢ پیاده سازی و تست ‪API Versioning‬‬
‫➢ منسوخ کردن ‪Version‬‬
‫‪API Versioning‬‬
‫با رشاا اللیکیشاان‪ ،‬نیازمن یهای لروه ه لییر میکن و همین باعث میشااود ‪API‬ها نیو‬
‫لییر کنن ‪.‬‬

‫لییرات ‪ API‬می وان شامل موارد لایین باش ‪.‬‬

‫لییر نام فیل ها‪ ،‬لرالر یها یا ‪Resource URI‬ها باش ‪.‬‬ ‫•‬

‫لییرات در ساختار باش ‪.‬‬ ‫•‬

‫لییرات ‪ Response Code‬یا ‪HTTP Verb‬ها باش ‪.‬‬ ‫•‬

‫طراحی مج د ‪ API‬باش ‪.‬‬ ‫•‬

‫لییر کن ‪ ،‬ک سامت کالینت به مشاکل برمیخورد و در نتیجه ‪ API‬با شاکسات روبرو‬ ‫‪API‬‬ ‫وقتی‬
‫میشود‪.‬‬

‫بهترین را برای جلوگیری از این شااکساات‪ ،‬اساات اد از ‪ API Versioning‬اساات و روشهای‬


‫مختل ی برای لیاد سازی آ وجود دارد‪.‬‬

‫هیچ راهنماایی وجود نا ارد کاه کا ام روش بهتر از بااقی روشهااسااات‪ ،‬بناابراین ماا روشهاای‬
‫مختل را معرفی میکنی و شما با وجه به نیاز ا می وانی هر ک ام را انتخاب نمایی ‪.‬‬

‫روش اول ‪:‬‬

‫‪ Microsoft.AspNetCore.Mvc.Versioning‬در پروژه ‪API‬‬ ‫نصب پکیج‬

‫لس از نصااب لکی باال‪ ،‬بای ساارویس ‪ Versioning‬را رجیسااتر کنی لس به یک مت ج ی در‬
‫کالس ‪ ServiceExtensions‬نیاز داری ‪.‬‬

‫ابت ا ‪ Namespace‬لایین را در کالس ‪ ServiceExtensions‬اضافه کنی ‪.‬‬


‫;‪using Microsoft.AspNetCore.Mvc‬‬

‫‪273‬‬
. ‫و سپس مت لایین را بنویسی‬
public static void ConfigureVersioning(this IServiceCollection services)
{
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.DefaultApiVersion = new ApiVersion(1, 0);
});
}

. ‫ از سه لرالر ی است اد میکن‬،Versioning ‫این اکستنشن برای لیکربن ی‬

. ‫ اضافه میکن‬Response ‫ را به ه ر‬API ‫ وره‬: ReportApiVersions •

‫ این‬، ‫ اگر کالینت وره را ارسال نکن‬: AssumeDefaultVersionWhenUnspecified •

. ‫ را مشخش میکن‬API ‫لرالر ی وره لی فرض‬


. ‫ این لرالر ی ع اد وره لی فرض را نظی میکن‬: DefaultApiVersion •

‫را در متا‬ API Versioning ‫ باایا‬،AddApiVersioning ‫خاب حااال باا اسااات ااد از متا‬
. ‫ اضافه کنی‬ConfigureService
services.ConfigureVersioning();

. ‫ حاال می وانی ادامه دهی‬، ‫ نصب و لیکربن ی ش‬API ‫خب‬

Versioning ‫تست‬
‫ این‬. ‫ ایجااد کنی‬CompaniesV2Controller ‫برای سااات این قاابلیات باایا کنترلر دیگری باا ناام‬
.‫ دارد‬Get ‫کنترلر فقط یک اکشنمت‬
using Contracts.IServices;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Controllers
{
[ApiVersion("2.0")]
[Route("api/companies")]

274
[ApiController]
public class CompaniesV2Controller : ControllerBase
{
private readonly IRepositoryManager _repository;

public CompaniesV2Controller(IRepositoryManager repository)


{
_repository = repository;
}

[HttpGet]
public async Task<IActionResult> GetCompaniesAsync()
{
var companies = await
_repository.Company.GetAllCompaniesAsync(trackChanges:
false);

return Ok(companies);
}
}

}
: ‫بررسی کد‬

.‫ است‬2.0 ‫[ مشخش میکن که این کنترلر وره‬ApiVersion("2.0")] ‫ا ربیوت‬ •

. ‫ برگردان ی‬Entity ‫ یک‬،‫ به کالینت‬DTO ‫ به جای برگردان‬،Get ‫در اکشنمت‬ •

. ‫ را به کنترلر اصلی اضافه نمایی‬Version ‫خب حاال قبل از اجرا بای‬


[ApiVersion("1.0")]
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase

1.0 ‫ را بر روی‬Versioning ‫ ماا‬، ‫ دیا یا‬AddApiVersioning ‫هماانطور کاه در اکساااتنشااان متا‬


. ‫نظی کردی‬
opt.DefaultApiVersion = new ApiVersion(1, 0);

‫بود مق ار لی‬ ‫ مشااخش نشاا‬Versioning ‫همچنین با ک لایین مشااخش میکنی که اگر‬


.‫فرض را در نظر بگیر‬
opt.AssumeDefaultVersionWhenUnspecified = true;

275
‫است اد خواه کرد‪.‬‬ ‫‪1.0‬‬ ‫بنابراین اگر کالینت وره را مشخش نکن ‪ API ،‬از نسخه‬
‫‪https://localhost:5001/api/companies‬‬

‫اسات‪،‬‬ ‫لرالر ی ‪ fullAddress‬در این صاویر به این معنی اسات که کنترلر اصالی ما فراخوانی شا‬
‫حتی اگر درخواستی وره ‪ API‬را مشخش نکرد باشی ‪.‬‬

‫خب حاال می وانی با اسات اد از یک ‪ Query String‬در ‪ ،URI‬نساخهی ‪ API‬را در ریکوئسات ارائه‬
‫کنی ‪.‬‬
‫‪https://localhost:5001/api/companies?api-version=2.0‬‬

‫‪276‬‬
‫‪Company Entity‬‬ ‫هماانطور کاه میبینیا ‪ ،‬باه جاای خروجی ‪ ،CompanyDto‬یاک لیساااتی از نو‬
‫است‪.‬‬ ‫داری ‪ .‬بنابراین مطملن میشوی که حتما وره ‪ 2.0‬ص ا زد ش‬

‫می وانی ه رهای ‪ Response‬را ه بررسی کنی ‪.‬‬ ‫برای مطملن ش‬

‫استفاده از ‪URL Versioning‬‬


‫در ساات باال برای مشااخش کرد وره ‪ ،‬از ‪ Query String‬اساات اد کردی ‪ .‬اما همین کار را‬
‫می وا با است اد از ‪ URL‬ه مشخش کرد‪.‬‬

‫برای این کار بای ا ربیوت ‪ Route‬باالی کنترلر را به صورت لایین لییر دهی ‪.‬‬
‫])"‪[ApiVersion("2.0‬‬
‫])"‪[Route("api/{v:apiversion}/companies‬‬
‫]‪[ApiController‬‬
‫‪public class CompaniesV2Controller : ControllerBase‬‬

‫حاال می وانی آ را ست کنی ‪.‬‬


‫‪https://localhost:5001/api/2.0/companies‬‬

‫الگوی ‪Query‬‬ ‫به این نکته وجه داشااته باشاای که برای ‪ CompaniesV2Controller‬نمی وا از‬
‫بای از ‪ Query String‬است اد کنی ‪.‬‬ ‫‪1.0‬‬ ‫‪ String‬است اد کرد اما برای وره‬

‫‪277‬‬
HTTP Header Versioning
‫ برای فعال کرد‬.‫ ارسااال کرد‬HTTP ‫ می وا وره را در ه ر‬، ‫ را لییر دهی‬URI ‫اگر نخواهی‬
. ‫ را لییر دهی‬ConfigureVersioning ‫این گوینه بای مت‬
public static void ConfigureVersioning(this IServiceCollection services)
{
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.ApiVersionReader = new HeaderApiVersionReader("api-
version");
});
}

. ‫ لایین را ه در این کالس اضافه کنی‬Namespace ‫فراموش نکنی که حتما‬


using Microsoft.AspNetCore.Mvc.Versioning;

. ‫لییر دهی‬ CompaniesV2Controller ‫ را در‬Route ‫خب حاال بای ا ربیوت‬


[ApiVersion("2.0")]
[Route("api/companies")]
[ApiController]
public class CompaniesV2Controller : ControllerBase

. ‫بیایی این لییرات را ست کنی‬


https://localhost:5001/api/companies

278
Version ‫منسوخ کردن‬
‫ بای از لرالر ی‬، ‫ منساود شاود اما نخواهی آ را به طور کامل حذف کنی‬2.0 ‫اگر بخواهی وره‬
. ‫ است اد کنی‬Deprecated
[ApiVersion("2.0", Deprecated = true)]
[Route("api/companies")]
[ApiController]
public class CompaniesV2Controller : ControllerBase

. ‫ را اینطور میبینی‬Header ‫خب اگر ریکوئست لایین را اجرا کنی نتیجه‬


https://localhost:5001/api/companies

!!‫نکته‬

‫اگر ورژنهای زیادی از یک کنترل داشتتته باشتتیم و برواهیم تعدادی از این کنترلرها را منستتوخ‬
.‫ میتوان از پیکربندی اختصاصی استفاده کرد‬،‫کنیم‬
public static void ConfigureVersioning(this IServiceCollection services)
{
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;

279
opt.DefaultApiVersion = new ApiVersion(1, 0);

opt.Conventions.Controller<CompaniesController>().HasApiVe
rsion(new ApiVersion(1, 0));

opt.Conventions.Controller<CompaniesV2Controller>().HasDepr
ecatedApiVersion(new ApiVersion(2, 0));
});
}

.‫ پایین را اضافه کنید‬Namespace ‫فراموش نکنید که‬


using CompanyEmployee.API.Controllers;

.‫] را از کنترلرها حذف کنیم‬ApiVersion[ ‫حاال باید اتربیوت‬

280
Rate Limiting ‫ و‬Cache : ‫فصل دوازدهم‬

:‫آنچه خواهید آموخت‬


‫ چیست؟‬Caching ➢
Cache ‫➢ معرفی انواع‬
Cache ‫➢ افزودن هدرهای‬

‫ چیست؟‬Expiration Model ➢
‫ چیست؟‬Validation Model ➢
Validation ‫ و‬Expiration ‫➢ پیکربندی هدرهای‬
‫ چیست؟‬Rate Limiting ➢
Rate-Limit ‫➢ پیادهسازی‬
‫‪ Caching‬چیست؟‬
‫به ذخیر داد ها در قسامتی از حافظه به نام ‪ Cache‬را‪ Caching ،‬میگوین ‪ .‬با ‪ Caching‬سارعت‬
‫دسترسی به داد ها زیاد ر از حالت عادی است‪.‬‬

‫‪ Cache‬یک کامپوننت ج اگانه اسات که ریکوئساتهای کالینت و ریساپانس ‪ API‬را میلذیرد و‬


‫باشن ‪ ،‬آ ها را ذخیر میکن ‪.‬‬ ‫ش‬ ‫در صور ی که قابل ک‬

‫‪ Caching‬می وان کی یت و لرفورمنس اللیکیشان را باال ر ببرد و ه ف اصالی آ این اسات که‬
‫در بسایاری از موارد‪ ،‬نیاز به ارساال ریکوئسات به سامت ‪ API‬و یا ارساال ‪ Response‬کامل ن اشاته‬
‫باشی ‪.‬‬

‫ع اد ریکوئسااتهای ارسااالی‪ Cache ،‬از مکانیوم انقضااا اساات اد میکن ‪ .‬این کار‬ ‫برای کاه‬
‫یاب ‪.‬‬ ‫باعث میشود رفت و برگشت درو شبکهای کاه‬

‫‪Response‬‬ ‫عالو بر این‪ Caching ،‬از مکانیوم اعتبارساانجی اساات اد میکن ا نیاز به ارسااال‬
‫کامل را از بین ببرد‪.‬‬

‫لس از ذخیر ریسپانس‪ ،‬اگر کالینت دوبار هما ریسپانس را درخواست کن ‪ ،‬بای ریسپانس از‬
‫حافظه ‪ Cache‬ارائه شود‪.‬‬

‫انواع ‪Cache‬‬
‫سه نوع ‪ Cache‬وجود دارد ‪:‬‬

‫‪ Client‬بر روی ‪( Client‬مرورگر) زنا گی میکنا ‪ .‬لس چو‬ ‫‪Cache‬‬ ‫‪: Client‬‬ ‫‪Cache‬‬ ‫•‬

‫مربوط به کالینت اسات‪ ،‬بای یک ‪ Private cache‬باشا ‪ .‬بنابراین هر کالینت که ‪ API‬ما‬


‫را مصرف میکن یک ‪ Private cache‬دارد‪.‬‬
‫‪Cache‬‬ ‫زن ا گی میکن ا و ی اک‬ ‫‪Server‬‬ ‫در‬ ‫‪Gateway Cache‬‬ ‫‪:‬‬ ‫‪Gateway Cache‬‬ ‫•‬

‫‪ Shared‬اسات‪ .‬این ‪ Cache‬به اشاتراک گذاشاته میشاود چو ‪Resource‬هایی که در آ‬


‫ذخیر میشون بای بر روی کالینتهای مختل به اشتراک گذاشته شون ‪.‬‬
‫‪ Proxy Cache : Proxy Cache‬ه یک ‪ Shared Cache‬اسات که نه در سارور و در نه‬ ‫•‬

‫در کالینت زن گی میکن ؛ بلکه مول زن گی او در شبکه است‪.‬‬

‫‪282‬‬
‫تفاوت بین ‪ Private cache‬و ‪: Shared Cache‬‬

‫در ‪ Private cache‬اگر لن کالینت برای اولین بار ریساپانس مشاابه درخواسات کنن ‪ ،‬در‬ ‫•‬

‫این صاورت هر ریساپانس از سامت ‪ API‬ارائه میشاود (و نه از‪ )Cache‬اما اگر آنها دوبار‬
‫هما ریساپانس را بخواهن ‪ ،‬این بار بای ریساپانس از ‪ Cache‬باشا (البته اگر انقضاای آ‬
‫باش )‪.‬‬ ‫مام نش‬
‫در مورد ‪ Shared Cache‬اینگونه نیساات‪ .‬در این نو ‪ ، Cache‬ابت ا ریسااپانس کالینت‬ ‫•‬

‫اول ذخیر میشااود و سااپس چهار کالینت دیگر‪ ،‬در صااورت درخواساات مشااابه بای‬
‫را دریافت کنن ‪.‬‬ ‫ریسپانس ‪ Cache‬ش‬

‫افزودن هدرهای ‪Cache‬‬


‫هساااات یاا‬ ‫‪Cacheable‬‬ ‫کرد برخی ‪Resource‬هااا‪ ،‬بااای ا با انی کااه آی اا‬ ‫‪Cache‬‬ ‫برای‬
‫خیر‪ Response Header.‬در انجام این کار به ما کمک میکن ‪ .‬به طور مثال‪:‬‬

‫‪ Cache-Control: Cache-Control: max-age = 180‬موردی است که بیشتر اوقات است اد می‬


‫شود و ب ین معنی است که ‪ Response‬بای به م ت ‪ 180‬ثانیه ‪ Cache‬شود‪.‬‬

‫از ‪Caching‬‬ ‫قبل از افوود ه رهای ‪ ،Cache‬بای ‪ Postman‬را باز کنی و نظیمات لشااتیبانی‬
‫را لییر دهی ‪.‬‬

‫‪.‬‬ ‫‪ ،Headers‬بای سربرگ ‪ Send no-cache‬را ‪ OFF‬کنی‬ ‫در سربرگ ‪ General‬در بخ‬

‫بیایی اکشن مت ‪ GetCompanyAsync‬را به م ت ‪ 60‬ثانیه ‪ Cache‬کنی ‪.‬‬


‫])"‪[HttpGet("{id}", Name = "CompanyById‬‬
‫])‪[ResponseCache(Duration = 60‬‬

‫‪283‬‬
public async Task<IActionResult> GetCompanyAsync(Guid id)
{
var company = await _repository.Company.GetCompanyAsync(id,
trackChanges: false);

if (company == null)
{
_logger.LogInfo($"Company with id: {id} doesn't exist in the
database.");

return NotFound();
}
else
{
var companyDto = _mapper.Map<CompanyDto>(company);

return Ok(companyDto);
}
}
. ‫خب حاال بیایی نتیجه را ست کنی‬
https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce2

‫ اما‬.‫اساات‬ ‫ ثانیه ایجاد ش ا‬60 duration ‫ و‬public cache ‫ با‬Cache-Control ‫میبینی که ه ر‬


‫ی از‬cache ‫ برای ذخیر سااازی ریسااپانس نیاز به‬.‫ این فقط یک ه ر اساات‬، ‫همانطور که گ تی‬
. ‫ داری‬cache-store ‫نو‬

284
cache-store ‫اضافه کردن‬
‫ اضافه کرد یک اکستنشن مت در‬، ‫ اولین کاری که بای انجام دهی‬،cache-store ‫برای افوود‬
.‫ است‬ServiceExtensions ‫کالس‬
public static void ConfigureResponseCaching(this IServiceCollection
services) => services.AddResponseCaching();
‫ رجیساتر میکنی و حاال بای این اکساتنشان مت را‬IoC container ‫ را در‬Caching ‫ما ریساپانس‬
. ‫ ص ا بونی‬ConfigureServices ‫در مت‬
services.ConfigureResponseCaching();

. ‫ اللیکیشن اضافه کنی‬Middleware ‫ به‬UseRouting ‫ را باالی‬Caching ‫ بای‬،‫عالو بر این‬


app.UseResponseCaching();
app.UseRouting();

Cache- ‫ این ریکوئسات ه ر‬. ‫ کرد و ریکوئسات لایین را ارساال کنی‬Start ‫حاال برنامه خود را‬
. ‫ را ایجاد میکن‬Control
https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce2

: ‫بار اول‬

: ‫بار دوم‬
285
‫قبل از اینکه ‪ 60‬ثانیه بگذرد‪ ،‬دوبار هما ریکوئسات را ارساال کنی و ساپس ه رها را بررسای‬
‫نمایی ‪.‬‬

‫اسات‪ .‬این ه ر ع اد ثانیههای‬ ‫همانطور که میبینی در ریکوئسات دوم ه ر ‪ Age‬اضاافه شا‬


‫شای در ‪ cache‬را نشاا میده و به این معنی اسات که ما ریساپانس دوم خود را‪،‬‬ ‫ذخیر شا‬
‫از ‪ cache-store‬گرفتی ‪.‬‬

‫مییاب ‪.‬؛ سپس بع‬ ‫اگر در عرض ‪ 60‬ثانیه‪ ،‬چن ین ریکوئست ارسال کنی ‪ ،‬لرالر ی ‪ Age‬افوای‬
‫‪Age‬‬ ‫از لایا دور انقضااا‪ ،‬ریسااپانس از ‪ API‬ارسااال و دوبار ‪ Cache‬خواه ش ا بنابراین ه ر‬
‫ایجاد نمیشود‪.‬‬

‫‪Cache Profiles‬‬ ‫عالو بر این‪ ،‬می وا برای اعمال قوانین یکسا در ‪Resource‬های مختل ‪ ،‬از‬
‫است اد کرد‪.‬‬

‫در صااویر باال لرالر یهای زیادی هساات که اگر همه این لرالر یها را در باالی اکشاان مت یا‬
‫کنترلر قرار دهی ‪ ،‬منجر به ناخوانایی ک خواه شاا ‪ .‬بنابراین برای واکشااای این لیکربن یها‪،‬‬
‫می وانی از ‪ CacheProfiles‬است اد کنی ‪.‬‬

‫برای انجام این لیکربن ی بای ‪ AddControllers‬را لییر دهی ‪.‬‬


‫{ >= ‪services.AddControllers(config‬‬

‫‪286‬‬
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
config.CacheProfiles.Add("120SecondsDuration", new CacheProfile
{
Duration = 120
});
}).AddNewtonsoftJson()
.AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();

‫ لرالر یهای دیگر را نیو اضاافه‬، ‫ اما شاما در اینجا می وانی‬، ‫ما فقط م ت زما را نظی کردی‬
. ‫کنی‬

. ‫ بگذاری‬CompaniesController ‫ را در باالی‬Profile ‫حاال بای این‬


[ResponseCache(CacheProfileName = "120SecondsDuration")]
‫ به جو مواردی که‬،‫ در مامی اکشاانهای درو کنترلر‬Cache Rule ‫الزم به ذکر اساات که این‬
‫) اعمال‬GetCompanyAsync ‫ اساات اد کرد ان (اکشاان مت‬ResponseCache ‫قبالً از لرالر ی‬
. ‫خواه ش‬

‫ بر روی ع د‬Age ‫ ب رساتی بای لرالر ی‬GetCompanyAsync ‫حاال اگر ریکوئسات به اکشانمت‬
. ‫ باش‬60
https://localhost:5001/api/companies/C9D4C053 -49B6-410C-BC78-2D54A9991870

287
‫ب رساتی بای لرالر ی ‪ Age‬بر روی ع د‬ ‫‪GetCompaniesAsync‬‬ ‫و اگر ‪ Request‬به اکشانمت‬
‫‪ 120‬باش ‪.‬‬
‫‪https://localhost:5001/api/companies‬‬

‫‪ Validation models‬بیشتر صوبت کنی ‪.‬‬ ‫‪ Expiration‬و‬ ‫بیایی در مورد‬

‫‪ Expiration Model‬چیست؟‬
‫‪ Expiration Model‬به سارور این امکا را میده که مشاخش کن ‪ ،‬ریساپانس منقضای شا‬
‫یا خیر‪.‬‬

‫بیایی ببینی که ‪ Expiration Model‬چگونه کار میکن ‪.‬‬

‫‪288‬‬
‫همانطور که میبینی کالینت ریکوئستی را برای دریافت ‪ companies‬ارسال میکن ‪.‬‬ ‫•‬

‫هیچ وره ‪ cache‬شا ای از این ریساپانس وجود ن ارد بنابراین ریکوئسات به ‪ API‬ارساال‬ ‫•‬

‫میشود‪.‬‬
‫ساااپس ‪ API‬این ریساااپانس را همرا با ه ر ‪ 600 Cache-Control‬ثانیه انقضاااا‪ ،‬برای‬ ‫•‬

‫کالینت ارسال و آ را در ‪ cache‬ذخیر میکن ‪.‬‬


‫و برای دساتیابی به آ ‪ ،‬از‬ ‫لس ا زمانی که ریساپانس ‪ Fresh‬باشا ‪ ،‬از ‪ Cache‬ارائه شا‬ ‫•‬

‫ه ر ‪ Cache-Control‬است اد میشود‪.‬‬
‫اگر بع از دو دقیقه ریکوئست مشابهی درخواست شود صویر زیر ا اق میافت ‪.‬‬ ‫•‬

‫همانطور که میببینی ‪ ،‬ریسااپانس ذخیر شاا ‪ ،‬با یک ه ر ‪ 120 Age‬ثانیه برگردان‬ ‫•‬

‫میشود‪.‬‬
‫اگر این یک ‪ Private Cache‬باشا ‪ ،‬لس ریساپانس در مرورگر ذخیر خواه شا بنابراین‬ ‫•‬

‫کالینت دیگر‪ ،‬ریسپانس را از ‪ API‬میگیرد‪.‬‬


‫‪ Shared‬بااشااا ‪ ،‬کالینات دیگر‪ ،‬قبال از دو دقیقاه زماا اضاااافاه‪،‬‬ ‫‪Cache‬‬ ‫اماا اگر این یاک‬ ‫•‬

‫هما ریسپانس را میگیرد‪.‬‬

‫‪289‬‬
‫همانطور که میبینی در ‪ Shared Cache‬ریساپانس از ‪ cache‬ارائه میشاود و دو دقیقه دیگر به‬
‫ه ر ‪ Age‬اضافه خواه ش ‪.‬‬

‫ما با نوو کار ‪ Expiration Model‬آشنا ش ی حاال بیایی ‪ Validation Model‬را بررسی کنی ‪.‬‬

‫‪ Validation Model‬چیست؟‬
‫‪ Validation Model‬بررس ای میکن که ریسااپانس درو ‪ ، Cache‬هنوز قابل اساات اد اساات یا‬
‫‪Shared Cached‬‬ ‫خیر؟ فرض کنی کاه یاک ریساااپاانس ‪ GetCompany‬باه ما ت ‪ 30‬دقیقاه در‬
‫گذاشااته شا اساات‪ .‬اگر کسای بع از لن دقیقه‪ ،‬آ شاارکت را آل یت کن ‪ ،‬کالینت بای ‪25‬‬
‫‪Cache‬‬ ‫دقیقه باقیمان ‪ ،‬ریساپانس اشاتبا دریافت کن ؛ چو هیچ اعتبارسانجی روی این داد‬
‫وجود ن ارد‪.‬‬

‫وصایه میکن‬ ‫‪HTTP‬‬ ‫برای جلوگیری از این ا اق‪ ،‬بای از اعتبارسانجی اسات اد کنی ‪ .‬اساتان ارد‬
‫در صورت امکا ‪ ،‬به صورت رکیبی از اعتبارسنجیهای ‪ LastModified‬و ‪ ETag‬است اد کنی ‪.‬‬

‫بیایی ببینی که اعتبارسنجی چگونه کار میکن ‪.‬‬

‫کالینت ریکوئستی را ارسال میکن ‪.‬‬ ‫•‬

‫این ریکوئست ‪ Cache‬نمیشود و به ‪ API‬ارسال خواه ش ‪.‬‬ ‫•‬

‫‪ API‬ریسپانسی را که شامل ‪ Etag‬و ‪ Last-Modified‬است برمیگردان ‪.‬‬ ‫•‬

‫و به کالینت ارسال میشود‪.‬‬ ‫این ریسپانس‪ Cache ،‬ش‬ ‫•‬

‫لس از دو دقیقه‪ ،‬کالینت هما ریکوئست را ارسال میکن ‪.‬‬


‫‪290‬‬
‫همانطور که میبینی ‪ ،Cache‬ریکوئسات را با ه رهای اضاافی ‪(If-None-Match‬که روی‬ ‫•‬

‫نظی‬ ‫‪Last-Modified‬‬ ‫‪( If‬کاه باه مقا ار‬ ‫‪Modified-From‬‬ ‫نظی شااا ) و‬ ‫‪Etag‬‬ ‫مقا ار‬
‫ش ) به سمت ‪ API‬برمیگردان ‪.‬‬
‫‪Response‬‬ ‫اگر این ‪ Request‬با اعتبارسنجیها بررسی شود‪ ،‬دیگر نیازی به ایجاد دوبار‬ ‫•‬

‫از سمت ‪ API‬نیست بنابراین ‪ 304 Not Modified status‬ارسال میکن ‪.‬‬
‫بع از آ ‪ Response ،‬به طور منظ از ‪ Cache‬ارائه میشود‪.‬‬ ‫•‬

‫البته اگر این بررسی انجام نشود‪ ،‬لس بای ‪ Response‬ج ی ایجاد شود‪.‬‬ ‫•‬

‫این صااویر ما را به این نتیجه میرسااان که در ‪ Cache Shared‬اگر ریسااپانس لییر‬ ‫•‬

‫نکرد باش ‪ ،‬این ‪ Response‬بای فقط یک بار ولی شود‪.‬‬

‫بیایی همه اینها را در یک مثال ببینی ‪.‬‬

‫پیادهسازی اعتبارسنجی‬
‫ابت ا بای در لروه اصاالی لکی ‪ Marvin.Cache.Headers‬را نصااب کنی ‪ .‬این لکی از ه رهای‬
‫‪Expiration‬‬ ‫‪ Etag ،Expires ،Cache-Control‬و ‪ Last-Modified‬لشاااتیبانی و ‪ Validation‬و‬
‫‪ model‬را لیاد سازی میکن ‪.‬‬
‫‪Install-Package Marvin.Cache.Headers -Version 5.0.1 -ProjectName‬‬
‫‪CompanyEmployee.API‬‬

‫‪291‬‬
. ‫ اضافه کنی‬ServiceExtensions ‫در مرحله بع بای اکستنشن مت لایین را در کالس‬
public static void ConfigureHttpCacheHeaders(this IServiceCollection
services) => services.AddHttpCacheHeaders();

.‫ رجیستر شود‬ConfigureServices ‫خب حاال بای این سرویس در مت‬


services.ConfigureHttpCacheHeaders();

. ‫ اضافه نمایی‬Configure ‫ لایین را در مت‬Middleware ‫و سپس‬


app.UseResponseCaching();
app.UseHttpCacheHeaders();

.‫حاال نوبت ست است‬


https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce3

60 ‫فرض روی‬ ‫ انقضاااای لی‬. ‫ ما مام ه رهای موردنیاز را ایجاد کردی‬، ‫هماانطور که میبینی‬
.‫است‬ ‫ثانیه نظی ش‬

. ‫ میگیری‬Age ‫ یک ه ر‬، ‫ را یک بار دیگر ارسال کنی‬Request ‫اگر این‬

292
Validation‫ و‬Expiration ‫پیکربندی هدرهای‬
‫االاایاایاارا اای در ماات ا‬ ‫بااای ا‬ Validation‫ و‬Expiration ‫باارای لاایااکاارباان ا ی ه ا رهااای‬
. ‫ دهی‬ConfigureHttpCacheHeaders

. ‫ اضافه کنی‬ServiceExtensions ‫ لایین را به کالس‬Namespace ‫ابت ا‬


using Marvin.Cache.Headers;

. ‫ را لییر دهی‬ConfigureHttpCacheHeaders ‫و بع مت‬


public static void ConfigureHttpCacheHeaders(this IServiceCollection
services) =>
services.AddHttpCacheHeaders(
(expirationOpt) =>
{
expirationOpt.MaxAge = 65;
expirationOpt.CacheLocation = CacheLocation.Private;
},
(validationOpt) =>
{
validationOpt.MustRevalidate = true;
});

. ‫ ثانیه ایجاد میکن‬65 ‫ با طول عمر‬Private cache ‫این لیکربن ی یک‬

. ‫ را ارسال کنی‬Requset ‫خب حاال دوبار‬


https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce3

293
‫ ما‬API ‫ اسات لس‬Private cache ‫ از آنجا که این یک‬.‫اسات‬ ‫میبینی که لییرات اعمال شا‬
. ‫ نمیکن‬Cache ‫آ را‬

‫و‬ HttpCacheExpiration ‫مااا مایاوانایا ایان لایاکاربان ا ی را بااا اساااتا اااد از ااربایاوتهااای‬
. ‫ روی اکشن مت یا کنترلر بگذاری‬HttpCacheValidation
[HttpGet("{id}", Name = "CompanyById")]
[HttpCacheExpiration(CacheLocation = CacheLocation.Public, MaxAge = 60)]
[HttpCacheValidation(MustRevalidate = false)]
public async Task<IActionResult> GetCompanyAsync(Guid id)

. ‫ لایین را ه در این کنترلر اضافه کنی‬Namespace ‫البته‬


using Marvin.Cache.Headers;

‫ و‬HttpCacheExpiration ‫ بایا از ا ربیوتهای‬،Resource ‫برای اسااات ااد از لیکربنا ی ساااطح‬


. ‫ است اد کنی‬HttpCacheValidation

. ‫ را میبینی‬Global ‫ لایین را ارسال کنی مقادیر‬Request ‫حاال اگر‬


https://localhost:5001/api/companies

294
‫و اگر ‪ Request‬لایین را ب رستی نتیجه مت اوت خواه بود‪.‬‬
‫‪https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce3‬‬

‫‪ Rate Limiting‬چیست؟‬
‫‪ ،Rate Limiting‬رافیک ورودی وب سااایت را م یریت میکن ‪ .‬این قابلیت به ما امکا میده‬
‫ا از ‪ API‬در برابر ع اد ریکوئستهای زیاد‪ ،‬که باعث از بین رفتن لرفورمنس میشون موافظت‬
‫کنی ‪.‬‬

‫به عنوان مثال‪:‬‬

‫ما می وانی مشاخش کنی که هر کالینت در هر سااعت فقط ‪ 100‬ریکوئسات به ‪ API‬داشاته‬


‫باشن ‪ .‬یا اینکه هر کالینت را به ح اکثر ‪ 1000‬ریکوئست در روز مو ود کنی ‪.‬‬

‫ریکوئسااتهای مجاز با ‪ XRate-Limit‬شاارو میشااون ‪ .‬اطالعات این ریکوئسااتها را بای در‬


‫ه رهای ریسپانس بررسی کنی ‪.‬‬

‫‪295‬‬
‫ه ر ریکوئستهای مجاز شامل اطالعات زیر هستن ‪:‬‬

‫‪ : X-Rate-Limit-Limit‬دور ‪ Rate-Limit‬را مشخش میکن ‪.‬‬ ‫•‬

‫را مشخش میکن ‪.‬‬ ‫‪ : X-Rate-Limit-Remaining‬ع اد ریکوئستهای باقیمان‬ ‫•‬

‫‪ : X-Rate-Limit-Reset‬اطالعات اریخ و زما در مورد مو ودیت ریکوئست است‪.‬‬ ‫•‬

‫میشااود و ه ر‬ ‫از ح مجاز شااود‪ 429 status code ،‬برگردان‬ ‫اگر ع اد درخواسااتها بی‬
‫‪ Retry-After‬به ه رهای ریسپانس اضافه خواه ش ‪.‬‬

‫پیادهسازی ‪Rate-Limit‬‬
‫برای شرو ‪ ،‬بای لکی ‪ AspNetCoreRateLimit‬را در لروه اصلی نصب کنی ‪.‬‬
‫‪Install-Package AspNetCoreRateLimit -Version 3.2.2 -ProjectName‬‬
‫‪CompanyEmployee.API‬‬

‫حاال بای این ساارویس را رجیسااتر کنی ‪ .‬اما از آنجاییکه این لکی برای ذخیر شاامارن ها و‬
‫‪Rule‬هاای خود از ‪ MemoryCache‬اسااات ااد میکنا لس باایا ‪ MemoryCache‬را نیو در متا‬
‫‪ Configuration‬رجیستر کنی ‪.‬‬
‫;)(‪services.AddMemoryCache‬‬

‫خب حاال بای یک اکساتنشان مت دیگر‪ ،‬برای اضاافه کرد ‪ Rate-Limit‬داشاته باشای ‪ .‬بنابراین‬
‫اکستنشن مت لایین را در کالس ‪ ServiceExtensions‬اضافه کنی ‪.‬‬

‫‪Namespace‬های موردنیاز ‪:‬‬


‫;‪using AspNetCoreRateLimit‬‬
‫;‪using System.Collections.Generic‬‬
‫کدهای اکستنشن متد ‪: ConfigureRateLimitingOptions‬‬
‫‪296‬‬
public static void ConfigureRateLimitingOptions(this IServiceCollection
services)
{
var rateLimitRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit= 3,
Period = "5m"
}
};
services.Configure<IpRateLimitOptions>(opt =>
{
opt.GeneralRules = rateLimitRules;
});
services.AddSingleton<IRateLimitCounterStore,
MemoryCacheRateLimitCounterStore>();

services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();

services.AddSingleton<IRateLimitConfiguration,
RateLimitConfiguration>();
}

: ‫بررسی کد‬

5 ‫ ایجاد کردی که ساااه ریکوئسااات را در یک دور‬RateLimitRule ‫ما در این ک یک‬ •

. ‫ها مجاز میکن‬Endpoint ‫دقیقه ای برای مام‬


. ‫ را لیکربن ی کردی‬IpRateLimitOptions ،‫ باال‬Rule ‫سپس برای اضافه کرد‬ •

IRateLimitConfiguration ‫ و‬IIpPolicyStore ، IRateLimitCounterStore ‫در لاایاا باایا‬ •

. ‫ رجیستر کنی‬Singleton ‫را به صورت‬

.‫ است‬ConfigureServices ‫حاال نوبت است اد کرد این اکستنشن مت در مت‬

297
services.AddMemoryCache();
services.ConfigureRateLimitingOptions();
services.AddHttpContextAccessor();

. ‫ اضافه کنی‬Configure ‫ لایین را در مت‬Middleware ‫البته بای‬


app.UseIpRateLimiting();
app.UseRouting();

. ‫ قرار دهی‬Startup ‫ لایین را ه در کالس‬Namespace ‫فراموش نکنی که‬


using AspNetCoreRateLimit;

.‫خب حاال نوبت ست است‬


https://localhost:5001/api/companies/3d490a70-94ce-4d15-9494-5248280c2ce3

‫ اگر در م ت زما‬.‫اسات‬ ‫ دو درخواسات باقی مان‬،‫ دقیقه‬5 ‫همانطور که میبینی در م ت زما‬


.‫ سه درخواست اضافی ارسال کنی ریسپانس مت او ی خواهی گرفت‬،‫لن دقیقه‬

298
‫‪ 429 Too‬را همرا باا اه ر ‪ Retry-After‬دریاافات کردی ‪.‬‬ ‫‪Many Requests‬‬ ‫هماانطور کاه میبینیا‬
‫اگر ‪ body‬را بررسی کنی بای نتیجه لایین را ببینی ‪.‬‬

‫‪299‬‬
Identity‫ و‬JWT : ‫فصل سیزدهم‬

:‫آنچه خواهید آموخت‬


‫ چیست؟‬ASP.NET Identity ➢
‫ چیست؟‬Authorization ‫ و‬Authentication ➢
‫ چیست؟‬JWT ➢
‫‪ JWT‬و‪Identity‬‬
‫مهمی از هر اللیکیشان اسات و به لروساه أیی هویت کاربرا یک‬ ‫‪ User Authentication‬بخ‬
‫اللیکیشن‪ ،‬اشار دارد‪.‬‬

‫اگر به رون کار آشانا نباشای ‪ ،‬لیاد ساازی این ویژگی می وان کار ساختی باشا و زما زیادی از‬
‫شما بگیرد‪.‬‬

‫‪ ASP.NET‬را باا‬ ‫‪Core‬‬ ‫بناابراین در این بخ ‪ ،‬میخواهی ‪ Authentication‬و ‪ Authorization‬در‬


‫است اد از ‪ Identity‬و )‪ JWT (Json Web Token‬بیاموزی ‪.‬‬

‫ما میخواهی گام به گام نوو ی ادغام ‪ Identity‬در لروه موجود و ساااپس نوو لیاد ساااازی‬
‫وضیح دهی ‪.‬‬ ‫‪Authorization‬‬ ‫‪ JWT‬را برای عملیات ‪ Authentication‬و‬

‫‪ ASP.NET Identity‬چیست؟‬
‫این روزها امنیت برنامههای وت وب‪ ،‬یکی از داف رین موضااوعات دنیای وب اساات‪ .‬هر ه ته‬
‫و حملههای سااایبری به سااایتهای مختل به گوش میرس ا ‪ .‬شااای‬ ‫خبرهایی از هک ش ا‬
‫به نظر برساا ‪ .‬اما نگرا نباشااای ‪ ،‬با دانساااتن برخی‬ ‫این جمالت کمی ناامی کنن‬ ‫شااانی‬
‫‪.‬‬ ‫موضوعات می وانی از بسیاری از این حمالت جلوگیری کنی‬

‫در این فصل میخواهی به نوو ی ح اظت از اللیکیشن نگاهی بین ازی ‪.‬‬

‫‪ ،ASP.NET‬قاابلیات ایجااد برنااماههاای دایناامیاک اسااات‪ .‬این‬ ‫‪Core‬‬ ‫یکی از مهمترین ویژگیهاای‬
‫بخ هایی را به کاربر میده که اجاز دساترسای داشاته باشا ‪ .‬با این‬ ‫قابلیت‪ ،‬نها امکا دی‬
‫حساب اللیکیشن ما می وان برای کاربرا مختل س ارشی شود‪.‬‬

‫بسایاری از اللیکیشانها‪ ،‬م هومی به نام حسااب کاربری دارن که با آ می وانی وارد نرمافوار‬
‫شاوی و جربه کاربری‪ ،‬مت اوت داشاته باشای ‪ .‬با داشاتن قابلیت حسااب کاربری در اللیکیشان‪،‬‬
‫امکانات مت او ی ارائه دهی ‪.‬‬ ‫می وانی بسته به شخش الگین ش‬

‫‪ ASP.NET Core Identity‬یک ‪ Membership system‬در اللیکیشاانهای وب اساات که قابلیت‬


‫عضااویت‪ ،‬ورود به س ایساات و داد های کاربر را اضااافه میکن ‪ Identity .‬می وان با اساات اد از‬

‫‪301‬‬
‫‪ SQL‬لیکربنا ی شاااود اا ناام کااربر‪ ،‬کلماه عبور و اطالعاات لروفاایال را ذخیر‬ ‫‪Server‬‬ ‫دیتاابیس‬
‫نمای ‪.‬‬

‫بیایید کمی بیشتر به این موضوع بپردازیم و این قابلیت فو العاده را به این اپلیکیشن‬
‫اضافه نماییم‪.‬‬

‫‪ Authentication‬و ‪ Authorization‬چیست؟‬
‫زمانیکه میخواهی یک کاربر را در اللیکیشن ثبت نمایی ‪ ،‬بای به دو جنبه مهم وجه کنی ‪:‬‬

‫‪ : Authentication‬فرآیند ایجاد کاربر برای ورود به برنامه است‪.‬‬ ‫•‬

‫‪ : Authorization‬میزان دستترستی کاربران و کنترل میزان دستترستی آنها به‬ ‫•‬

‫مینماید‪.‬‬ ‫اپلیکیشن را مشر‬

‫و ‪Authorization‬‬ ‫به عبارت دیگر‪ Authentication ،‬مشخش میکن چه کسی وارد سیست ش‬
‫‪.‬‬ ‫میگوی ‪ ،‬به چه چیوهایی بای دسترسی داشته باش‬

‫باشا ‪.‬‬ ‫سااد رین حالت برای ‪ Authorization‬این اسات که ح اقل‪ ،‬کاربر بای ‪ Authenticate‬شا‬
‫این کار وسااط اضااافه کرد ا ربیوت ]‪ [Authorize‬به باالی اکشاانمت ها یا ‪Controller‬ها انجام‬
‫میشود‪.‬‬

‫نکته!!‬

‫اتربیوت ]‪ [Authorize‬را میتوان در هر جای برنامه به کار برد‪ ،‬اما استتفاده از آن در باالیکنترلرها و‬
‫اکشنمتدها به این دلیل است که کنترل کنیم چه کاربری به چه اکشنی دسترسی دارد‪.‬‬

‫بیایید با هم ‪ Identity‬را به این اپلیکیشن اضافه نماییم‪.‬‬

‫‪Entities‬‬ ‫را در لروه‬ ‫‪Microsoft.AspNetCore.Identity.EntityFrameworkCore‬‬ ‫لکای‬ ‫•‬

‫اضافه کنی ‪.‬‬


‫‪Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore -Version‬‬
‫‪5.0.2 -ProjectName Entities‬‬

‫‪302‬‬
. ‫ ایجاد نمایی‬User ‫ کالسی با نام‬Entities / Models ‫ در فول ر‬،‫لس از نصب‬ •

using Microsoft.AspNetCore.Identity;

namespace Entities.Models
{
public class User : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

‫ارث‬ ‫ ارائه شاا‬ASP.NET Core Identity ‫ که وسااط‬،IdentityUser ‫این کالس بای از کالس‬
. ‫بری کن‬

‫ به همین جهت بع‬. ‫ اسات اد میکن‬User Account ‫ برای ذخیر‬EF Core ‫ از‬Identity •

. ‫ را ه کمی لییر دهی‬DbContext ‫ بای‬،User ‫از اضافه نمود‬


‫ یااک کاالس بااا نااام‬،CompanyEmployeeDBContext ‫قاباال از الاایاایاار کاالس‬
. ‫ ایجاد کنی‬Entities/Configuration ‫ در مسیر‬IdentityUserLoginConfiquration
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Entities.Configuration
{

303
public class IdentityUserLoginConfiquration :
IEntityTypeConfiguration<IdentityUserLogin<int>>
{
public void Configure(EntityTypeBuilder<IdentityUserLogin<int>>
builder)
{
builder.HasNoKey();
}
}
}

: CompanyEmployeeDBContext ‫کالس‬
using Entities.Configuration;
using Entities.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Entities
{
public class CompanyEmployeeDbContext : IdentityDbContext<User>

public CompanyEmployeeDbContext(DbContextOptions options):


base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.ApplyConfiguration(new CompanyConfiguration());
modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
modelBuilder.ApplyConfiguration(new
IdentityUserLoginConfiquration());
base.OnModelCreating(modelBuilder);
}

public DbSet<Company> Companies { get; set; }

304
public DbSet<Employee> Employees { get; set; }

‫ بای به‬CompanyEmployeeDbContext ، ‫ لشاتیبانی کن‬Identity‫ بتوان از‬EF Core ‫برای اینکه‬


‫ کالسی است که‬TUser . ‫ ارثبری کن‬IdentityDbContext<TUser> ‫ از کالس‬DbContext ‫جای‬
. ‫ ارثبری کرد باش‬IdentityUser ‫بای از‬

‫ شااما را با‬User Entity ‫های ضااروری اساات که‬DbSet<T> ‫ شااامل‬IdentityDbContext ‫کالس‬


. ‫ ذخیر میکن‬EF Core ‫است اد از‬

. ‫ خود را لیکربن ی کنی‬Identity ‫حاال بای‬ •

. ‫ابت ا لکی لایین را در لروه اصلی نصب کنی‬


Install-Package Microsoft.AspNetCore.Authentication.Cookies -Version
2.2.0 -ProjectName CompanyEmployee.API

. ‫ اضافه کنی‬ServiceExtensions ‫ها لایین را در کالس‬Namespace ‫سپس‬


using Entities.Models;
using Microsoft.AspNetCore.Identity;

. ‫ اضافه نمایی‬ServiceExtensions ‫خب حاال یک اکستنشن مت ج ی در کالس‬


public static void ConfigureIdentity(this IServiceCollection services)
{
var builder = services.AddIdentityCore<User>(o =>
{
o.Password.RequireDigit = true;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
305
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 10;
o.User.RequireUniqueEmail = true;
});

builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole),


builder.Services);
builder.AddEntityFrameworkStores<CompanyEmployeeDbContext>()
.AddDefaultTokenProviders();
}

:‫بررسی کد‬

User ‫ اضافه و آ را برای نو‬AddIdentityCore ‫ را با است اد از مت‬Identity ‫در این ک‬ •

. ‫لیکربن ی کردی‬
AddIdentityCore ‫لیکربنا ی را در متا‬ ‫هماانطور کاه میبینیا لاارامترهاای مختل‬ •

. ‫است اد کردی‬
token ‫ را همرا باا‬EntityFrameworkStores ‫ ایجااد و‬IdentityBuilder ‫در لاایاا یاک‬ •

. ‫فرض اضافه کردی‬ ‫ لی‬provider

. ‫ رجیستر کنی‬ConfigureServices ‫خب حاال این مت را بای در مت‬


services.AddAuthentication();
services.ConfigureIdentity();

. ‫ اضافه کنی‬Configure ‫ لایین را در مت‬Middleware ‫حاال دو‬


app.UseAuthentication();
app.UseAuthorization();

‫ها‬Role ‫ایجاد جداول و اضافه کردن‬


. ‫ما مام چیوهایی که الزم بود را آماد کردی حاال بای به سراف اعمال لییرات دیتابیس بروی‬

.‫ است‬Migration ‫مام کاری که بای انجام دهی ایجاد و اعمال‬

306
Add-Migration CreatingIdentityTables -Project CompanyEmployee.API

. ‫حاال دیتابیس را آل یت کنی‬


Update-Database

. ‫را ببینی‬ ‫بیایی با ه نمای دیتابیس و ج اول اضافه ش‬

‫ را همرا با سااایر‬LastName ‫ و‬FirstName ‫ سااتو های‬، ‫ را باز کنی‬AspNetUsers ‫اگر ج ول‬


. ‫ستو ها میبینی‬

‫ در‬،‫ برای انجااام این کااار‬. ‫وارد کنی‬ Role ‫ چن ا‬،AspNetRoles ‫حاااال بیااایی ا در جا ول‬
. ‫ ایجاد کنی‬RoleConfiguration ‫ یک کالس با نام‬Entities / Configuration‫فول ر‬
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

307
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Entities.Configuration
{
public class RoleConfiguration :
IEntityTypeConfiguration<IdentityRole>
{
public void Configure(EntityTypeBuilder<IdentityRole> builder)
{
builder.HasData(
new IdentityRole
{
Name = "Manager",
NormalizedName = "MANAGER"
},
new IdentityRole
{
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
}
);
}

}
}
‫ اضااافه‬CompanyEmployeeDbContext ‫ کالس‬OnModelCreating ‫حاال این کالس را در مت‬
. ‫کنی‬
using Entities.Configuration;
using Entities.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Entities
{
public class CompanyEmployeeDbContext : IdentityDbContext<User>

public CompanyEmployeeDbContext(DbContextOptions options):


base(options)
{
}

308
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new CompanyConfiguration());
modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
modelBuilder.ApplyConfiguration(new
IdentityUserLoginConfiquration());
modelBuilder.ApplyConfiguration(new RoleConfiguration());

base.OnModelCreating(modelBuilder);
}

public DbSet<Company> Companies { get; set; }


public DbSet<Employee> Employees { get; set; }

}
. ‫ لایین را اجرا و دیتابیس را آل یت کنی‬Migration ‫سپس‬
Add-Migration AddedRolesToDb -Project CompanyEmployee.API

Update-Database

. ‫ ج ی را بینی‬Role ‫ را بررسی کنی بای این دو‬AspNetRoles ‫اگر ج ول‬

309
User ‫ایجاد‬
. ‫ ایجاد کنی‬AuthenticationController ‫ ابت ا یک کنترلر ج ی با نام‬،User ‫برای ایجاد‬
using AutoMapper;
using Contracts.IServices;
using Entities.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace CompanyEmployee.API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
public AuthenticationController(ILoggerManager logger, IMapper
mapper, UserManager<User> userManager)
{
_logger = logger;
_mapper = mapper;
_userManager = userManager;
}
}

: ‫بررسی کد‬
310
.‫ مام ک های باال آشنا است‬،UserManager <TUser< ‫به جو بخ‬ •

. ‫ ارائه ش‬Identity ‫ سرویسی برای م یریت کاربرا است که وسط‬UserManager •

‫ماام نیاازمنا یهاای ماا را‬ UserManager ‫در اینجاا نیاازی باه ریپاازیتوری نا اری چو‬ •

. ‫فراه میکن‬

.‫ است‬DataTransferObjects ‫ در فول ر‬UserForRegistrationDto ‫حاال نوبت ایجاد کالس‬


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class UserForRegistrationDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
[Required(ErrorMessage = "Username is required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public ICollection<string> Roles { get; set; }
}

.. ‫ اضافه کنی‬MappingProfile ‫در کالس‬ Mapping Rule ‫سپس یک‬


CreateMap<UserForRegistrationDto, User>();

. ‫ را ایجاد کنی‬RegisterUser ‫در لایا بای اکشنمت‬


using AutoMapper;
using CompanyEmployee.API.Infrastructure.ActionFilters;
using Contracts.IServices;
using Entities.DataTransferObjects;
311
using Entities.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
public AuthenticationController(ILoggerManager logger, IMapper
mapper, UserManager<User> userManager)
{
_logger = logger;
_mapper = mapper;
_userManager = userManager;
}

[HttpPost]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult> RegisterUserAsync([FromBody]
UserForRegistrationDto userForRegistration)
{
var user = _mapper.Map<User>(userForRegistration);
var result = await _userManager.CreateAsync(user,
userForRegistration.Password);

if (!result.Succeeded)
{
foreach (var error in result.Errors)
{

312
‫‪ModelState.TryAddModelError(error.Code,‬‬
‫;)‪error.Description‬‬
‫}‬

‫;)‪return BadRequest(ModelState‬‬
‫}‬

‫‪await _userManager.AddToRolesAsync(user,‬‬
‫;)‪userForRegistration.Roles‬‬

‫;)‪return StatusCode(201‬‬
‫}‬
‫}‬
‫}‬

‫بررسی کد ‪:‬‬

‫ابت ا برای اعتبارسنجی م ل‪ ،‬یک اکشن فیلتر را باالی اکشنمت خود قرار دادی ‪.‬‬ ‫•‬

‫سپس م ل ورودی را به نو ‪ User‬مپ کردی ‪.‬‬ ‫•‬

‫حاال برای ایجاد کاربر در دیتابیس‪ ،‬مت ‪ CreateAsync‬را ص ا میزنی ‪.‬‬ ‫•‬

‫اگر عمل ایجاد کاربر موفقیت آمیو باشا ‪ ،‬کاربر در دیتابیس ذخیر میشاود‪ .‬در غیر این‬ ‫•‬

‫صورت لیلام خطا را برمیگردان ‪.‬‬


‫شود بای آنها را به ‪ ModelState‬اضافه کنی ‪.‬‬ ‫اگر لیلام خطا برگردان‬ ‫•‬

‫در لایا اگر کاربری ایجاد شاااود بای آ را به ‪Role‬های خود متصااال کنی و ساااپس‬ ‫•‬

‫‪ 201 Created‬را برگردانی ‪.‬‬

‫نکته!!‬

‫در صتتورت تمایل‪ ،‬قبل از صتتدا زدن ‪ AddToRoleAsync‬یا ‪ ،AddToRolesAsync‬میتوانید ‪Role‬های‬


‫‪ RoleManager‬را در کنترلر‬ ‫تد <‪<TRole‬‬
‫این کتاربتای‬ ‫موجود در دیتتابیس را بررستتی کنیتد‪ .‬البتته برای‬
‫‪ Inject‬و از متد ‪ RoleExistsAsync‬استفاده کنید‪.‬‬

‫حاال می وانی این را ست کنی ‪.‬‬

‫‪313‬‬
‫ مت‬،ServiceExtensions ‫ (کالس‬30 ‫ به‬3 ‫ را از‬Rate Limit ‫ بهتر اسااات مق ار‬،‫قبل از سااات‬
.‫نشود‬ ‫دهی ا ست لیچی‬ ‫) افوای‬ConfigureRateLimitingOptions

. ‫ابت ا یک درخواست معتبر ارسال میکنی‬


https://localhost:5001/api/authentication
body

{
"firstname":"Zahra",
"lastname":"Bayat",
"username":"ZahraBayat",
"password":"P@ssw0rd12345",
"email":"[email protected]",
"phonenumber":"0123456789",
"roles":[
"Manager"
]
}

.‫است‬ ‫ اضافه ش‬Role ‫ این یعنی کاربر ایجاد و به‬. ‫ گرفتی‬201 ‫همانطور که میبینی‬

. ‫ را ست کنی‬API ‫خب حاال بیایی با م لهای نامعتبر این‬

: ‫ خالی باشد‬UserName ‫اگر‬


314
‫اگر ‪ Password‬خالی باشد ‪:‬‬

‫‪315‬‬
‫در پایان اگر کاربری با همان نام کاربری و ایمیل ایجاد کنیم‪.‬‬

‫عالی شد همه چیز طبق برنامه کار میکند‪ .‬حاال میتوانیم به سراغ ‪ JWT‬برویم‪.‬‬

‫فرآیند ‪Login‬‬
‫‪Login‬‬ ‫و ‪ Authorization‬بیایی نگاهی اجمالی به فرآین‬ ‫‪Authentication‬‬ ‫قبل از لیاد سازی‬
‫داشته باشی ‪.‬‬

‫اللیکیشان یک فرم ورود به سایسات دارد که در آ ‪ ،‬کاربر نام کاربری و رمو عبور خود را وارد و‬
‫دکماه ورود را میزن ‪ .‬لس از زد دکماه ورود‪ ،‬کالینات (به عنوان مثال ‪ :‬مرورگر وب) داد های‬
‫کاربر را به ‪ API‬سرور میفرست ‪.‬‬

‫‪316‬‬
‫وقتی سرور اعتبار کاربر را بررسی و أیی کرد‪ ،‬بای یک ‪ JWT Token‬برای کالینت ارسال کن ‪.‬‬

‫‪ JWT‬چیست؟‬
‫(مثل نام‬ ‫‪ JWT‬یک ش ای ‪ JavaScript‬اساات که حاوی برخی از ا ربیوتهای کاربر الگین ش ا‬
‫کاربری‪Role ،‬های کاربر یا برخی اطالعات دیگر) میباشا ‪ .‬این شای به صاورت ایمن‪ ،‬داد ها را‬
‫بین دو طرف منتقل میکن ‪.‬‬

‫شکل کلی ‪ JWT‬به صورت لایین است‪.‬‬

‫که هر قسمت با رنگ مت اوت نشا داد‬ ‫همانطور که میبینی ‪ JWT‬از سه قسمت شکیل ش‬
‫است‪.‬‬ ‫ش‬

‫با فرمت‬ ‫‪ : Header‬اولین قسامت ‪ ،JWT‬ه ر اسات که یک شای ‪ JSON‬رموگذاری شا‬ ‫•‬

‫‪ Base64‬میباش ‪ .‬این قسمت اطالعا ی مانن نو ‪ token‬و نام الگوریت را در خود دارد‪.‬‬
‫{‬
‫‪"alg": "HS256",‬‬
‫"‪"typ": "JWT‬‬
‫}‬

‫‪ :Payload‬بع از ه ر‪ ،‬ما یک ‪ Payload‬داری که این ه یک شا ای ‪ JSON‬رموگذاری‬ ‫•‬

‫با فرمت ‪ Base64‬میباشا ا ‪ Payload .‬حاوی ا ربیوتهایی در مورد کاربر الگین‬ ‫شا ا‬
‫است‪ .‬به عنوان مثال ‪:‬‬ ‫ش‬

‫‪317‬‬
‫می وان حاوی ‪ Id‬کاربر‪ Subject ،‬کاربر و اطالعا ی در مورد اینکه آیا کاربر ‪ Admin‬اسات‬
‫یا خیر؟ باش ‪.‬‬

‫‪Base64‬‬ ‫نیساات و می وان با هر‬ ‫وجه داشااته باشاای که ‪ ،JWT‬رموگذاری شاا‬


‫‪ decoder‬رموگشای شود‪ .‬بنابراین هرگو اطالعات حساس را در ‪ Payload‬قرار ن هی ‪.‬‬
‫{‬
‫‪"sub": "1234567890",‬‬
‫‪"name": "Zahra Bayat",‬‬
‫‪"iat": 1516239022‬‬
‫}‬

‫‪ : Signature‬در لایا ‪ ،‬قسامت ‪ Signature‬را داری ‪ .‬معموالً سارور از این قسامت برای‬ ‫•‬

‫بررسای اینکه آیا وکن حاوی اطالعات معتبری اسات یا خیر اسات اد میکن ‪ .‬این امضای‬

‫و بر اساااس یک ‪ Secret Key‬که فقط ساارور‬ ‫‪Payload‬‬ ‫دیجیتال‪ ،‬از رکیب ‪ Header‬و‬

‫میدان ولی میشود‪.‬‬

‫وقتی کالینت ساعی در لییر مقادیر موجود در ‪ Payload‬داشاته باشا بای ‪ Signature‬را دوبار‬
‫که سرور آ را میدان ‪ ،‬نیاز دارد‪.‬‬ ‫‪Secret key‬‬ ‫ولی کن بنابراین به‬

‫وقتی کالینت این ‪ Signature‬جعلی را ایجاد و ارساال کرد‪ ،‬در سامت سارور ‪ Signature‬اصالی‬
‫مقایساااه میشاااود و به راحتی می وا از جعلی بود اطالعات‬ ‫با ‪ Signature‬دریافت شا ا‬
‫مطملن ش ‪.‬‬

‫بناابراین ماا باه راحتی می وانی ‪ ،‬باا مقاایساااه امضااااهاای دیجیتاالی‪ ،‬از یکپاارچگی داد هاای خود‬
‫اطمینا حاصل کنی ‪ .‬این دلیل است اد ما از ‪ JWT‬است‪.‬‬

‫‪318‬‬
JWT ‫پیکربندی‬
‫در فااایاال‬ issuer ‫و‬ audience ‫ اولایان ق ا م ذخایار اطاالعااات‬،JWT ‫بارای لایاکاربان ا ی‬
.‫ است‬appsettings.json
{
"ConnectionStrings": {
"sqlConnection": "server=.; database=CompanyEmployee; Integrated
Security=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"JwtSettings": {
"validIssuer": "MicrodevAPI",
"validAudience": "https://localhost:5001"
},
"AllowedHosts": "*"
}

Key ‫ بنابراین بای این‬. ‫ نیاز داری‬secret key ‫ در سامت سارور به یک‬، ‫همانطور که باال ر گ تی‬
. ‫را ایجاد و در یک متلیر سیستمی ذخیر کنی‬

. ‫ باز و دستور زیر را اجرا کنی‬Admin ‫ را به صورت‬cmd ‫برای ایجاد یک متلیر سیستمی‬
setx SECRET "MicrodevSecretKey" /M

319
‫ ایجاد می‬MicrodevSecretKey ‫ و با مق ار‬SECRET ‫این دسااتور یک متلیر ساایسااتمی با نام‬
.local ‫ مشخش میکنی که متلیر بای از نو سیست باش نه‬/M ‫ با است اد از‬. ‫کن‬

. ‫ ایجاد کنی‬ServiceExtensions ‫ در کالس‬JWT ‫حاال بای اکستنشن مت ی برای لیکربن ی‬

ServiceExtensions ‫ در کالس‬، ‫های لایین را قبل از نوشاتن این اکساتنشان مت‬Namespace


. ‫اضافه کنی‬
using System;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

: ConfigureJWT ‫کدهای اکستنشن متد‬


public static void ConfigureJWT(this IServiceCollection services,
IConfiguration configuration)
{
var jwtSettings = configuration.GetSection("JwtSettings");
var secretKey = Environment.GetEnvironmentVariable("SECRET");
services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.GetSection("validIssuer").Value,
ValidAudience =
jwtSettings.GetSection("validAudience").Value,

320
IssuerSigningKey = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
});
}

: ‫بررسی کد‬

‫را مایگایاریا و ساااپاس‬ JwtSettings ‫ماق ا ار‬ appsettings.json ‫ابات ا ا از فااایاال‬ •

. ‫را واکشی میکنی‬secretKey ‫مق ار‬


JWT Middleware ‫ بارای رجایساااتار کارد‬AddAuthentication ‫صاااا ا زد مات ا‬ •

.‫ است‬Authentication
‫ و‬JwtBearerDefaults.AuthenticationScheme ‫باااایا ا‬ Authentication ‫بااارای‬ •

. ‫ را مشخش کنی‬ChallengeScheme

.‫است‬ ‫ مورد نیاز هست آورد ش‬JWT ‫در اینجا برخی لارامترهایی که هنگام أیی‬

)ValidateIssuer = true( .‫ سروری است که رمو را ایجاد کرد است‬issuer •

)ValidateAudience=true( .‫معتبر است‬ ‫رمو یک گیرن‬ ‫گیرن‬ •

)ValidateLifetime = true( .‫است‬ ‫وکن منقضی نش‬ •

)ValidateIssuerSigningKey=true( .‫ معتبر و مورد اعتماد سرور است‬signing key •

Signature ‫ که ساارور برای ولی‬secret key ‫ و‬audience ، issuer ‫ مقادیر‬،‫عالو بر این‬ •

. ‫ است اد میکن را ارائه میدهی‬JWT ‫برای‬

. ‫ ص ا بونی‬ConfigureServices ‫حاال بای این اکستنشن مت را در مت‬


services.ConfigureIdentity();
services.ConfigureJWT(Configuration);

‫ در‬Authorize ‫ را بااز کرد و یاک ا ربیوت‬CompanyController ،API ‫ساااپس برای مواافظات از‬
. ‫ اضافه کنی‬GetCompaniesAsync ‫باالی اکشن مت‬
[HttpGet(Name = "GetCompanies"), Authorize]
public async Task<IActionResult> GetCompaniesAsync()
{

321
var companies = await
_repository.Company.GetAllCompaniesAsync(trackChanges:
false);

return Ok(companies);
}
. ‫ اضافه نمایی‬CompanyController ‫ لایین را در‬Namespace ‫فراموش نکنی که‬
using Microsoft.AspNetCore.Authorization;

. ‫ بای لکی لایین را نصب کنی‬،‫قبل از ست‬


Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version
5.0.2 -ProjectName CompanyEmployee.API

. ‫ ریکوئست لایین را به اللیکیشن ارسال کنی‬JWT ‫برای ست‬


https://localhost:5001/api/companies

!!‫نکته‬

‫ نگران نباشتید یکبار ویژوال استتدیو‬.‫شتاید بعد از اجرای این ریکوئستت به اکستپشتن برخورد کنید‬
.‫را ببندید و دوباره باز کنید‬

.‫حاال می توانید ریکوئست را اجرا کنید‬

322
-‫ دریافت کردی چو یک کاربر غیر مجاز می‬401 Unauthorized ‫همانطور که میبینی لیام‬
. ‫ دسترسی داشته باش‬API ‫خواه به‬

. ‫ شود و یک رمو معتبر داشته باش‬Authenticate ،‫خب ما بای کاری کنی که کاربر‬

Authentication ‫پیادهسازی‬
‫ و‬UserName ‫ داشااتن یک کالس برای نگه اری‬،Authentication ‫اولین گام برای لیاد سااازی‬
‫ یااک کااالس بااا نااام‬DataTransferObjects ‫ بااناااباارایاان در فااول ا ر‬.‫اساااات‬ Password

. ‫ ایجاد کنی‬UserForAuthenticationDto
using System.ComponentModel.DataAnnotations;

namespace Entities.DataTransferObjects
{
public class UserForAuthenticationDto
{
[Required(ErrorMessage = "User name is required")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password name is required")]
public string Password { get; set; }
}

}
323
‫ بنابراین بهتر‬. ‫ داشاته باشای‬Token ‫ و ولی‬Authentication ‫میخواهی منطق لیچی ای برای‬
. ‫است این اکشنها را در سرویس دیگری واکشی کنی‬

IAuthenticationManager ‫ یاک اینترفیس جا یا باا ناام‬Contracts/IServices ‫لس در مسااایر‬


. ‫ایجاد نمایی‬
using Entities.DataTransferObjects;
using System.Threading.Tasks;

namespace Contracts.IServices
{
public interface IAuthenticationManager
{
Task<bool> ValidateUserAsync(UserForAuthenticationDto
userForAuth);
Task<string> CreateTokenAsync();
}

. ‫ نصب کنی‬Repository ‫حاال نیاز است ا لکی لایین را در لروه‬


Install-Package System.IdentityModel.Tokens.Jwt -Version 6.8.0 -
ProjectName Repository

‫ را برای لیاد سااازی‬AuthenticationManager ‫ کالس‬Repository ‫در مرحله بع بای در لروه‬


. ‫این اینترفیس ایجاد نمایی‬
using Contracts.IServices;
using Entities.DataTransferObjects;
using Entities.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;

324
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Repository
{
public class AuthenticationManager : IAuthenticationManager
{
private readonly UserManager<User> _userManager;
private readonly IConfiguration _configuration;
private User _user;
public AuthenticationManager(UserManager<User> userManager,
IConfiguration configuration)
{
_userManager = userManager;
_configuration = configuration;
}
public async Task<bool>
ValidateUserAsync(UserForAuthenticationDto userForAuth)
{
_user = await
_userManager.FindByNameAsync(userForAuth.UserName);

return (_user != null && await


_userManager.CheckPasswordAsync(_user,
userForAuth.Password));
}

public async Task<string> CreateTokenAsync()


{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
325
var tokenOptions = GenerateTokenOptions(signingCredentials,
claims);

return new JwtSecurityTokenHandler().WriteToken(tokenOptions);


}

private SigningCredentials GetSigningCredentials()


{
var key =
Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SEC
RET"));
var secret = new SymmetricSecurityKey(key);

return new SigningCredentials(secret,


SecurityAlgorithms.HmacSha256);
}

private async Task<List<Claim>> GetClaims()


{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, _user.UserName)
};
var roles = await _userManager.GetRolesAsync(_user);

foreach (var role in roles)


{
claims.Add(new Claim(ClaimTypes.Role, role));
}

return claims;
}

private JwtSecurityToken GenerateTokenOptions(SigningCredentials


signingCredentials, List<Claim> claims)
{
326
var jwtSettings = _configuration.GetSection("JwtSettings");
var tokenOptions = new JwtSecurityToken
(
issuer: jwtSettings.GetSection("validIssuer").Value,
audience: jwtSettings.GetSection("validAudience").Value,
claims: claims,
expires:

DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings.GetSe
ction("expires").Value)),
signingCredentials: signingCredentials
);

return tokenOptions;
}
}

: ‫بررسی کد‬

‫ بررسا ای میکنی کاربر در دیتابیس وجود دارد و آیا رمو ورود با‬ValidateUser ‫در مت‬ •

.‫رمو دیتابیس مطابق است‬


‫ از متا‬،‫ و برای چاک کرد لساااورد‬FindByNameAsync ‫برای یاافتن کااربر از متا‬ •

. ‫ است اد کردی‬UserManager<User> ‫ کالس‬FindByNameAsync


private ‫بااا جاامااع آوری اطااالعااا اای از مااتاا هااای‬ CreateToken ‫مااتاا‬ •

‫) و ماااتااا‬ GenerateTokenOptions- GetClaims-GetSignInCredentials(

. ‫ یک وکن ایجاد میکن‬،WriteToken


. ‫ را برمیگردان‬Secret key ‫ یک‬GetSignInCredentials ‫مت‬ •

‫علق دارد را‬ ‫هاایی کاه کااربر باه آ‬Role ‫هاا و‬Claim ‫لیساااتی از‬ GetClaims ‫متا‬ •

. ‫برمیگردان‬
‫ باا ماام گویناههاای مورد‬JwtSecurityToken ‫ یاک شااای‬GenerateTokenOptions ‫متا‬ •

‫ بای‬JwtSecurityToken ‫ وجه داشاته باشای که لارامترهای‬. ‫ ایجاد و برمیگردان‬،‫نیاز را‬


327
‫ را ه به این فایل‬expires ‫ واکشای شاود بنابراین بای لارامتر‬appsettings.json ‫از فایل‬
. ‫اضافه کنی‬
{
"ConnectionStrings": {
"sqlConnection": "server=.; database=CompanyEmployee; Integrated
Security=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},

"JwtSettings": {
"validIssuer": "MicrodevAPI",
"validAudience": "https://localhost:5001",
"expires": 5
},
"AllowedHosts": "*"
}

. ‫ رجیستر کنی‬ConfigureServices ‫حاال بای این کالس را در مت‬


services.AddScoped<IAuthenticationManager, AuthenticationManager>();

. ‫ اضافه نمایی‬Startup ‫ لایین را در کالس‬Namespace ‫حتما‬


using Repository;

. ‫ اضافه کنی‬AuthenticationController ‫ را در‬AuthenticateAsync ‫در لایا بای مت‬


using AutoMapper;
using CompanyEmployee.API.Infrastructure.ActionFilters;
using Contracts.IServices;
using Entities.DataTransferObjects;
using Entities.Models;

328
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace CompanyEmployee.API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
private readonly IAuthenticationManager _authManager;

public AuthenticationController(ILoggerManager logger, IMapper


mapper, UserManager<User> userManager , IAuthenticationManager
authManager)
{
_logger = logger;
_mapper = mapper;
_userManager = userManager;
_authManager = authManager;
}

[HttpPost("login")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult> AuthenticateAsync([FromBody]
UserForAuthenticationDto user)
{
if (!await _authManager.ValidateUserAsync(user))
{
_logger.LogWarn($"{nameof(AuthenticateAsync)}: Authentication
failed. Wrong user name or password.");

return Unauthorized();
329
}

return Ok(new { Token = await _authManager.CreateTokenAsync() });


}

[HttpPost]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult> RegisterUserAsync([FromBody]
UserForRegistrationDto userForRegistration)
{
var user = _mapper.Map<User>(userForRegistration);
var result = await _userManager.CreateAsync(user,
userForRegistration.Password);

if (!result.Succeeded)
{
foreach (var error in result.Errors)
{
ModelState.TryAddModelError(error.Code,
error.Description);
}

return BadRequest(ModelState);
}

await _userManager.AddToRolesAsync(user,
userForRegistration.Roles);

return StatusCode(201);
}
}
}

: ‫بررسی کد‬

330
‫‪401‬‬ ‫چیو خاصاای در این مت وجود ن ارد‪ .‬اگر اعتبارساانجی انجام نشااود ریسااپانس‬ ‫•‬

‫میشود‪.‬‬ ‫برگردان‬ ‫‪ Unauthorized‬را برمیگردانی ‪ .‬در غیر این صورت‪ ،‬وکن ایجاد ش‬
‫‪https://localhost:5001/api/authentication/login‬‬

‫همانطور که میبینی یک وکن ولی ش ‪ .‬حاال بیایی با لسورد نامعتبر امتوا کنی ‪.‬‬

‫یک لیام ‪ 401 Unauthorized‬دریافت میکنی ‪.‬‬

‫‪331‬‬
‫اال با هر ریکوئساتی‪ ،‬بای این وکن ارساال شاود وگرنه همچنا لاساخ ‪ 401 Unauthorized‬را‬
‫دریافت خواهی کرد‪.‬‬
‫‪https://localhost:5001/api/companies‬‬

‫اگر ریکوئست را ب رستی ‪ ،‬بای نتیجه زیر را ببینی ‪.‬‬

‫‪332‬‬
Role ‫اعتبارسنجی براساس‬
‫ اگر‬. ‫می وان به هر اکشانی دساترسای لی ا کن‬ ‫ شا‬Authenticate ‫ هر کاربر‬،‫در حال حاضار‬
‫ افراد کمک‬Role ‫بخواهی برخی افراد به برخی اکشانمت ها دساترسای ن اشاته باشان بای از‬
‫ را صا ا‬GetCompaniesAsync ‫ به عنوا مثال میخواهی فقط م یر بتوان اکشان مت‬. ‫بگیری‬
. ‫بون‬

GetCompaniesAsync ‫نها کاری که بای انجام دهی اضااافه کرد ک لایین به اکشاان مت‬
.‫است‬
[HttpGet(Name = "GetCompanies"), Authorize(Roles = "Manager")]
public async Task<IActionResult> GetCompaniesAsync()

. ‫ ایجاد کنی‬Administrator ‫برای ست این مورد بای کاربر دیگری با نق‬


https://localhost:5001/api/authentication
body

{
"firstname":"Ali",
"lastname":"Bayat",
"username":"AliBayat",
"password":"P@ssw0rd67894",
"email":"[email protected]",
"phonenumber":"0123456789",
"roles":[
"Administrator"
]
}

333
‫گرفتی لس به سراف ریکوئست بع ی میروی ‪.‬‬ ‫‪201‬‬ ‫همانطور که میبینی‬

‫خب حاال بای وکن ج ی ی برای این کاربر داشته باشی ‪.‬‬

‫‪334‬‬
‫حاال این وکن را در ریکوئست بع ی بای است اد کنی ‪.‬‬
‫‪https://localhost:5001/api/companies‬‬

‫‪API‬‬ ‫همانطور که میبینی ‪ 403 Forbidden‬گرفتی چو این کاربر مجاز به دسااترساای به این‬
‫نیست‪.‬‬

‫ماا می وانی ا ربیوت ‪ Authorize‬را در ساااطح کنترلر نیو قرار دهی اا کااربرا مجااز‪ ،‬اجااز‬
‫دسترسی به مام اکشنمت های آ کنترلر را داشته باشن ‪.‬‬

‫نکته!!‬

‫توکن ما پس از پنج دقیقه از زمان ایجاد‪ ،‬منقضتی میشتود‪ .‬بنابراین اگر پس از ‪ 5‬دقیقه ریکوئستت‬
‫ارسال کنیم مطمئناً وضعیت ‪ 401 Unauthorized‬را دریافت خواهیم کرد‪.‬‬

‫‪335‬‬
‫فصل چهاردهم ‪ :‬ایجاد داکیومنت با ‪Swagger‬‬

‫آنچه خواهید آموخت‪:‬‬

‫داکیومنت ‪ API‬چیست؟‬ ‫➢‬

‫➢ ‪ Swagger‬چیست؟‬
‫➢ تنظیمات ‪Swagger‬‬
‫داکیومنت ‪ API‬چیست؟‬
‫‪API‬‬ ‫برنااماهنویساااانی کاه ‪API‬ی ماا را مصااارف میکننا ‪ ،‬نیااز دارنا کاه با اننا چطور باایا از آ‬
‫است اد کنن ‪ .‬لس اینجا جایی است که داکیومنت ‪ API‬وارد بازی میشود‪.‬‬

‫داکیومنت ‪ API‬دساتورالعملهایی در مورد نوو اسات اد از ‪ API‬را ارائه میده بنابراین می وا‬
‫آ را به عنوا یک کتابچه راهنمای‪ ،‬مختصاار در نظر گرفت که شااامل مام اطالعات مورد نیاز‬
‫برای کار با ‪ API‬است‪.‬‬

‫داشاتن داکیومنت مناساب باعث میشاود ا ساایر برنامهنویساا بتوانن ‪API‬های ما را با کارهای‬
‫خود‪ ،‬ادغام و وساعه دهن ‪ .‬همچنین این کار باعث میشاود ا نگه اری و لشاتیبانی از ‪API‬ها‪،‬‬
‫ساد ر شود‪.‬‬

‫‪ Swagger‬چیست؟‬
‫‪ Swagger‬یاک ابوار برای وصااای ‪ API‬اسااات کاه باه ماا امکاا میدها اا با و بررسااای یاک‬
‫سیست ‪API ،‬های آ را ببینی ‪.‬‬

‫‪ Swagger‬باه برنااماهنویس کماک میکنا اا باا سااارعات و دقات‪ ،‬برای ‪API‬هاای خود داکیومنات‬
‫ولی کن ‪.‬‬

‫‪Web API‬‬ ‫ماا می وانی باا اسااات ااد از لکی ‪ ،Swashbuckle‬باه راحتی ‪ Swagger‬را در لروه‬
‫اضافه کنی ‪.‬‬

‫سه کامپوننت اصلی در لکی ‪ Swashbuckle‬وجود دارد ‪:‬‬

‫‪ : Swashbuckle.AspNetCore.Swagger‬این کاامپوننات شاااامال ما ل ‪ Swagger‬و‬ ‫•‬

‫‪ SwaggerDocument‬است‪.‬‬ ‫‪Middleware‬ی برای نمای‬


‫‪ : Swashbuckle.AspNetCore.SwaggerGen‬یاک ‪ Swagger generator‬اسااات که‬ ‫•‬

‫‪ SwaggerDocument‬را مستقیماً از ‪Route‬ها‪Controller ،‬ها و ‪Model‬های ما میسازد‪.‬‬


‫‪ Swagger‬اسااات کاه‬ ‫‪UI‬‬ ‫‪ : Swashbuckle.AspNetCore.SwaggerUI‬یاک وره از‬ ‫•‬

‫‪ Swagger JSON‬را رجمه میکن ‪ .‬این کامپوننت برای وصی ‪ Web API‬است‬

‫‪337‬‬
‫ حتماا این دو خط‬، ‫ نگاا کنیا‬Startup ‫ کالس‬Configure ‫ و‬ConfigureServices ‫اگر باه متا‬
. ‫دستور را میبینی‬

: ConfigureServices ‫متد‬
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "CompanyEmployee.API",
Version = "v1" });
});

: Configure ‫متد‬
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"CompanyEmployee.API v1"));

‫ را داشااتی اما در این قساامت میخواهی این قابلیت را برای وره های‬Swagger ، ‫ما در لروه‬
. ‫ س ارشی کنی‬API

. ‫ اضافه کنی‬Configure ‫ و‬ConfigureServices ‫بنابراین دستورات لایین را در مت‬

: ConfigureServices ‫متد‬
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "CompanyEmployee.API",
Version = "v1" });
c.SwaggerDoc("v2", new OpenApiInfo { Title = "CompanyEmployee.API",
Version = "v2" });

});

: Configure ‫متد‬
app.UseSwagger();
app.UseSwaggerUI(c =>
{

338
c.SwaggerEndpoint("/swagger/v1/swagger.json", "CompanyEmployee.API v1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "CompanyEmployee.API v2");
});

‫قرار‬ CompaniesV2Controller ‫ و‬CompaniesController ‫حااال باایا ا ربیوت لاایین را در بااالی‬


. ‫دهی‬

: CompaniesController
[Route("api/companies")]

[ResponseCache(CacheProfileName = "120SecondsDuration")]
[ApiController]
[ApiExplorerSettings(GroupName = "v1")]
public class CompaniesController : ControllerBase

: CompaniesV2Controller

[Route("api/companies")]
[ApiController]
[ApiExplorerSettings(GroupName = "v2")]
public class CompaniesV2Controller : ControllerBase

‫و‬ v1 ‫ماتاعالاق بااه گارو‬ CompaniesController ‫ مایگاویایا کااه‬،‫بااا ایان الایایارات‬


‫ بااقی کنترلرهاای ه در هر دو گرو قرار‬.‫ اسااات‬v2 ‫ متعلق باه گرو‬CompaniesV2Controller
. ‫می گیرن زیرا وره ن ارن‬

‫ حااال اللیکیشااان را اجرا کنیا و در یاک مرورگر‬. ‫این ماام کااری اسااات کاه باایا انجاام میدادی‬
. ‫آدرس لایین را وارد کنی‬
https://localhost:5001/Swagger/index.html

339
‫در این صا وه یک داکیومنت ‪ json‬میبینی که شاامل مام کنترلرهاسات‪ .‬همانطور که میبینی‬
‫در باالی این ص وه در قسمت ‪ ،Select a definition‬می وانی وره ‪ API‬را مشخش کنی ‪.‬‬

‫لییر دهی نتیجه لایین را میبینی ‪.‬‬ ‫‪v2‬‬ ‫اگر ‪ v1‬را به‬

‫‪340‬‬
‫با کلیک بر روی هر اکشان مت ‪ ،‬می وانی اطالعات دقیق لارامترها‪ ،‬ریساپانس و مقادیر مثال را‬
‫کنی ‪ .‬همچنین با کلیک بر روی دکمه ‪ Try it out‬می وا هر یک از اکشاان مت ها را‬ ‫مشاااه‬
‫ست کنی ‪.‬‬

‫خب برای ست این ‪ API‬روی دکمه ‪ Try it out‬کلیک و سپس دکمه ‪ Execute‬را بونی ‪.‬‬

‫‪341‬‬
‫ما‬
‫الگین نکردی لس خطای ‪ 401‬میگیری ‪.‬‬

‫خب حاال برای فعال کرد ‪ ،Authorization‬بای برخی لییرات را اعمال کنی ‪.‬‬

‫کامای زیاااد اساااات و باااعااث شااالاوغای مات ا‬ ‫‪Authorization‬‬ ‫چاو اع ا اد خاط ک ا هااای‬
‫‪ ConfigureServices‬میشاود؛ لس بهتر اسات در کالس ‪ ServiceExtensions‬یک اکساتنشان‬
‫مت با نام ‪ ConfigureSwagger‬داشته باشی ‪.‬‬

‫‪ Namespace‬لایین را در این کالس اضافه کنی ‪.‬‬


‫;‪using Microsoft.OpenApi.Models‬‬

‫و سپس مت لایین را بنویسی ‪.‬‬


‫)‪public static void ConfigureSwagger(this IServiceCollection services‬‬
‫{‬
‫>= ‪services.AddSwaggerGen(s‬‬
‫{‬

‫‪342‬‬
s.SwaggerDoc("v1", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v1"
});
s.SwaggerDoc("v2", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v2"
});
s.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Place to add JWT with Bearer",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
s.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Name = "Bearer",
},
new List<string>()
}
});
});
}

343
‫خاب حااال دساااتور ‪ AddSwaggerGen‬را در متا ‪ ConfigureServices‬حاذف و ساااپس متا‬
‫‪ ConfigureSwagger‬را رجیستر کنی ‪.‬‬
‫;)(‪services.ConfigureSwagger‬‬

‫لییرات‪ ،‬اللیکیشن را دوبار استارت کنی ‪.‬‬ ‫برای دی‬

‫گوینه ‪ Authorize‬به ریکوئستهاست‪.‬‬ ‫اولین چیوی که میببینی اضافه ش‬

‫برای اسات اد از ‪ Authorize‬ابت ا بای وکن بگیری ‪ .‬لس اکشان ‪ api/authentication/login‬را باز‬
‫را کپی کنی ‪.‬‬ ‫و بع از وارد کرد ‪ User‬و ‪ ،Password‬وکن دریافت ش‬

‫‪344‬‬
‫خب حاال روی دکمه ‪ Authorization‬ریکوئست ‪ api/companies/‬کلیک کنی ‪.‬‬

‫این وکن را در مقابل ‪ Bearer‬لیست و روی ‪ Authorize‬کلیک کنی ‪.‬‬ ‫در کادر باز ش‬

‫وجه داشته باشی که حتما ‪ Bearer‬را در اول وکن بگذاری ‪.‬‬

‫لس از کلیک بر روی دکمه ‪ Authorize‬بر روی دکمه ‪ Close‬کلیک کنی ‪.‬‬

‫‪345‬‬
. ‫ را ست کنی‬API ‫حاال می وانی این‬

Swagger ‫تنظیمات‬
‫ دارد که میخواه آ را با‬UI ‫ گوینههایی برای وساعه داکیومنت و سا ارشای کرد‬Swagger
. ‫ه بررسی کنی‬

‫ را ارائاه‬Description ‫ و‬License ،Contact ‫ می وانا اطالعاا ی از جملاه‬AddSwaggerGen ‫متا‬


. ‫ده‬
public static void ConfigureSwagger(this IServiceCollection services)
{
services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v1",
Description = "CompanyEmployees API by Zahra Bayat",
Contact = new OpenApiContact
{
Name = "Zahra Bayat",
Email = "[email protected]",
346
Url = new
Uri("https://www.linkedin.com/in/zahrabayat"),
},
License = new OpenApiLicense
{
Name = "CompanyEmployees API ",
}
});
s.SwaggerDoc("v2", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v2"
});
s.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Place to add JWT with Bearer",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
s.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Name = "Bearer",
},
new List<string>()
}
});
347
‫;)}‬
‫}‬

‫‪.‬‬ ‫حاال بیایی برنامه را یک بار دیگر اجرا کنی و ‪ Swagger UI‬را بررسی کنی‬

‫برای فعال کرد ‪XML comment‬ها بای مراحل زیر را انجام دهی ‪.‬‬

‫گوینهی ‪ Properties‬را انتخاب‬ ‫بر روی لروه اصالی راسات کلیک کنی و از منو باز شا‬ ‫•‬

‫نمایی ‪.‬‬
‫در ب ‪ Build‬چک باکس ‪ XML documentation file‬را یک بونی ‪.‬‬ ‫•‬

‫حاال بای لییرا ی در مت ‪ ConfigureSwagger‬دهی ‪.‬‬


‫‪Namespace‬های لایین را قبل از نوشتن این مت به کالس ‪ ServiceExtensions‬اضافه کنی ‪.‬‬
‫;‪using System.IO‬‬

‫‪348‬‬
using System.Reflection;

: ConfigureSwagger ‫متد‬
public static void ConfigureSwagger(this IServiceCollection services)
{
services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v1",
Description = "CompanyEmployees API by Zahra Bayat",
Contact = new OpenApiContact
{
Name = "Zahra Bayat",
Email = "[email protected]",
Url = new Uri("https://www.linkedin.com/in/zahrabayat"),
},
License = new OpenApiLicense
{
Name = "CompanyEmployees API ",
}
});
s.SwaggerDoc("v2", new OpenApiInfo
{
Title = "CompanyEmployee.API",
Version = "v2"
});
var xmlFile =
$"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
s.IncludeXmlComments(xmlPath);
s.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,

349
Description = "Place to add JWT with Bearer",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
s.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Name = "Bearer",
},
new List<string>()
}
});
});
}

‫ اضافه‬GetCompaniesAsync ‫ را به اکشن مت‬triple-slash comment ‫در مرحله بع‬ •

.‫ اضافه شود‬Swagger UI ‫ ا وضیوا ی به‬، ‫کنی‬


/// <summary>
/// Gets the list of all companies
/// </summary>
/// <returns>The companies list</returns>
[HttpGet(Name = "GetCompanies"), Authorize(Roles = "Manager")]
public async Task<IActionResult> GetCompaniesAysnc()

. ‫ اللیکیشن را اجرا کنی‬،‫نتیجه‬ ‫حاال برای دی‬

350
،‫ میشااود‬Return ‫های ما را اساات اد میکنن به نتیجهای که‬API ‫معموالً برنامه نویسااانی که‬
‫ بنابراین وصای انوا ریساپانسها‬. ‫ عالقهمن هساتن‬،‫مخصاوصاا انوا ریساپانس و ک های خطا‬
.‫بسیار مه است‬
/// <summary>
/// Creates a newly created company
/// </summary>
/// <param name="company"></param>
/// <returns>A newly created company</returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
/// <response code="422">If the model is invalid</response>
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(422)]

[ServiceFilter(typeof(ValidationFilterAttribute))]
public async Task<IActionResult>
CreateCompanyAsync([FromBody]CompanyForCreationDto company)

351
‫کتابهای نوشته شده‪:‬‬

‫‪352‬‬

You might also like