Core5 0webapi
Core5 0webapi
راهنمای عملی
ASP.NET Core 5.0
Web API
ConnectionStringچیست؟ 49................................................................
234........................................................................... چیست؟Paging
282.......................................................................... چیست؟Caching
تنظیمات 346.........................................................................Swagger
تقدیم به
تقدیم به تمام دوستتداران برنامهنویستی که آمادهی استتفاده از تمام قابلیتهای خود
برای یادگیری هستند.
11
با تشکر
برنامه نویسها معموال به داشتتتن یک دوستتت هنرمند افترار میکنند .من هم خیلی
خوشتحالم که با یکی از بهترین گرافیستتهای کشتورم یعنی خانم عستل ضتیایی دوستت
هستتم و تشتکر میکنم که با ایدههای زیبایش به هر چه بهتر شتدن این کتاب کمک
کردند.
12
درباره این کتاب
که میخواهن به صااورت عملی ،صاا ر ا صاا این کتاب برای برنامهنویسااانی نوشااته شاا
ساخت Web APIرا برای ورود به بازار کار یاد بگیرن .
این روزها به دلیل اسات اد روز افوو از اللیکیشانهای موبایل ،نیاز به APIهایی که امکا ارائه
است. سرویس به هوارا کالینت به طور هموما را دارن به یک نیاز حیا ی ب یل ش
اگر نگاهی به آگهیهای شاللی بین ازی خواهی دی که ساه عم ای از بازار برنامهنویسای نیو
مربوط به وسعهی APIاست.
Web API از این رو در این کتااب ساااعی کردم برای برطرف کرد نیاازهاایی کاه در لروه هاای
وجود دارد مثال بیاورم و جربیا را با شما به اشتراک بگذارم .این کتاب می وان یک نقشه را
. برای است اد در لروه های واقعی باش
13
فصل اول :تنظیمات پروژه
.NETاسااات ااد میکردیا Framework .NETباا چیوی کاه در لروه هاای Core نظیماات در
،.NETدیگر خبری از فاایال web.configنیسااات و ماا از Core خیلی فرق دارد .در لروه هاای
نظیمات داخلی فری ورک است اد میکنی .
شاما در این فصال با کالس Startupو مت های درو آ آشانا میشاوی و یاد میگیری چطور
سرویسهای خود را رجیستر کنی .
در کادر بع ی گوینه ASP.NET Core Web Applicationرا انتخاب و بر روی Nextبونی .
15
حاال نام و مسیر لروه را انتخاب کنی .
مرحله بع انتخاب ASP.NET Core 5.0 ، .NET Coreو ASP.Net Core Web APIاست.
16
حاال بر روی Createکلیک کنی ا لروه ایجاد شود.
فایل : 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 در این فایل یک لرالر ی •
18
یاک زد بااشااایا باایا در بخ Setup اگر چاک بااکس مربوط باه HTTPSرا در مرحلاه •
HTTPS applicationUrlدو URLببینیا .این URLهاا یکی برای HTTPو دیگری برای
است.
""applicationUrl": "https://localhost:5001;http://localhost:5000
در این فاایال یاک لرالر ی sslPortوجود دارد کاه یاک عا د میگیرد .این عا د لورت •
اجرا کنیا این لورت باه IISExpress را نظی و زماانیکاه اللیکیشااان را باا HTTPS
نکته!!
توجه داشتته باشتید که این پیکربندی ،HTTPSتنها در محیط Localمعتبر استت .پس
زمانیکه برنامه را در ستتروری Deployمیکنید باید این HTTPSرا مجددا پیکربندی و
در اینجا یک HTTPSمعتبر قرار دهید.
در اینجاا یاک Propertyباا ناام launchUrlوجود دارد کاه عیین میکنا کا ام URLدر •
ابت ای اللیکیشاان اجرا شااود .البته برای اینکه این Propertyبه درسااتی کار کن بای
نظی کنی . True launchBrowserرا نیو بر روی
namespace CompanyEmployee.API
19
{
public class Program
{
)public static void Main(string[] args
{
;)(CreateHostBuilder(args).Build().Run
}
نکته!!
متتد ) CreateDefaultBuilder(argsفتایتلهتای پیشفرض ،متغیرهتای پروژه و پیکربنتدی
Loggerرا تنظیم میکند.
20
کالس Startup
همانطور که دی ی کالس Programبرای پیکربندی سااختار اللیکیشان شاما بود اما لیکربن ی
برخی از رفتارهای اپلیکیشن در کالس Startupانجام میشود.
هرگونه لیکربن ی که بای در زما اجرای ASP.NET Coreانجام شااود ،از این کالس شاارو
خواه شا .این کالس یک Constructorو دو مت دارد که مساالول لیکربن ی وب اللیکیشاان
است.
که ک ی اساات که می وان HTTP Requestو Middleware اللیکیشاان اضااافه کنی .
HTTP Responseرا لردازش ،لییر و در نهایت به Middlewareبع ی ده .
نکته!!
از آنجا که اپلیکیشتنهای بزرگتر ،سترویسهای بیشتتری هم دارند پس حتما کدهای
آشتفته زیادی در متد ConfigureServicesخواهیم داشتت .برای آنکه خوانایی این متد
باالتر رود ،میتوانیم با استتفاده از اکستتنشتن متدها ،ستاختاری تعریف کنیم و از این
آشفتگی دور باشیم.
اکستنشن مت ،یک مت استا یک است که بای قبل از اولین لارامتر ورودی آ ،کلمه کلی ی
thisرا قرار دهی .این اولین لارامتر ،نشا دهن ی نو داد ای است که این اکستنشن مت بر
آ یک قابلیت اضافه میکن .
21
وجه داشته باشی که اکستنشن مت بای درو یک کالس استا یک عری شود.
خب حاال بیایی ک نویسی را شرو کنی ا ببینی این چیوهایی که گ تی چیست؟ به چه دردی
میخورد؟ و چطور بای اضافه شود؟
یک فول ر ج ی با نام Infrastructureایجاد کنی سپس درو این فول ر یک فول ر دیگر با نام
Extensionsاضافه نمایی .
CORS (Cross- اولین کاری که میخواه انجام ده لیکربن ی CORSدر اللیکیشاان اساات.
) Origin Resource Sharingمکانیوم دساترسای داد یا مو ود کرد دساترسای به دومینهای
مختل اللیکیشن است.
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و •
همچنین )( AllowAnyMethodبه مامی HTTP methodها اجاز دساترسای میده ،در •
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());
});
namespace CompanyEmployee.API
{
public class Startup
{
public Startup(IConfiguration configuration)
24
{
Configuration = configuration;
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
app.UseRouting();
app.UseAuthorization();
25
>= app.UseEndpoints(endpoints
{
;)(endpoints.MapControllers
;)}
}
}
}
بررسی کد :
است.
در متا Configureه ،متا هاای UseRoutingو UseAuthorizationباه ر یاب ▪
دارن .
بااه Proxy کرد ه ا رهااای Forward برای UseForwardedHeaders مت ا ▪
میکنا .اگر مسااایری را برای دایرکتوری فاایالهاای اساااتاا یاک نظی نکنیا ،
ریکوئست به صورت لی فرض از فول ر wwwrootاست اد میکن .
نکته!!
26
توجه داشتته باشتید که ترتیب اضتافه کردن Middlewareها بستیار مهم استت بنابراین
قترار دهتیتد و هتمتچتنتیتن UseAuthorization را بتایتد بتعتد از UseRouting متتتد
باید قبل از UseRoutingصدا زده شوند. متدهای UseCorsیا UseStaticFiles
تنظیمات Environment
وسااعه میدهی اما به موض اینکه Development ما در حال حاضاار برنامه خود را در مویط
برنامه خود را Publishکنی ،اللیکیشن به مویط Productionمیرود.
در اینجا بهتر اسات به ساراف فایل appsettings.jsonبری .این فایل حاوی نظیمات ماسات .اگر
فارض یااک فااایاال کانای ا مایبایانای ا کااه بااه صاااورت لایا Expand ایان فااایاال را
appsetings.Development.jsonوجود دارد.
گوینهی برای ایجاد مویط Productionبای بر روی لروه راسات کلیک کنی و از منو باز شا
Add→New Itemرا انتخاب نمایی .
را انااتااخاااب و نااام ایاان فااایاال را App Settings File حاااال از کااادر باااز شاااا
appsettings.Production.jsonبگذاری .
27
بع از زد Addیک مویط ج ی اضافه خواه ش .
خب ا اینجا در مورد مویطهای وسااعه اللیکیشاان و نوو ایجاد آ ها چیوهایی را یاد گرفتی
اما چطور بای مشخش کنی که اللیکیشن در ک ام یک از مویطها اجرا شود؟
روش اول :روی لروه راست کلیک کنی و با انتخاب گوینه Propertiesوارد کادر زیر شوی .
28
ماااقاا ار ماااتااالااایااار مااای اااوانااایاا Environment variables حااااال در کاااادر
ASPNETCORE_ENVIRONMENTرا لییر دهی .
29
فصل دوم :سرویس Logger
➢ DIو IoCچیست؟
➢ تست سرویس Loggerبا Postman
پیکربندی سرویس Logger
چرا پیامهای Logدر طول توسعه اپلیکیشن بسیار مهم هستند؟
بیای ،به راحتی می وانی ک هایما را دیباگ و وسااعه اللیکیشاان خطایی لی اگر در زما
را حل کنی .اما دیباگ کرد در مویط Productionساااد نیساات به آم مشااکلی که لی
مشاکل و بررسای اکساپشان همین دلیل لیامهای Logمی وان یک را حل خوب برای فهمی
باش .
عالو بر این ،با است اد از Logمی وا در زمانیکه به دیباگر دسترسی ن اری ،به راحتی جریا
برنامه را کنترل کنی .
ما در این فصاال میخواهی با اساات اد از لکی ،NLogیک ساارویس س ا ارشاای Logبه لروه
اضافه کنی .
اولین لروه را Contractsناامگاذاری میکنی چو قرار اسااات اینترفیسهاای ماا در این •
راساااات کلیاک و ساااپس Solution Explorer خاب برای ایجااد یاک لروه جا یا بر روی
Class Library (.NET → Addرا انتخااب کنیا .حاال در کادر باز شااا گویناهیNew Project
Referenceرا انتخاب نمایی .سااپس همانن صااویر لایین ،چک باکس Contractsرا
یک زد و بر روی OKکلیک کنی .
32
گوینه Add Project در لروه اصاالی بر روی Dependenciesراساات کلیک و سااپس •
را بونیا .چو در لروه LoggerService یاک را انتخااب و در لاایاا Reference
}
قبل لیاد سازی این اینترفیس ،بای در لروه LoggerServiceلکی NLogرا نصب کنی .
برای اضاافه کرد NLogوارد مسایر Tools→NuGet Package Managerشاوی ساپس بر روی
Package Manager Consoleکلیک نمایی .
33
Install-Package NLog.Extensions.Logging -Version 1.7.0 -ProjectName
LoggerService
namespace LoggerService
{
public class LoggerManager : ILoggerManager
{
private static ILogger logger =
LogManager.GetCurrentClassLogger();
public LoggerManager()
{
}
}
: بررسی کد
ناام این فاایالهاا و حا اقال ساااطوی کاه،Log باایا اطالعاا ی در مورد مکاا فاایالهاایNLog
بنابراین در لروه اصالی بای مام این قراردادها. را در اختیار داشاته باشا، کنیLog میخواهی
. عری کنی nlog.config را در یک فایل متنی به نام
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>
36
بع از این مرحله بای سارویس Loggerرا در مت ConfigureServicesرجیساتر کنی .رجیساتر
کرد این سرویس می وان به سه حالت انجام شود :
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
{
;)}
}
}
اسااات ااد کنی باایا اینترفیس Logger از این لس هر زماا کاه بخواهی از سااارویس
.NETباا اجرای Core ILoggerManagerرا باه Constructorکالس موردنظر Injectکنی ،اا
اللیکیشن ،این سرویس را Resolveو ویژگی Logرا قابل دسترسی نمای .
DIو IoCچیست؟
ممکن اسات در مورد DIشانی ،و حتی در اللیکیشانهای خود از آ اسات اد کرد باشای ؛ اما
اجاز دهی کمی در مورد این موضو صوبت کنی .
کنیکی اساااات کااه بااا اسااات اااد از آ می وانی آبجکااتهااا و Dependency Injection
وابسااتگیهایشااا را از ه ج ا کنی .به بیان دیگر :به جای اینکه هر بار که به آبجکت نیاز
اسات ،آ را به صاورت صاریح در کالس newکنی ،می وانی یک بار از شای Instanceبساازی و
سپس آ را به کالس ارسال کنی .
38
درک DIبستیار مهم اساات زیرا ASP.NET Coreبرای دریافت هر ساارویساای از DIاساات اد
میکن .
DIیک Design Patternاست که قابلیت نوشتن ک های Loosely Coupledرا فراه میکن .
ج ایی نالذیر در اللیکیشانهای ماسات .این لییرات به مرور در دنیای نرمافوار ،لییرات بخ
لایههای اللیکیشان میشاود .آقای رابرت ساسایل مار ین معروف به عمو زما باعث خراب شا
بااب ،اصاااولی باه ناام SOLIDطراحی کرد کاه باه ماا کماک میکنا اا نرمافوار را طوری طراحی
کنی ،که کمتر به شکست بینجام .
قلب این اصاول ،اصال 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 در اصل یک
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ها به ما خیلی کمک میکن . در ارسال ریکوئست و نمای
https://localhost:5001/weatherforecast
بع از اجرا ،به مسایری که برای فایل nlog.configمشاخش کردی بروی .در این فول ر دو فایل
internal_logsو logfileوجود دارد.
42
اگر فایلها را باز کنی نتیجه لایین را میبینی .
این مام کاری بود که ما برای لیکربن ی Loggerبای انجام میدادی .
43
فصل سوم :دیتابیس و Repository Pattern
در این لتر می وا رویکرد Loosely Coupledرا جهت دسااترساای به داد های دیتابیس اجرا
کرد.
برای لیاد سازی این لتر ،بهتر است (براساس اصل )Separated Interface Patternاینترفیس
را از ه ج ا کنی ا کالینت وابسته لیاد سازی نشود. سازی Repository و لیاد
ایجاد مدلها
Class Library (.NET میخواهی مثال فصاال دوم این کتاب را کامل کنی بنابراین یک لروه
) Coreج ی با نام Entitiesایجاد کنی .
فراموش نکنی که در لروه اصلی بای از این لروه رفرنس بگیری .
در این لروه فول ری با نام Modelsایجاد کنی ا Entityها را درو آ قرار دهی .
45
کرد با ج اولMap از آ ها برایEntity Framework Core ،ها کالسهایی هساتن کهEntitiy
. میشونMap با ستو های دیتابیس،Entity های دروProperty . دیتابیس است اد میکن
: 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; }
[ForeignKey(nameof(Company))]
public Guid CompanyId { 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; }
: بررسی کد
47
Entity Framework Core ایاجاااد کاردیا . Employee و Company مااا دو کاالس •
لرالر یهای این کالسها را برای Mapکرد با ساتو های ج اول دیتابیس اسات اد می
. کن
(یاعانای )Employeesو آخاریان لارالارای کاالس Company آخاریان لارالارای کاالس •
Map [ ]Columnمشاااخش میکنا کاه Propertyباا یاک ناام دیگر در دیتاابیس ▪
شود.
] [Requiredورود اطالعات برای لرالر ی را اجباری میکن . ▪
] [MaxLengthحا اکثر کااراکتری کاه لرالر ی باایا داشاااتاه بااشااا را مشاااخش ▪
میکن .
و Attribute بعا از اینکاه این ما ل باه دیتاابیس انتقاال یاافات خواهی دیا کاه چطور این
Navigational Propertyها روی ستو ها اثیر میگذارن .
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)
{
}
چیست؟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 یاک مرحلاه دیگر باه ناام رجیساااتر کرد،برای کمیال لیکر بنا ی دیتاابیس
بای نو دیتابیس خود را،این ساارویس اما قبل از رجیسااتر شاا.دارد که بای انجام شااود
. مشخش کنی
اساااات ا اااد ک ان ا لااس لااکاای ا SQL Server ماان در ایاان کااتاااب ماایخااواه ا از
. را با دستور زیر نصب میکنMicrosoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.2 -
ProjectName CompanyEmployee.API
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());
});
}
: بررسی کد
Connection وانساااتی مقا ار GetConnectionString در کا بااال باا اسااات ااد از متا •
. بخوانیappsettings.json را از فایلString
52
از ورودی میگیرد و این رشاااتاه را باا Keyهاای درو فاایال string این متا یاک •
خواهی دی .
باه متا Key باه عنوا Connection String هماانطور کاه در کا بااال میبینیا ،ناام •
کنی .
;)services.ConfigureSqlContext(Configuration
Migrationچیست؟
بع از انجام مراحل باال ،نوبت به ایجاد دیتابیسمیرس .اما دیتابیس چطور ایجاد میشود؟
یک روش خوب برای ایجاد دیتابیس ،وادار کرد EFبه ساااخت دیتابیس اساات .ساااد رین
رویکرد EFبرای انجام این کار ،است اد از Migrationاست.
Migrationرا حلی برای ما یریات جا اول در دیتاابیس میبااشااا .باا Migrationمی وانیا با و
هیچگونه دردسری ،لییرات را به ج اول دیتابیس اعمال نمایی .
در لروه اصلی باش ،لس بای مت 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")));
را در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این ک ها را به دیتابیس اعمال نمایی .
نکته!!
Seed Dataچیست؟
در بسااایاری از مواقع با اجرای اللیکیشااان ،نیاز اسااات ا برخی از ج وال دیتابیس با اطالعات
لی فرضی مق اردهی اولیه شون .را حل این مسأله Seed Dataاست.
55
اطالعاا ی را باه جا اولی از، این امکاا را باه ماا می دها اا در حین اجرای برنااماه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
{
}
. ایجاد کنیMigration کرد دیتا در دیتابیس یکSeed برای: مرحله سوم
Add-Migration SeedData
Update-Database
58
.با این کار مام داد ها از فایلهای لیکربن ی به ج اول انتقال یافت
. کنی
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 نیااز باه یاک لروه جا یا باا ناام،بعا از ایجااد این اینترفیس •
59
!!نکته
.فراموش نکنید که پروژه اصلی باید از این پروژه رفرنس بگیرد
در لروه Repositories یاک فولا ر باا ناام،IRepositoryBase برای لیااد سااااازی •
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>();
61
}
بررسی کد :
کنی کاه نو فیلا هاا ،لاارامترهاا و جنریاک باه ماا امکاا میدها اا کالسهاایی عری
مت هایشا در زما است اد عیین شون .
را مایبایانای ا .از ایان لاااراماتار بارای باااال رفاتان trackChanges در ک ا باااال لاااراماتار •
قبال از ایجااد این کالسهاا باایا با انیا ،کاه هر کالس می وانا عالو بر ارثبری از کالس
،RepositoryBaseیک اینترفیس مخصاوا به خود نیو داشاته باشا .بنابراین ما بای منطقی را
که برای همه ریپازیتوریهایما یکی است را از ریپازیتوری کالسهای کاربر ج ا کنی .
(فولا ر )IServicesدو اینترفیس باا ناامهاای Contracts خاب باا این اوصاااااف در لروه
ICompanyRepositoryو IEmployeeRepositoryایجاد کنی .
اینترفیس : ICompanyRepository
namespace Contracts.IServices
{
public interface ICompanyRepository
{
}
}
اینترفیس : IEmployeeRepository
namespace Contracts.IServices
{
public interface IEmployeeRepository
{
}
62
}
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 لس از این مرحله ،ایجاد
ادامه با ه انجام میدهی .
مام شرکتهایی است که کارمن ا باالی 30سال دارن . APIی که خروجی آ
در چنین حاالتی باایا از هر دو ریپاازیتوری Instanceداشاااتاه بااشااای و داد هاای خود را از
Resourceها ب ست آوری .
شاای با دو کالس ،مشاکل چن انی با این مسالله وجود ن اشاته باشا اما اگر منطق ما نیاز به
میشود. رکیب 5یا 6کالس داشته باش مطملنا این موضو خیلی لیچی
خب برای اینکه به این قابلیت برسااای بای اینترفیس ج ی ی با نام 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;
}
}
65
_employeeRepository = new
;)EmployeeRepository(_repositoryContext
;return _employeeRepository
}
}
}
بررسی کد :
این روش خوبی اساات زیرا می وانی چن کار را در یک اکشاان انجام دهی .به عنوان
مثال:
دو شارکت اضاافه کنی ساپس دو کارمن لییر دهی و یک شارکت حذف کنی و در
ماام لییرات اعماال شاااود .در این صاااورت یاا هماه لییرات انجاام 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());
});
}
خط کا 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();
68
}
بررسی کد :
کاردیا .ایان روش در Inject را در کاناتارلار Repository هاماااناطاور کااه مایبایانای ا مااا •
اللیکیشاانهای کوچک خوب اساات اما برای اللیکیشاانهایی با مقیاس بورگتر بای یک
الیااه بایاویاناس بایان کاناتارلارهااا و ماناطاق ریاپااازیاتاوری ایاجاااد کانایا و سااارویاس
RepositoryManagerدرو الیه بیونس Injectشااود .با این کار کنترلر ساابک و آزاد از
منطق ریپازیتوری میشود و همچنین ک ما قابل نگه اری و قابل است اد مج د خواه
ش .
69
فصل چهارم :مسیریابی و REST
بستتته به ماهیت ریکوئستتت دادههای درخواستتت شتتده را از مدل میگیرد یا •
راساااات کلیاک و ساااپس Controllers برای ایجااد کنترلر ،در لروه اصااالی بر روی فولا ر
Add→Controllerرا انتخاب نمایی .
71
. کنترلر ما بای شبیه ک لایین باش
using Microsoft.AspNetCore.Mvc;
namespace CompanyEmployee.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CompaniesController : ControllerBase
{
}
}
: بررسی کد
را فراه CompaniesController این کالس مام مت های ضروری برای کالس. میکن
. میکن
. را میبینیRoute در ک باال شما ا ربیوت •
[Route("api/[controller]")]
72
Web این ،ا ربیوت مسایریابی اسات که ریکوئساتهای ورودی را به اکشان مت های درو
API Controllerمیرسااان .به موض ارسااال یک HTTP Requestبه اللیکشاان ،فری
ورک MVCآ ریکوئساات را جویه و سااپس الش میکن ا آ را با یک اکشاان در
کنترلر مطابقت ده .
هنگامی که یک URLصاا ا زد میشااود ،مو ور مساایریابی الش میکن ا متن URLرا (در
در وب اللیکیشن مطابقت ده . عری ش Controller مکا } ){controllerبا یک
داد خواه شا .در غیر این اگر مو ور مسایریابی نتوان چیوی برای طبیق بیاب ،خطا نمای
صورت نتیجه مسیریابی ،یک اکشنمت و Controllerمر بط با آ خواه بود.
میکند که وارد کردن آن برش ستوم یک پارامتر اختیاری به نام idرا مشتر •
اجباری نیستت اما در صتورت وجود ،مستیریاب مقداری برای پارامتر idضتبط
میکند.
73
نکته!!
در روش بااال باایا Controllerهاا و اکشااانمتا هاای خود را عری کنیا اا ریکوئسااات دریاافتی
AttributeRouting مطابق با قرارداد حرکت و باالخر مستتیریابی موردنظر خود را بیاب .اما در
بای برای مپ کرد مسااایرها عبارت [ ]Routeرا بر روی کنترلرها یا اکشااانمت های خود قرار
دهی .
Web API این روش ،انعطتافپتذیری بیشاااتری نسااابات باه روش قبال دارد و برای زماانیکاه از
است اد میکنی بسیار کارآم است.
هماانطور کاه بااال ر دیا یا ،معموال مسااایر لاایاه را بااالی کالس کنترلر میگاذاری بناابراین برای
اکشن مت های خاا ه ،بای مسیرها را درست باالی آ ها قرار دهی .
Webکاار میکنیا ،لیشااانهااد ی ASP.NET Coreاین اسااات کاه از API زماانیکاه باا لروه
مسیریابی مبتنی بر Attributeاست اد کنی .
RESTچیست؟
HTTP RESTیک سابک معماری اسات که ار باط کامپیو رهای را دور را با اسات اد از لرو کل
برقرار میکن .
74
اینترنت پر از نظرات این چنینی استت اما RESTدقیقا چیستت و چه مشتکلی را حل
میکند؟
اگر APIاز Http Methodهایی مثل GETیا POSTاست اد کرد ،دلیل بر Restبود •
آ نیست.
اگر یک APIخروجی JSONبرگردان ،دلیل بر RESTبود آ نیست زیرا مت های •
اما RESTچیست؟
Status انجاام عملیاا ی بر روی Resourceرا ،برای کالینات فراه و در نهاایات یاک REST
" Representational State Codeباه کااربر برمیگردانا .بناابراین از همین جااسااات کاه مخ
"Transferمیای .
Resource 1آبجکتهایی هستن که در طراحی APIاست اد میکنی .مثل User, Order :و...
75
HTTP Methodچیست؟
HTTP Methodها افعالی هسااتن که عملیات Resourceها را مشااخش میکنن .احتماالً شااما
و احتماال با این افعال کار کرد باشاای .بیایی نگاهی HTTP GETو HTTP POSTرا شاانی
دقیق ر به این افعال بین ازی .
GET : GETرای رین و سااد رین HTTP Verbی اسات که می وانی با اسات اد از آ •
را آل یت میکن وگرنه یک Resource یک Verb باش این Id دارای Resource
میکننا .کالینات اول فرض کنیا دو کالینات ،هموماا اطالعاات یاک کاارمنا را ویرای
نام کارمن را لییر میده و کالینت دوم دلار ما کارمن را عوض میکن .
حااال بعا از آلا یات ،این دو کالینات اطالعاا ی کاه دریاافات میکننا باا اطالعاات اولیاه آ هاا
مت اوت خواه بود و کالینتها نمیدانن وضعیت واقعی ،Resourceدر سرور چیست.
این موضاو دقیقا اوت بین PATCHو PUTاسات .در PUTاطالعات به صاورت کامل
آلا یات میشاااود و کالینات دقیقاا میدانا کاه اطالعاات این Resourceدر سااارور باه چاه
صورت است اما در Patchاین طور نیست.
را برای کالینت ارسال Response Body می وان POST در ،RESTful APIفقط •
کن PUT .و PATCHفقط می وانن ه رهای Created 201یا No Content 204را
برگردانن .
77
تفاوت بین این سه متد باعث ایجاد دو دیدگاه شده است :
راحتتر میتوان آن را تغییر داد .البته همین مورد میتواند به عنوان یک عیب
هم تلقی شود چون هیچ ساختاری برای کالینت وجود ندارد.
مه دیگری که بای به آ وجه کنی ،ساالسااله مرا ب بین Resourceهاساات .در این بخ
اللیکیشان Company ،به عنوا Entityاصالی و Employeeبه عنوا یک Entityوابساته اسات.
وقتی میخواهی یک مسااایر برای Entityوابساااتاه ایجااد کنی ،بای قرارداد را کمی مت ااوت ر
دنبال کنی .
/api/principalResource/{principalId}/dependentResource.
از آنجا که هیچ کارمن ی نمی وان ب و شاارکت باش ا لس مساایر برای Resourceکارمن ا
بای به صورت لایین نوشته شود.
api/companies/{companyId}/employees/
CompaniesController دوم ،به صاورت صاریح مشاخش میکنی که مسایریاب بای به کالس
اشار کن .
اطالعات مام شرکتها اولین اکشنمت را بنویسی . که برای برگردان حال زما آ رسی
برای نوشتن این مت بای به لروه Contractsیک رفرنس از لروه Entitiesاضافه کنی .
79
توجه!!
و Entitiesرفرنس دارد .از آنجتاییکته LoggerService ,Repository پروژه اصتتلی بته پروژههتای
LoggerServiceو Repositoryیک رفرنس از پروژه Contractsدارند ،پس پروژه اصتلی ما از طریق
LoggerServiceو Repositoryبه پروژه 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)
{
}
}
اطالعات شاارکتها را با اساات اد از مت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;
[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} ");
}
}
}
: بررسی کد
. کردی
ا عملکرد، گذاشااتیGetCompanies ] را باالی اکشاان متHttpGet[ سااپس ا ربیوت •
82
درو با ناه اکشااان متا ،برای Logکرد لیاامهاا و گرفتن دیتاا از کالس ریپاازیتوری از •
Statusرا همرا باا نتیجاه متا برگردانا .در اینجاا متا OKاطالعاات Code بتوانا یاک
برمیگردان . Status Code 200 مام شرکتها را همرا با
میخواهی اگر یک اکساپشان رد ده Status Code 500 ،را( که نمایانگر خطای داخلی •
سرور است) برگردانی .لس در قسمت Catchاین Status Codeرا Returnمیکنی .
api/companies چو باالی اکشاان مت [Route] ،وجود ن ارد لس مساایر این مت برابر •
است .این مسیر هما مسیری است که باالی کنترلر قرار گرفته است.
اگر اینگوناه نبااشااا احتمااال آ را در حاالات IISاجرا کرد ایا .لس اللیکیشااان را از حاالات اجرا
خارج کنی و دوبار همانن صویر ،در م CompanyEmployeesاجرا کنی .
83
عالی شد .همه چیز مطابق با کدی که نوشتیم کار کرد.
لییرکن APIما خراب نشااود؛ چو همیشااه Entity Backward Compatibilityیعنی اینکه اگر
دیتاهایی که کالینت برای ما میفرساات بای با م لی که ما از ورودی میگیری یکی باشاا .به
بیان دیگر:
اگر به هر دلیلی نیاز باش دیتابیس را لییر دهی ،بای لرالر یهای م ل ورودی ه لییر کن .
همانطور که می دانی لییر م ل دیتابیس به این معنی نیسات که اللیکیشان سامت کالینت ه
بای لییر کن .بنابراین اگر DTOداشااته باشاای ،نتیجهای که کالینت بای داشااته باش ا ،مثل
گذشته میمان و نها Entityما لییر میکن .
84
ما در ک باال برای مپ کرد ریکوئسات به دیتابیس ،مساتقیما از Company Entityاسات اد و
Bad Practice Entityباه عنوا Responseیاک نتیجاه را باه کالینات برمیگردانی .برگردانا
اساات چو هیچ Backward Compatibilityن اری و گاهی شااای نخواهی همه لرالر یها یک
Entityرا به عنوا نتیجه برگردانی .به عنوان مثال:
لس ا اینجا متوجه شا ی که اسات اد از 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را حذف کردی چو در اکشن مت •
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;
[HttpGet]
public IActionResult GetCompanies()
{
try
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);
return Ok(companiesDto);
}
86
)catch (Exception ex
{
_logger.LogError($"Something went wrong in the
;)" }{nameof(GetCompanies)} action {ex
}
}
}
حاال اللیکیشن را استارت و ریکوئست لایین را در Postmanاجرا کنی .
https://localhost:5001/api/companies
87
باه ماا کماک میکنا اا آبجکاتهاا را در اللیکیشااان ماپ کنی و کا یAutoMapper کتاابخااناه
. خوانا رو قابل نگه اری داشته باشی
namespace CompanyEmployee.API
{
public class Startup
88
{
public Startup(IConfiguration configuration)
{
LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDire
ctory(),"/nlog.config"));
Configuration = configuration;
}
services.ConfigureRepositoryManager();
services.AddAutoMapper(typeof(Startup));
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title =
"CompanyEmployee.API", Version = "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
}
: بررسی کد
. ارثبری کن
اسااات ااد کنی اا شااای منبع وCreateMap باایا از متا، این کالسConstructor در •
ماپ را در متا لس باایا قوانین اضاااافاه شااا، بااشااا Country وAddress لرالر ی
. مشخش کنیForMember
namespace CompanyEmployee.API.Controllers
{
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _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
بنابراین در ولی اللیکیشاان ،بای برای اینگونه خطاها لاسااخ مناساابی به کاربر ارائه دهی و با
بهترین حالت ،این مسالله را م یریت نمایی .این مسالله می وان باعث شاکسات اللیکیشان شاما
شود.
فلسا ا هی طراحی ASP.NET Coreاین اسااات که ،هر Featureیک Optionاسااات .بنابراین
م یریت خطاها ،یک Featureاساات که شااما بای به صااراحت آ را در اللیکیشاان خود فعال
نمایی .
خطاهای مت او ی ،می وانن در اللیکیشاان شااما رد دهن و را های بساایاری برای بررساای آ
وجود دارن ؛ اما در اینجا میخواه دو مورد از مرسومترین خطاها را به شما نشا ده :
Exceptionها •
گاهی اوقات هیچ Exceptionی رد نمیده اما Middlewareممکن اساات ک خطایی را ایجاد
Handle باشاای ،زمانیساات که مساایر ریکوئساات کن .یکی از مواردی که ممکن اساات دی
95
نمیشاود .در این وضاعیت Pipelineخطای 404را برمیگردان .در نتیجه صا وه خطای 404به
داد میشود. کاربر نمای
گرچه این رفتار ،صتحیح استت اما تجربه خوبی برای کاربران اپلیکیشتن شتما نرواهند
بود.
الش میکن قبل از اینکه اللیکیشاان Error handling middleware برای حل این مشااکالت،
ده Response ،را لییر ده . چیوی را به کاربر نمای
میده .
با وجه به اینکه هرگونه خطا می وان اللیکیشان شاما را با شاکسات روبرو کن ،لس عجله کنی
و هرچه سریعتر Error handling middlewareرا به Middleware Pipelineاضافه نمایی .
یاک Middlewareداخلی باه ناام UseExceptionHandlerوجود دارد کاه ASP.Net Core در
می وانی از آ ،برای مقابله با Exceptionاست اد کنی .
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 در این کالس یاک اکساااتنشااان متا ایجااد کردی کاه در آ •
. اضافه کنی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;
}
services.ConfigureRepositoryManager();
services.AddAutoMapper(typeof(Startup));
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title =
"CompanyEmployee.API", Version = "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);
return Ok(companiesDto);
}
}
}
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);
}
}
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();
}
. اضافه کنی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;
[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);
}
}
}
}
: بررسی کد
را404 کاNotFound اگر این شااارکات وجود نا اشااات باا اسااات ااد از متا. میکنا
104
برمیگردانا در غیر این صاااورت اطالعاات شااارکات را باه CompanyDtoماپ میکنا و
همرا با ک 200به کالینت برمیگردان .
ناام آ هاا می وانی ،ASP.NETعا ادی متا وجود دارد کاه نهاا باا دیا Core در •
را برمیگردانا .و یاا متا Status Code 200 برای نتیجاه خوب اساااات و OK متا
و ( )Status Code 404را برمیگردان . NotFoundبرای نتیجهای است که لی ا نش
بیایی اینبار یک ریکوئسات معتبر و یک ریکوئسات نامعتبر را با Postmanارساال کنی ،ا نتیجه
هر ک ام از مت های ASP.NET Coreرا ببینی .
105
Web API درParent/Child ارتباطات
، اما همانطور که میدانی. بود کار کردیParent Entity که یکCompany ا اینجا فقط با م ل
هر کارمن بای با یک شارکت. هر شارکت ع ادی کارمن مر بط دارد که وابساته شارکت هساتن
. های این کارمن ا از این طریق ایجاد شونURI خاا در ار باط باش بنابراین بای
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;
}
بررسی کد :
همه شااما با این ک ها آشاانا هسااتی .نها نکته این ک Route ،باالی کنترلر اساات که •
همانطور که میدانی این چیوی اساات که در مام اکشاان مت ها مشااترک میباش ا به
همین دلیل ما این مسیر را به عنوا مسیر ریشه عیین میکنی .
قبل از ایجاد یک اکشاان مت برای واکشاای کارمن ا هر شاارکت ،بای لییرا ی در اینترفیس
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)
{
}
}
. اضافه کنی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;
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);
}
}
}
: بررسی کد
بناابراین یاک کالس باا ناام. بااشااا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>();
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;
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);
namespace Repository.Repositories
{
public class EmployeeRepository : RepositoryBase<Employee>,
IEmployeeRepository
{
public EmployeeRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }
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();
}
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);
}
}
}
.عالی شد
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را لییر دهی .
XMLلشتیبانی کن .
118
حاال که سرور خود را لیکربن ی کردی ،اجاز دهی یکبار دیگر Content Negotiationرا ست
کنی .
حااال باا لییر ها ر Acceptاز text/xmlباه text/jsonمی وانی ریساااپاانسااای باا فرمات مت ااوت
بگیری .
خب به نظر شتتما اگر کالینت نوعی را درخواستتت کند که ستترور نتواند آن فرمت را
نمایش دهد چه اتفاقی میافتد؟
لاسخ این سوال را Media Typeمیده .با ما همرا باشی ا در این بار بیشتر ب انی .
119
محدود کردن Media Type
با یال میکنا .اماا ماا JSON در حاال حااضااار ،سااارور باه طورلی فرض Responseرا باه نو
می وانی با اضافه کرد یک خط به نظیمات ،این رفتار را مو ود کنی .
{ >= services.AddControllers(config
;config.RespectBrowserAcceptHeader = true
;config.ReturnHttpNotAcceptable = true
;)(}).AddXmlDataContractSerializerFormatters
بررسی کد :
نظی کنی و APIلایین را ص ا بونی . text/css حاال برای ست ،ه ر Acceptرا بر روی
https://localhost:5001/api/companies
406 همانطور که انتظار میرفت ،هیچ Response Bodyوجود ن ارد و نها چیوی که میگیری
Not Acceptableاست.
120
ایجاد فرمت سفارشی
اگر برواهیم ،APIفرمتی که در کادر نیست را هم پشتیبانی کند چه باید کرد؟
خوشابختانه ASP.NET Coreاز ایجاد فرمتهای سا ارشای لشتیبانی میکن .برای اینکه بتوانی
فرمت س ارشی داشته باشی می وانی از مت های لایین است اد کنی .
بااای ا ی اک کاالس ایاجاااد کانای ا کااه از کاالس Input Formatter بارای ایاجاااد •
حاال بیایی یک قالب CSVس ارشی برای مثالما آماد کنی .
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;
}
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 مت •
. میکن
123
خیلی عالی ش ،همه چیو خیلی خوب کار میکن .
در این فصال ریکوئساتهای GETرا اضاافه کردی .در فصال بع ریکوئساتهای PUT ،POSTو
DELETEرا بررسی میکنی .
124
POST , PUTو DELETE فصل ششم :
➢ Model Bindingچیست؟
مدیریت Post Request
Resource Postیکی از HTTP Methodهای Restاسااات که برای ایجاد یا آل یت بخشااای از
اساات اد میشااود .اگر Resourceدارای Idباشاا ،این فعل یک Resourceرا آل یت میکن
وگرنه یک Resourceج ی را ایجاد خواه کرد.
را مااننا کا لاایین CompaniesController گتام اول :ا ربیوت اکشااان متا GetCompanyدر
اصالح کنی ،چو در ادامه به این ک نیاز داری .
])"[HttpGet("{id}", Name = "CompanyById
}
همانطور که میبینی این کالس قریبا مشابه کالس Companyاست با این اوت که Idن ارد.
در برخی لروه ها ،کالس DTOورودی و خروجی یکی اساات؛ اما وصاایه میکنی این دو را از
ه جا ا کنیا اا نگها اری و ری کتور کا ،سااااد ر شاااود .عالو بر این ،وقتی صاااوبات از
اعتبارسانجی را شارو کنی ،نیازی به اعتبارسانجی آبجکت خروجی نیسات اما آبجکت ورودی
بای اعتبارسنجی شود.
namespace Contracts.IServices
126
{
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
void CreateCompany(Company company);
}
}
namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext)
: base(companyEmployeeDbContext)
{ }
127
public void CreateCompany(Company company) => Create(company);
}
!!نکته
تمام کاری که. انجام می شتودEF Core را برای شترکت خود ایجاد نمی کنیم این کار توستطId ما
. قرار دهیمAdded شرکت را بر رویState انجام می دهیم این است
دیگرRule اضاافه کنی بای یک CompaniesController قبل از اینکه یک اکشان مت ج ی در
بنابراین ک لایین. بنویساایCompanyForCreationDto وCompany برای مپ کرد آبجکت
. اضافه نماییMappingProfile را در کالس
CreateMap<CompanyForCreationDto, Company>();
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;
[HttpGet]
public IActionResult GetCompanies()
{
var companies =
_repository.Company.GetAllCompanies(trackChanges: false);
var companiesDto =
_mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companiesDto);
}
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.");
130
}
}
}
ست کنی . Postman قبل از وضیح ب نه ک بیایی یکبار این اکشن مت را با
https://localhost:5001/api/companies
Body:
{
"name": "Fara_Ltd",
"address": "Tehran, Vanak Iran",
""country":"Iran
}
بررسی کد :
بگذاری در مورد ک های این اکشن مت و این ریکوئست کمی بیشتر صوبت کنی .
131
چو لاارامتر ورودی از URIگرفتاه نمیشاااود لس باایا لاارامتر را از Bodyریکوئسااات •
بگیری .
از آنجاییکه لارامتر companyاز سامت کالینت میای لس ممکن اسات مقادیر معتبری •
ن اشاته باشا و نتوا آ را Deserializedکرد .در نتیجه بای این لارامتر را اعتبارسانجی
کنی ا مق ار آ nullنباش .
بعا از اعتباار سااانجی باایا این لاارامتر را برای ایجااد یاک Companyماپ کنی و متا •
لس از ذخیر Companyدر دیتاابیس ،باایا آبجکات companyرا باه CompanyDtoماپ •
132
Child Resource ایجاد
نیاز داشاتی حاال برایDTO برای ایجاد یک شارکت به یک،CreateCompany در اکشان مت
. ایجاد یک کارمن ه ما بای همین کار را انجام دهی
}
CompanyId را در سمت سرور ایجاد وId ن اری چو میخواهیCompanyId وId ما لرالر ی
. را از طریق مسیر ب رستی
[Route("api/companies/{companyId}/employees")]
namespace Contracts.IServices
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges);
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)
{
}
}
،MappingProfile را در اکشان بگیری لس در کالسEmployeeDTO چو میخواهی آبجکت
. نیاز داریMapping Rule به یک
CreateMap<EmployeeForCreationDto, Employee>();
134
. استEmployeesController حاال نوبت به ایجاد یک اکشن مت ج ی در
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");
return NotFound();
}
return
CreatedAtRoute("GetEmployeeForCompany",
new
{
135
companyId,
id = employeeToReturn.Id
},
employeeToReturn);
}
: بررسی کد
.خیر اولین اینکه بای بررسی کنی آیا شرکت موردنظر در دیتابیس وجود دارد یا •
به [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 تتا حتاال توانستتتیم یتک
.چه باید کرد؟ در این قسمت میخواهیم این موضوع را پیادهسازی کنیم
139
در دیتابیس را با اساات اد از می وانی اطالعات درج شااCompany بع از ایجاد،این یعنی
را GetCompanyCollection بناابراین قبال از اینکاه کا هاای. برگردانی GetCompany متا
. بهتر است اکشن مت ی برای واکشی اطالعات شرکتها داشته باشی، بنویسی
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);
}
}
namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
140
}
}
{
. اضافه نماییCompaniesController خب یک اکشن مت لایین را به
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);
}
var companyEntities =
_mapper.Map<IEnumerable<Company>>(companyCollection);
_repository.Save();
var companyCollectionToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);
142
var ids = string.Join(",", companyCollectionToReturn.Select(c => c.Id));
: بررسی کد
اساات یاNull برابر CompanyCollection در این اکشاان مت بررساای میکنی که آیا •
خیر؟
. برمیگردانیBadRequest باش یکNull اگر •
[
{
"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
است یا خیر؟
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;
}
if (string.IsNullOrEmpty(providedValue))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
var genericType =
bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0
];
bindingContext.Result =
ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
146
}
}
}
بررسی کد :
ایجااد میکنی ،لس باایا Model Binder داری یاک IEnumerable چو برای نو •
Nullیا Emptyباشا ،بای Nullرا برگردانی در غیر حاال در صاور یکه مق ار ب سات آم •
genericTypeمیریوی .
Converter ساپس بررسای میکنی که نو متلیر genericTypeچیسات و برای آ نو •
147
if (ids == null)
{
_logger.LogError("Parameter ids is null");
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 این
.این اکشنمت اجرا میشود
148
Delete ایجاد اکشن متد
. را لیاد سازی کنیDelete اکشنChild Resource بیایی با حذف یک
using Entities.Models;
using System;
using System.Collections.Generic;
namespace Contracts.IServices
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool
trackChanges);
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);
}
. اضافه کنی 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();
}
: بررسی کد
Status Code 204 حااوی را کاهNoContent در لاایاا کاارمنا موردنظر را حاذف و متا •
. است بر میگردانی
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است.
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)
{ }
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();
}
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.");
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");
return NotFound();
}
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اساات اد کردی .این یعنی :مساایر ما •
نها ک ی که کمی مت اوت اسات نوو واکشای companyو employeeEntityمیباشا . •
{
"name": "Sima Ahmadi",
"age": 25,
""position": "Marketing
}
159
در ریکوئست باال لرالر ی Ageاز 28به 25لییر کرد.
نامعتبر ارسااال کنی بای ریسااپانس EmployeeId اگر همین ریکوئساات را با CompanyIdیا
404دریافت کنی .
نکته:
همانطور که دیدید ما میخواستتیم فقط پراپرتی Ageرا تغییر دهیم اما تمام پراپرتیها با مقادیری
که در دیتابیس داشتند را ،هم ارسال کردیم.
پراپرتی Age PUTیک ریکوئستتت برای آپدیت کامل استتت بنابراین اگر ریکوئستتت باال را تنها با
(بدون باقی پراپرتیها ) هم می فرستتادیم ،باز تمام پراپرتیها روی مقادیر پیش فرضتشتان تنظیم
میشدند .بنابراین در ریکوئست PUTنیاز به ارسال پارامترهای اضافه نیست.
160
قبل از لاسخ به این سوال ،بگذاری به دو روش Updateاطالعات اشار ای داشته باش :
یاک آبجکات همرا باا ،Idاز کالینات دریاافات کردی و نیاازی باه واکشااای اطالعاات از دیتاابیس
ن اری .در این شارایط ،نها کاری که بای انجام دهی این اسات که به EF Coreاطال دهی ا
لییرات موجود در Entityرا Trackو Stateآ را Modifiedکن .بنابراین می وانی هر دو عمل
را با است اد از مت Updateکالس RepositoryBaseانجام دهی .
اولین کااری کاه باایا انجاام دهی ،ایجااد یاک کالس 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>();
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();
}
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 :و اعتبارسنجی
در PUTهمیشتته Resourceبته صتتورت کتامتل آپتدیتت میشتتود در حتالیکته در •
نکته!!
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را حاذف و مقا ار آ را باه صاااورت لی
کرد. خواهی
167
Employee Entity بهPatch اضافه کردن
.قبل از اینکه کنترلر را تغییر دهیم باید دو پکیج نصب کنیم
.است اد میشود
Request Body برای با یالMicrosoft.AspNetCore.Mvc.NewtonsoftJson و لکی •
168
داشااتهEmployeeForUpdateDto بهEmployee از نوMapping خب حاال نیاز اساات ا یک
از Mapping میبینیا کاه یاک، بینا ازیا MappingProfile اگر نگااهی باه کالس. بااشااای
Rule وجود دارد؛ لس دیگر نیاازی باه اضاااافاه کردEmployee باهEmployeeForUpdateDto
. است اد کنیReverseMap فقط کافیست که از مت، ج ی ن اری
CreateMap<EmployeeForUpdateDto, Employee>().ReverseMap();
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();
}
: بررسی کد
. ب رست
در دیتاابیس وجودemployee وcompany در خطهاای بعا ی باایا مطملن شاااوی کاه •
.دارد یا خیر
اعماال شاااودEmployeeForUpdateDto فقط می وانا باه نوpatchDoc چو متلیر •
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 نظی میشود (مق ار لی
[
{
"op": "add",
"path": "/age",
""value": "28
}
]
173
بیایی با ه Employeeرا چک کنی .
اعتبارسنجی چیست؟
داد ها قبل از ارساال بای اعتبارستنجی شاون و این موضاو خیلی مهمی در هخیرهستازی
اساات. اطالعات اساات .برای حل این مساالله ،اساات اد از DataAnnotationها مطرح شاا
DataAnnotationها به شاااما این امکا را میدهن ا ،قوانینی مشاااخش کنی و لرالر یها در
Modelمطابق با این قوانین عمل نماین .
شایو ی کار ب ین صاورت است کهDataAnnotation ،ها Metadataهایی برای وصی داد آماد
میکنن و داد ها بای از قوانین این Metadataها لیروی نماین .
به جای اینکه nullبود داد ها به صاورت دساتی چک شاود ،ا ربیوت ] [Requiredرا باالی
لرالر یهای خود بگذاری و دیگر خیالتا راحت باش که نمی وا داد ی Nullوارد نمود.
174
]) : [MinLength(minبررسی حداقل کاراکتر وارد شده. •
برای اعتبارسانجی در زما ایجاد یا آل یت Resourceاز ا ربیوتها اسات اد میشاود؛ یعنی این
اعتبارسنجیها در ریکوئستهای PUT ،POSTو PATCHکاربرد دارن .
اعتبارسانجی قبل از اجرای اکشانمت ،رد میده اما توجه داشتته باشتید که اکشانمت ها در
هر صاورت (چه اعتبارسانجی موفق باشا چه نباشا ) اجرا خواهن شا بنابراین م یریت داد های
معتبر بر عه ی اکشنمت است.
با 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; }
}
. می وانی دوبار ریکوئست لایین را ارسال کنی،لس از اعمال قوانین باال
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شوی .
178
. را لییر دهیCreateEmployeeForCompany حاال بای اکشن مت
[HttpPost]
public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody]
EmployeeForCreationDto employee)
{
if (employee == null)
{
_logger.LogError("EmployeeForCreationDto object sent from client is
null.");
return UnprocessableEntity(ModelState);
}
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");
return NotFound();
}
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
}
{
"name":"Saman",
180
"age":29,
"position":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
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; }
}
. را لییر دهیCreateCompany اکشن مت
public IActionResult CreateCompany([FromBody] CompanyForCreationDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForCreationDto object sent from client is
null.");
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForCreationDto object");
return UnprocessableEntity(ModelState);
}
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; }
}
. اکنو بیایی یک بار دیگر ریکوئست لایین را ب رستی
https://localhost:5001/api/companies/3D490A70-94CE-4D15-9494-
5248280C2CE3/employees/
body:
{
"name":"Saman",
"position":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
184
. را داریAge لیام خطایResponse حاال در
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; }
}
مقایسااه کنی میبینی که ک هر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; }
}
ااا کااالس ایااجاااد ماایکااناای ا 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.");
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the EmployeeForUpdateDto
object");
return UnprocessableEntity(ModelState);
}
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");
return NotFound();
}
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
}
188
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId,
Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
if (patchDoc == null)
{
_logger.LogError("patchDoc object sent from client is null.");
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
این ریکوئسات با موفقیت انجام شا اما اگر یاد ا باشا گ تی که عملیات حذف ،مق ار لرالر ی
را به مق ار لی فرض نظی میکن یعنی در اینجا بای به مق ار ص ر نظی کن .
مشکل کجاست؟
191
راemployeeEntity را اعتبارسنجی میکنی اماpatchDoc ما، همانطور که در این ک میبینی
. در دیتابیس ذخیر میکنی،ب و اعتبارسنجی
if (company == null)
{
_logger.LogInfo($"Company with id: {companyId} doesn't exist in the
database.");
return NotFound();
}
if (employeeEntity == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in the
database.");
return NotFound();
}
192
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the patch document");
return UnprocessableEntity(ModelState);
}
_mapper.Map(employeeToPatch, employeeEntity);
_repository.Save();
return NoContent();
}
[
{
"op": "remove",
"path":"/age"
}
]
در حالت Asyncوقتی ریکوئسااتی را به Threadاصاالی ارسااال میکنی ،این Threadکار را به
یک Background Threadواگذار میکن ؛ بنابراین برای یک ریکوئست دیگر آزاد است.
ماام شااا باایا نتیجاه را باه Threadاصااالی دها .در آخر Background Thread زماانیکاه کاار
برمیگردان . Threadاصلی نتیجه را به درخواست کنن
ریکوئست در حالت Asyncو یک موضو مهمی که بای ب انی این است که زما طول کشی
Syncیک ان از است و ما نمی وانی در حالت Asyncآنرا سریعتر اجرا کنی .
ریکوئسات، نها مویت Asyncنسابت به Syncاین اسات که Threadاصالی در زما اجرا شا
بالک نمیشود بنابراین Threadاصلی می وان ریکوئستهای دیگر را لردازش کن .
195
حاال که این موضاو برای شاما روشان شا بیایی نوو لیاد ساازی ک های Asyncدر .NET 5.0
را یاد بگیری .
وقتی از کلماه کلیا ی 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برگردانی .
نکته!!
دستتورات EF Core asyncزمان زیادی برای اجرا شتدن صترف میکند و این به دلیل کد اضتافهای
است که برای مدیریت Threadها داریم .بنابراین همیشه asyncانتراب مناسبی نیست.
بته طور کلی هر کجتا کته امکتان داردبتایتد از کتد asyncاستتتفتاده کنیم امتا هر جتا کته کتد asyncمتا
کندتر عمل کرد باید از حالت 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 •
namespace Repository.Repositories
{
public class CompanyRepository : RepositoryBase<Company>,
ICompanyRepository
{
public CompanyRepository(CompanyEmployeeDbContext
companyEmployeeDbContext): base(companyEmployeeDbContext)
{
}
198
public async Task<Company> GetCompanyAsync(Guid companyId, bool
trackChanges) =>
await FindByCondition(c => c.Id.Equals(companyId), trackChanges)
.SingleOrDefaultAsync();
}
}
. استRepositoryManager وIRepositoryManager خب حاال نوبت لییر
وجود دارد کاهSave باا ناام، را بااز کنیا میبینیا کاه یاک متاRepositoryManager اگر کالس
. را ص ا میزنSaveChanges EF Core مت
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;
}
return _companyRepository;
}
}
return _employeeRepository;
200
}
}
}
: بررسی کد
هساااتنا لس ماا ازawaitable متا هاای... وToListAsync ،SaveAsync چو متا هاای •
مت، الوامی نیساات اما اگر از آ اساات اد نکنی،await البته اساات اد از کلمه کلی ی
. اجرا میشود و مطملنا این ه ف ما نیستsync به صورتAsync
CompaniesController ریفکتور
لس اول از متا. کنیasync راCompaniesController در لاایاا کاار باایا ماام اکشااانهاای
. شرو میکنیGetCompanies
[HttpGet]
public async Task<IActionResult> GetCompaniesAsync()
{
var companies =await
_repository.Company.GetAllCompaniesAsync(trackChanges: false);
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;
[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);
}
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);
}
}
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 UnprocessableEntity(ModelState);
}
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.");
var companyEntities =
_mapper.Map<IEnumerable<Company>>(companyCollection);
await _repository.SaveAsync();
var companyCollectionToReturn =
_mapper.Map<IEnumerable<CompanyDto>>(companyEntities);
var ids = string.Join(",", companyCollectionToReturn.Select(c
=> c.Id));
[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 UnprocessableEntity(ModelState);
}
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)
{
}
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;
[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 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();
}
if (employeeDb == null)
{
_logger.LogInfo($"Employee with id: {id} doesn't exist in
the database.");
210
return NotFound();
}
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.");
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForCreationDto object");
return UnprocessableEntity(ModelState);
}
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();
}
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.");
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the
EmployeeForUpdateDto object");
return UnprocessableEntity(ModelState);
}
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.");
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 •
Response :Exception Filterاین فیلتر برای م یریت اکسااپشاان ،قبل از لر شاا •
Bodyاست اد میشود.
:Result Filterاین فیلتر قبل و بع از نتیجه اکشن مت اجرا میشود. •
در این فصل میخواهی در مورد Action Filterو نوو است اد از آ صوبت کنی .
نکته!!
و چتنتد IAsyncActionFilter ،IActionFilter ایتنتتترفتیتس ActionFilterAttribute کتالس
اینترفیس دیگر را پیادهسازی کرده است.
216
public abstract class ActionFilterAttribute : Attribute,
IActionFilter,IFilterMetadata, IAsyncActionFilter, IResultFilter,
IAsyncResultFilter, IOrderedFilter
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
}
}
}
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" };
}
}
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)]
از اکشان مت ها ج ا کنی و با این کار ک های میو و، ما می وانی ک های اعتبارسانجی را ه
. قابل نگه اری داشته باشی
میبینیا کاه، نگاا کنیا 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.");
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the CompanyForCreationDto
object");
return UnprocessableEntity(ModelState);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateCompanyAsync(Guid id, [FromBody]
CompanyForUpdateDto
company)
{
if (company == null)
{
_logger.LogError("CompanyForUpdateDto object sent from client is
null.");
221
if (!ModelState.IsValid)
{
_logger.LogError("Invalid model state for the CompanyForUpdateDto
object");
return UnprocessableEntity(ModelState);
}
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
}
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);
}
}
بای ک اعتبارسانجی را از اکشان مت ها حذف و این اکشان فیلتر را به عنوا سارویس، در لایا
. است اد کنی
224
public async Task<IActionResult>
CreateCompanyAsync([FromBody]CompanyForCreationDto
company)
{
var companyEntity = _mapper.Map<Company>(company);
_repository.Company.CreateCompany(companyEntity);
await _repository.SaveAsync();
[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
}
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;
}
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();
}
}
}
}
: بررسی کد
. وجه کنی در اینجا دو نکته وجود دارد که بای به آ. اکشن فیلتر را است اد کنی
را باtrackChanges باشا می وانیPUT اگر ریکوئسات برابر،اولین نکته این اسات که •
ذخیر میکنی ؛HttpContext در دیتابیس باشا آ را درEntity اگر،نکته دوم این که •
احتیااج داری و نمیخواهی دو باار از دیتاابیسEntity چو ماا در اکشااان متا هاا باه
. کوئری بگیری
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 ماام مراحلی کاه در بااال انجاام دادی برای
.)برخی اختالفات در اجرای فیلتر
. ایجاد کنی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;
}
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;
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.");
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();
}
232
Paging, Filtering, Searching : فصل نهم
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
برای درخواستتت شتتماره صتتفحه و تعداد کارمندان ،باید از پارامترهای کوئری •
EmployeeParametersنیاز داریم.
235
public class EmployeeParameters : RequestParameters
{
}
:بررسی کد
. البته اال لارامتری ن ارد و این لارامترها در طول کار اضافه خواه ش
راAPI داری کاهmaxPageSize باه ناامConstant یاکRequestFeatures در کالس •
. را اضافه کنیEmployeeParameters
using Entities.RequestFeatures;
، لس همانن ک لایین. بای منطق ریپازیتوری را لیاد سااازی کنی،لس از این لییرات •
: 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();
}
. را لییر دهی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 employeesDto =
_mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
return Ok(employeesDto);
}
239
همانطور که میبینی در صا وه اول 3 ،کارمن را درخواسات کردی و نتیجه باال بازگشات داد
ش .
اگر این هما چیوی است که شما ه ب ست آورد ای لس در مسیر صویح هستی .
ارتقای Paging
List اا اینجاا ،فقط یاک Listباه کالینات برمیگردانا ی ؛ اماا ممکن اسااات بخواهیا باه جاای یاک
ساد ،یک PagedListبه کالینت برگردانی .
Skip/Take ارثبری میکن و ما می وانی منطق List PagedListکالسای اسات که ،از کالس
را ه ،در آ داشته باشی .
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);
}
241
:بررسی کد
: 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();
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);
}
}
. را لییر دهیGetEmployeesForCompanyAsync خب حاال بای ب نه اکشنمت
return NotFound();
}
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مکانیویمی اسات که ،نتای را با وجه به برخی معیارها واکشای میکن و ما می وانی
به اطالعات دقیق ر برسی .
فیلترهاا را می وا باا وجاه باه نو لرالر ی ،رن عا دی ،موا ود ااریخ و یاا هر چیو دیگری
عری شاا نوشااات .در هنگام اجرای فیلتر ،همیشاااه مو ود به مجموعه گوینههای از لی
هستی که می وانی در ریکوئست خود آ ها را نظی کنی .
یاک وب ساااایات فروش ا ومبیال را در نظر بگیریا .هنگاام فیلتر کرد ا ومبیالهاای مورد نظر
خود ،می وانی گوینههای لایین را انتخاب کنی :
یک Radio Buttonبرای مشخش کرد اینکه ماشین ج ی است یا خیر. •
و.. •
بنابراین ریکوئست برای این وب سایت ،می وان چیوی شبیه به این است.
246
https://bestcarswebsite.com/sale?manufacturer=ford&model=expedition&
state=used&city=washington&price_from=30000&price_to=50000
: بررسی کد
. آ را ص ر عری کنی
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();
}
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);
}
249
https://localhost:5001/api/Companies/c9d4c053-49b6-410c-bc78-
2d54a9991870/employees?maxAge=26
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معموالً یاک ورودی داری کاه از آ برای جساااتجوی هر چیوی در وب •
251
اسات اد از آ عبارت برای یافتن نتیجهای اسات که با آ مطابقت داشاته باشا .به طور
مثال:
در وب سااایت ا ومبیل ،ما از قساامت جسااتجو ،م ل ا ومبیل را ایپ میکنی و مام
نتای مربوط به آ ا ومبیل به ما بازگشت داد میشود.
• ما می وانی لیاد ساااازی Searchingرا طوری انجام دهی که اگر کاربر عبارت را ب و
کو یشاان جسااتجو کرد ،مام نتای مربوط به عبارت برگردان شااود .و اگر عبارت را در
کو یشن نوشت ،بای دقیقا نتای با عبارت مطابقت داشته باش .
اسات اد از جساتجو به معنای این نیسات که ،ما نمی وانی از Filteringاسات اد کنی .اسات اد از
Filteringو جساتجو در کنار ه ،کامالً منطقی اسات بنابراین بای هنگام ک نویسای این موضاو
را در نظر بگیری .
در این جساتجو میخواهی مام کساانی که نامشاا 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; }
}
فول ری با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));
}
. بود اکساتنشان مت ی نوشاتی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);
}
254
فصل دهم Sorting :و Data Shaping
نتای ،به شااکلی باشاا که ما میخواهی .به طور ه ف Sortingاین اساات که ر یب نمای
مثال :
برای انجام این کار ،ریکوئست ما بای چیوی شبیه به این باش .
https://localhost:5001/api/companies/companyId/employees?or
derBy=name,age desc
APIباایا ماام لاارامترهاا را در نظر بگیرد و نتاای را طبق آ مراب کنا .در ریکوئسااات بااال باای
نتای را براسااس نام مر ب ،ساپس اگر کارمن انی با نام مشابه وجود داشته باشن بای نتای را بر
اساس لرالر ی سن مر ب کن .
برای لیاد ساااازی این قابلیت ،بای همانن ساااایر ویژگیهایی که اکنو انجام دادی ،ابت ا در
کالس 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;
}
}
. باشا حتی اگر در ریکوئسات مشاخش نشا، میخواهی نتای خود را بر اسااس نام مر ب کنی
را لییر دهی اا در صاااور ی کاه حتی ناامی ه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; }
}
نیااز باه لکی، دایناامیاک در کوئریOrderBy وجاه داشاااتاه بااشااایا کاه برای اضاااافاه کرد
. نصب کنیRepository داری لس آ را در لروهSystem.Linq.Dynamic.Core
Install-Package System.Linq.Dynamic.Core -Version 1.2.7 -ProjectName
Repository
257
. اضافه کنیRepositoryEmployeeExtensions لایین را در کالسNamespace حاال
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));
258
{
if (string.IsNullOrWhiteSpace(orderByQueryString))
return employees.OrderBy(e => e.Name);
pi.Name.Equals(propertyFromQueryName,
StringComparison.InvariantCultureIgnoreCase));
if (objectProperty == null)
continue;
orderQueryBuilder.Append($"{objectProperty.Name.ToString(
)} {direction}, ");
}
if (string.IsNullOrWhiteSpace(orderQuery))
return employees.OrderBy(e => e.Name);
return employees.OrderBy(orderQuery);
}
}
259
این متا خیلی کاارهاا را انجاام میدها لس بگاذاریا قا م باه قا م آ را بررسااای کنی اا ببینی
دقیقاً چه کاری انجام داد ای .
بررسی کد :
این متا دو آرگوماا میگیرد ،یکی برای لیسااات کاارمنا ا و دیگری برای ریکوئسااات •
Orderingاست.
باا چاک کرد آرگوماا orderByQueryStringشااارو کنی .اگر این آرگوماا nullیاا •
در غیر این صاورت orderByQueryString ،را splitکرد ا فیل های موردنظر را ب سات •
آوری .
;]var propertyFromQueryName = param.Split(" ")[0
بااارای اینکاااه لیساااتی از لرالر ااایهاااای کاااالس Employeeرا ب سااات آوریا ا از •
Reflectionاست اد کردی .با ایان لیسات مای اوا مطمالن شا کاه فیلا دریافات
از ،Query Stringحتما در کالس Employeeوجود داشته باش . ش
| var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public
;)BindingFlags.Instance
حااال در یاک حلقاه Foreachاک اک لاارامترهاا را بررسااای میکنی اا ببینی موتوای •
pi.Name.Equals(propertyFromQueryName,
;))StringComparison.InvariantCultureIgnoreCase
اگر لرالر ی لی ا نکردی ،مرحله رفتن به لارامتر بع ی را بیخیال میشوی . •
260
آ را برمیگردانی و سپس برای صمی گیری در مورد مر ب، اگر لرالر ی را لی ا کنی •
orderQueryBuilder.Append($"{objectProperty.Name.ToString()}
{direction}, ");
بای کاماهای اضافی را حذف و، بررسی کردیForeach حاال که همهی لرالر یها را با •
if (string.IsNullOrWhiteSpace(orderQuery))
return employees.OrderBy(e => e.Name);
. در لایا می وانی کوئری خود را مر ب کنی •
return employees.OrderBy(orderQuery);
“Name ascending, DateOfBirth عبارت بای شاملorderQuery در این مرحله متلیر •
به صورت صعودی و Name این عبارت نتای ما را ابت ا براساس. باش descending”
!!نکته
. میتوانید به صورت پایین عمل کنید، این عبارتLINQ برای زدن کوئری
employees.OrderBy(e => e.Name).ThenByDescending(o => o.Age);
این کد ترفند کوچکی برای مرتبسازی کوئری است و برای زمانیکه نمیدانید چطور مرتب سازی
.کنید پرکاربرد است
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به خوبی کار کن .
263
_pageSize = (value > maxPageSize) ? maxPageSize : value;
}
}
Query لارامتر را اضااافه کردی و حاال می وانی از این لرالر ی به عنوا یکFields ما لرالر ی
. است اد کنیString
namespace Contracts.IServices
{
public interface IDataShaper<T>
{
IEnumerable<ExpandoObject> ShapeData(IEnumerable<T> entities,
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);
}
if (property == null)
continue;
requiredProperties.Add(property);
}
}
else
{
requiredProperties = Properties.ToList();
}
return requiredProperties;
}
shapedData.Add(shapedObject);
}
return shapedData;
}
266
shapedObject.TryAdd(property.Name, objectPropertyValue);
}
return shapedObject;
}
}
}
. لس بیایی آ را بشکنی و با ه بررسی کنی،ک زیادی در اینجا وجود دارد
: بررسی کد
نو این لرالر ی آرایاه. وجود داردProperties باه ناامpublic در این کالس یاک لرالر ی •
. کنترلر برمیگردان
private IEnumerable<PropertyInfo> GetRequiredProperties(string
fieldsString)
{
var requiredProperties = new List<PropertyInfo>();
if (!string.IsNullOrWhiteSpace(fieldsString))
{
var fields = fieldsString.Split(',',
StringSplitOptions.RemoveEmptyEntries);
if (property == null)
continue;
requiredProperties.Add(property);
}
}
else
{
requiredProperties = Properties.ToList();
}
return requiredProperties;
}
، خالی نباشاfieldsString اگر.همانطور که میبینی چیو خاصای در این ک وجود ن ارد •
مطابقت دارن یاEntity و سااپس چک میکنی که آیا فیل ها با لرالر یهایSplit آ را
.خیر
. آنها را به لیست لرالر یهای مورد نیاز اضافه میکنی، اگر مطابق باشن •
268
. انجام میدهEntity این عملیات را برای یکFetchDataForEntity مت •
shapedObject.TryAdd(property.Name, objectPropertyValue);
}
return shapedObject;
}
را اجرا وForeach یک حلقهrequiredProperties همانطور که میبینی با اسااات اد از •
اساات اد کنی و به اینTryAdd از مت،بنابراین می وانی برای اضااافه کرد لرالر یها
. به آبجکت اضافه شون،صورت به طور خودکار لرالر یهای موردنیاز
اسااات باا این ااوت کاه برایFetchDataForEntity شااابیاه، ه FetchData متا •
shapedData.Add(shapedObject);
}
return shapedData;
}
. رجیستر کنیConfigureServices را در متDataShaper اجاز دهی کالس،برای ادامه
269
services.AddScoped <IDataShaper<EmployeeDto>, DataShaper<EmployeeDto>>();
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();
}
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 :
لییر نام فیل ها ،لرالر یها یا Resource URIها باش . •
لییر کن ،ک سامت کالینت به مشاکل برمیخورد و در نتیجه APIبا شاکسات روبرو API وقتی
میشود.
هیچ راهنماایی وجود نا ارد کاه کا ام روش بهتر از بااقی روشهااسااات ،بناابراین ماا روشهاای
مختل را معرفی میکنی و شما با وجه به نیاز ا می وانی هر ک ام را انتخاب نمایی .
لس از نصااب لکی باال ،بای ساارویس Versioningرا رجیسااتر کنی لس به یک مت ج ی در
کالس ServiceExtensionsنیاز داری .
273
. و سپس مت لایین را بنویسی
public static void ConfigureVersioning(this IServiceCollection services)
{
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.DefaultApiVersion = new ApiVersion(1, 0);
});
}
را در متا API Versioning باایا،AddApiVersioning خاب حااال باا اسااات ااد از متا
. اضافه کنیConfigureService
services.ConfigureVersioning();
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;
[HttpGet]
public async Task<IActionResult> GetCompaniesAsync()
{
var companies = await
_repository.Company.GetAllCompaniesAsync(trackChanges:
false);
return Ok(companies);
}
}
}
: بررسی کد
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ص ا زد ش
برای این کار بای ا ربیوت Routeباالی کنترلر را به صورت لایین لییر دهی .
])"[ApiVersion("2.0
])"[Route("api/{v:apiversion}/companies
][ApiController
public class CompaniesV2Controller : ControllerBase
الگوی 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");
});
}
278
Version منسوخ کردن
بای از لرالر ی، منساود شاود اما نخواهی آ را به طور کامل حذف کنی2.0 اگر بخواهی وره
. است اد کنیDeprecated
[ApiVersion("2.0", Deprecated = true)]
[Route("api/companies")]
[ApiController]
public class CompaniesV2Controller : ControllerBase
!!نکته
اگر ورژنهای زیادی از یک کنترل داشتتته باشتتیم و برواهیم تعدادی از این کنترلرها را منستتوخ
. میتوان از پیکربندی اختصاصی استفاده کرد،کنیم
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));
});
}
280
Rate Limiting وCache : فصل دوازدهم
چیست؟Expiration Model ➢
چیست؟Validation Model ➢
Validation وExpiration ➢ پیکربندی هدرهای
چیست؟Rate Limiting ➢
Rate-Limit ➢ پیادهسازی
Cachingچیست؟
به ذخیر داد ها در قسامتی از حافظه به نام Cacheرا Caching ،میگوین .با Cachingسارعت
دسترسی به داد ها زیاد ر از حالت عادی است.
Cachingمی وان کی یت و لرفورمنس اللیکیشان را باال ر ببرد و ه ف اصالی آ این اسات که
در بسایاری از موارد ،نیاز به ارساال ریکوئسات به سامت APIو یا ارساال Responseکامل ن اشاته
باشی .
ع اد ریکوئسااتهای ارسااالی Cache ،از مکانیوم انقضااا اساات اد میکن .این کار برای کاه
یاب . باعث میشود رفت و برگشت درو شبکهای کاه
Response عالو بر این Caching ،از مکانیوم اعتبارساانجی اساات اد میکن ا نیاز به ارسااال
کامل را از بین ببرد.
لس از ذخیر ریسپانس ،اگر کالینت دوبار هما ریسپانس را درخواست کن ،بای ریسپانس از
حافظه Cacheارائه شود.
انواع Cache
سه نوع Cacheوجود دارد :
Clientبر روی ( Clientمرورگر) زنا گی میکنا .لس چو Cache : Client Cache •
282
تفاوت بین Private cacheو : Shared Cache
در Private cacheاگر لن کالینت برای اولین بار ریساپانس مشاابه درخواسات کنن ،در •
این صاورت هر ریساپانس از سامت APIارائه میشاود (و نه از )Cacheاما اگر آنها دوبار
هما ریساپانس را بخواهن ،این بار بای ریساپانس از Cacheباشا (البته اگر انقضاای آ
باش ). مام نش
در مورد Shared Cacheاینگونه نیساات .در این نو ، Cacheابت ا ریسااپانس کالینت •
اول ذخیر میشااود و سااپس چهار کالینت دیگر ،در صااورت درخواساات مشااابه بای
را دریافت کنن . ریسپانس Cacheش
از Caching قبل از افوود ه رهای ،Cacheبای Postmanرا باز کنی و نظیمات لشااتیبانی
را لییر دهی .
. ،Headersبای سربرگ Send no-cacheرا OFFکنی در سربرگ Generalدر بخ
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
284
cache-store اضافه کردن
اضافه کرد یک اکستنشن مت در، اولین کاری که بای انجام دهی،cache-store برای افوود
. استServiceExtensions کالس
public static void ConfigureResponseCaching(this IServiceCollection
services) => services.AddResponseCaching();
رجیساتر میکنی و حاال بای این اکساتنشان مت راIoC container را درCaching ما ریساپانس
. ص ا بونیConfigureServices در مت
services.ConfigureResponseCaching();
Cache- این ریکوئسات ه ر. کرد و ریکوئسات لایین را ارساال کنیStart حاال برنامه خود را
. را ایجاد میکنControl
https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce2
: بار اول
: بار دوم
285
قبل از اینکه 60ثانیه بگذرد ،دوبار هما ریکوئسات را ارساال کنی و ساپس ه رها را بررسای
نمایی .
مییاب .؛ سپس بع اگر در عرض 60ثانیه ،چن ین ریکوئست ارسال کنی ،لرالر ی Ageافوای
Age از لایا دور انقضااا ،ریسااپانس از APIارسااال و دوبار Cacheخواه ش ا بنابراین ه ر
ایجاد نمیشود.
Cache Profiles عالو بر این ،می وا برای اعمال قوانین یکسا در Resourceهای مختل ،از
است اد کرد.
در صااویر باال لرالر یهای زیادی هساات که اگر همه این لرالر یها را در باالی اکشاان مت یا
کنترلر قرار دهی ،منجر به ناخوانایی ک خواه شاا .بنابراین برای واکشااای این لیکربن یها،
می وانی از CacheProfilesاست اد کنی .
286
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
config.CacheProfiles.Add("120SecondsDuration", new CacheProfile
{
Duration = 120
});
}).AddNewtonsoftJson()
.AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();
لرالر یهای دیگر را نیو اضاافه، اما شاما در اینجا می وانی، ما فقط م ت زما را نظی کردی
. کنی
بر روی ع دAge ب رساتی بای لرالر یGetCompanyAsync حاال اگر ریکوئسات به اکشانمت
. باش60
https://localhost:5001/api/companies/C9D4C053 -49B6-410C-BC78-2D54A9991870
287
ب رساتی بای لرالر ی Ageبر روی ع د GetCompaniesAsync و اگر Requestبه اکشانمت
120باش .
https://localhost:5001/api/companies
Expiration Modelچیست؟
Expiration Modelبه سارور این امکا را میده که مشاخش کن ،ریساپانس منقضای شا
یا خیر.
288
همانطور که میبینی کالینت ریکوئستی را برای دریافت companiesارسال میکن . •
هیچ وره cacheشا ای از این ریساپانس وجود ن ارد بنابراین ریکوئسات به APIارساال •
میشود.
ساااپس APIاین ریساااپانس را همرا با ه ر 600 Cache-Controlثانیه انقضاااا ،برای •
ه ر Cache-Controlاست اد میشود.
اگر بع از دو دقیقه ریکوئست مشابهی درخواست شود صویر زیر ا اق میافت . •
همانطور که میببینی ،ریسااپانس ذخیر شاا ،با یک ه ر 120 Ageثانیه برگردان •
میشود.
اگر این یک Private Cacheباشا ،لس ریساپانس در مرورگر ذخیر خواه شا بنابراین •
289
همانطور که میبینی در Shared Cacheریساپانس از cacheارائه میشاود و دو دقیقه دیگر به
ه ر Ageاضافه خواه ش .
ما با نوو کار Expiration Modelآشنا ش ی حاال بیایی Validation Modelرا بررسی کنی .
Validation Modelچیست؟
Validation Modelبررس ای میکن که ریسااپانس درو ، Cacheهنوز قابل اساات اد اساات یا
Shared Cached خیر؟ فرض کنی کاه یاک ریساااپاانس GetCompanyباه ما ت 30دقیقاه در
گذاشااته شا اساات .اگر کسای بع از لن دقیقه ،آ شاارکت را آل یت کن ،کالینت بای 25
Cache دقیقه باقیمان ،ریساپانس اشاتبا دریافت کن ؛ چو هیچ اعتبارسانجی روی این داد
وجود ن ارد.
وصایه میکن HTTP برای جلوگیری از این ا اق ،بای از اعتبارسانجی اسات اد کنی .اساتان ارد
در صورت امکا ،به صورت رکیبی از اعتبارسنجیهای LastModifiedو ETagاست اد کنی .
نظی Last-Modified ( Ifکاه باه مقا ار Modified-From نظی شااا ) و Etag مقا ار
ش ) به سمت APIبرمیگردان .
Response اگر این Requestبا اعتبارسنجیها بررسی شود ،دیگر نیازی به ایجاد دوبار •
از سمت APIنیست بنابراین 304 Not Modified statusارسال میکن .
بع از آ Response ،به طور منظ از Cacheارائه میشود. •
البته اگر این بررسی انجام نشود ،لس بای Responseج ی ایجاد شود. •
این صااویر ما را به این نتیجه میرسااان که در Cache Sharedاگر ریسااپانس لییر •
پیادهسازی اعتبارسنجی
ابت ا بای در لروه اصاالی لکی 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();
60 فرض روی انقضاااای لی. ما مام ه رهای موردنیاز را ایجاد کردی، هماانطور که میبینی
.است ثانیه نظی ش
292
Validation وExpiration پیکربندی هدرهای
االاایاایاارا اای در ماات ا بااای ا Validation وExpiration باارای لاایااکاارباان ا ی ه ا رهااای
. دهیConfigureHttpCacheHeaders
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)
294
و اگر Requestلایین را ب رستی نتیجه مت اوت خواه بود.
https://localhost:5001/api/companies/3d490a70 -94ce-4d15-9494-5248280c2ce3
Rate Limitingچیست؟
،Rate Limitingرافیک ورودی وب سااایت را م یریت میکن .این قابلیت به ما امکا میده
ا از APIدر برابر ع اد ریکوئستهای زیاد ،که باعث از بین رفتن لرفورمنس میشون موافظت
کنی .
295
ه ر ریکوئستهای مجاز شامل اطالعات زیر هستن :
میشااود و ه ر از ح مجاز شااود 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اضافه کنی .
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitConfiguration,
RateLimitConfiguration>();
}
: بررسی کد
297
services.AddMemoryCache();
services.ConfigureRateLimitingOptions();
services.AddHttpContextAccessor();
298
429 Tooرا همرا باا اه ر Retry-Afterدریاافات کردی . Many Requests هماانطور کاه میبینیا
اگر bodyرا بررسی کنی بای نتیجه لایین را ببینی .
299
Identity وJWT : فصل سیزدهم
اگر به رون کار آشانا نباشای ،لیاد ساازی این ویژگی می وان کار ساختی باشا و زما زیادی از
شما بگیرد.
ما میخواهی گام به گام نوو ی ادغام Identityدر لروه موجود و ساااپس نوو لیاد ساااازی
وضیح دهی . Authorization JWTرا برای عملیات Authenticationو
ASP.NET Identityچیست؟
این روزها امنیت برنامههای وت وب ،یکی از داف رین موضااوعات دنیای وب اساات .هر ه ته
و حملههای سااایبری به سااایتهای مختل به گوش میرس ا .شااای خبرهایی از هک ش ا
به نظر برساا .اما نگرا نباشااای ،با دانساااتن برخی این جمالت کمی ناامی کنن شااانی
. موضوعات می وانی از بسیاری از این حمالت جلوگیری کنی
در این فصل میخواهی به نوو ی ح اظت از اللیکیشن نگاهی بین ازی .
،ASP.NETقاابلیات ایجااد برنااماههاای دایناامیاک اسااات .این Core یکی از مهمترین ویژگیهاای
بخ هایی را به کاربر میده که اجاز دساترسای داشاته باشا .با این قابلیت ،نها امکا دی
حساب اللیکیشن ما می وان برای کاربرا مختل س ارشی شود.
بسایاری از اللیکیشانها ،م هومی به نام حسااب کاربری دارن که با آ می وانی وارد نرمافوار
شاوی و جربه کاربری ،مت اوت داشاته باشای .با داشاتن قابلیت حسااب کاربری در اللیکیشان،
امکانات مت او ی ارائه دهی . می وانی بسته به شخش الگین ش
301
SQLلیکربنا ی شاااود اا ناام کااربر ،کلماه عبور و اطالعاات لروفاایال را ذخیر Server دیتاابیس
نمای .
بیایید کمی بیشتر به این موضوع بپردازیم و این قابلیت فو العاده را به این اپلیکیشن
اضافه نماییم.
Authenticationو Authorizationچیست؟
زمانیکه میخواهی یک کاربر را در اللیکیشن ثبت نمایی ،بای به دو جنبه مهم وجه کنی :
و Authorization به عبارت دیگر Authentication ،مشخش میکن چه کسی وارد سیست ش
. میگوی ،به چه چیوهایی بای دسترسی داشته باش
باشا . سااد رین حالت برای Authorizationاین اسات که ح اقل ،کاربر بای Authenticateشا
این کار وسااط اضااافه کرد ا ربیوت ] [Authorizeبه باالی اکشاانمت ها یا Controllerها انجام
میشود.
نکته!!
اتربیوت ] [Authorizeرا میتوان در هر جای برنامه به کار برد ،اما استتفاده از آن در باالیکنترلرها و
اکشنمتدها به این دلیل است که کنترل کنیم چه کاربری به چه اکشنی دسترسی دارد.
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 •
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>
304
public DbSet<Employee> Employees { get; set; }
:بررسی کد
. لیکربن ی کردی
AddIdentityCore لیکربنا ی را در متا هماانطور کاه میبینیا لاارامترهاای مختل •
. است اد کردی
token را همرا بااEntityFrameworkStores ایجااد وIdentityBuilder در لاایاا یاک •
306
Add-Migration CreatingIdentityTables -Project CompanyEmployee.API
در، برای انجااام این کااار. وارد کنی 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>
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);
}
}
. لایین را اجرا و دیتابیس را آل یت کنیMigration سپس
Add-Migration AddedRolesToDb -Project CompanyEmployee.API
Update-Database
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< به جو بخ •
ماام نیاازمنا یهاای ماا را UserManager در اینجاا نیاازی باه ریپاازیتوری نا اری چو •
. فراه میکن
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; }
}
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
}
}
}
بررسی کد :
ابت ا برای اعتبارسنجی م ل ،یک اکشن فیلتر را باالی اکشنمت خود قرار دادی . •
حاال برای ایجاد کاربر در دیتابیس ،مت CreateAsyncرا ص ا میزنی . •
اگر عمل ایجاد کاربر موفقیت آمیو باشا ،کاربر در دیتابیس ذخیر میشاود .در غیر این •
در لایا اگر کاربری ایجاد شاااود بای آ را به Roleهای خود متصااال کنی و ساااپس •
نکته!!
313
مت،ServiceExtensions (کالس30 به3 را ازRate Limit بهتر اسااات مق ار،قبل از سااات
.نشود دهی ا ست لیچی ) افوایConfigureRateLimitingOptions
{
"firstname":"Zahra",
"lastname":"Bayat",
"username":"ZahraBayat",
"password":"P@ssw0rd12345",
"email":"[email protected]",
"phonenumber":"0123456789",
"roles":[
"Manager"
]
}
.است اضافه شRole این یعنی کاربر ایجاد و به. گرفتی201 همانطور که میبینی
315
در پایان اگر کاربری با همان نام کاربری و ایمیل ایجاد کنیم.
عالی شد همه چیز طبق برنامه کار میکند .حاال میتوانیم به سراغ JWTبرویم.
فرآیند Login
Login و Authorizationبیایی نگاهی اجمالی به فرآین Authentication قبل از لیاد سازی
داشته باشی .
اللیکیشان یک فرم ورود به سایسات دارد که در آ ،کاربر نام کاربری و رمو عبور خود را وارد و
دکماه ورود را میزن .لس از زد دکماه ورود ،کالینات (به عنوان مثال :مرورگر وب) داد های
کاربر را به APIسرور میفرست .
316
وقتی سرور اعتبار کاربر را بررسی و أیی کرد ،بای یک JWT Tokenبرای کالینت ارسال کن .
JWTچیست؟
(مثل نام JWTیک ش ای JavaScriptاساات که حاوی برخی از ا ربیوتهای کاربر الگین ش ا
کاربریRole ،های کاربر یا برخی اطالعات دیگر) میباشا .این شای به صاورت ایمن ،داد ها را
بین دو طرف منتقل میکن .
که هر قسمت با رنگ مت اوت نشا داد همانطور که میبینی JWTاز سه قسمت شکیل ش
است. ش
با فرمت : Headerاولین قسامت ،JWTه ر اسات که یک شای JSONرموگذاری شا •
Base64میباش .این قسمت اطالعا ی مانن نو tokenو نام الگوریت را در خود دارد.
{
"alg": "HS256",
""typ": "JWT
}
با فرمت Base64میباشا ا Payload .حاوی ا ربیوتهایی در مورد کاربر الگین شا ا
است .به عنوان مثال : ش
317
می وان حاوی Idکاربر Subject ،کاربر و اطالعا ی در مورد اینکه آیا کاربر Adminاسات
یا خیر؟ باش .
: 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 با است اد از. کن
320
IssuerSigningKey = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
});
}
: بررسی کد
. استAuthentication
وJwtBearerDefaults.AuthenticationScheme باااایا ا Authentication بااارای •
. را مشخش کنیChallengeScheme
.است مورد نیاز هست آورد شJWT در اینجا برخی لارامترهایی که هنگام أیی
در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;
!!نکته
نگران نباشتید یکبار ویژوال استتدیو.شتاید بعد از اجرای این ریکوئستت به اکستپشتن برخورد کنید
.را ببندید و دوباره باز کنید
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 میخواهی منطق لیچی ای برای
. است این اکشنها را در سرویس دیگری واکشی کنی
namespace Contracts.IServices
{
public interface IAuthenticationManager
{
Task<bool> ValidateUserAsync(UserForAuthenticationDto
userForAuth);
Task<string> CreateTokenAsync();
}
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 claims;
}
DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings.GetSe
ction("expires").Value)),
signingCredentials: signingCredentials
);
return tokenOptions;
}
}
: بررسی کد
بررسا ای میکنی کاربر در دیتابیس وجود دارد و آیا رمو ورود باValidateUser در مت •
علق دارد را هاایی کاه کااربر باه آRole هاا وClaim لیساااتی از GetClaims متا •
. برمیگردان
باا ماام گویناههاای موردJwtSecurityToken یاک شااایGenerateTokenOptions متا •
"JwtSettings": {
"validIssuer": "MicrodevAPI",
"validAudience": "https://localhost:5001",
"expires": 5
},
"AllowedHosts": "*"
}
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;
[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
}
[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
همانطور که میبینی یک وکن ولی ش .حاال بیایی با لسورد نامعتبر امتوا کنی .
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()
{
"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
➢ Swaggerچیست؟
➢ تنظیمات Swagger
داکیومنت APIچیست؟
API برنااماهنویساااانی کاه APIی ماا را مصااارف میکننا ،نیااز دارنا کاه با اننا چطور باایا از آ
است اد کنن .لس اینجا جایی است که داکیومنت APIوارد بازی میشود.
داکیومنت APIدساتورالعملهایی در مورد نوو اسات اد از APIرا ارائه میده بنابراین می وا
آ را به عنوا یک کتابچه راهنمای ،مختصاار در نظر گرفت که شااامل مام اطالعات مورد نیاز
برای کار با APIاست.
داشاتن داکیومنت مناساب باعث میشاود ا ساایر برنامهنویساا بتوانن APIهای ما را با کارهای
خود ،ادغام و وساعه دهن .همچنین این کار باعث میشاود ا نگه اری و لشاتیبانی از APIها،
ساد ر شود.
Swaggerچیست؟
Swaggerیاک ابوار برای وصااای APIاسااات کاه باه ماا امکاا میدها اا با و بررسااای یاک
سیست API ،های آ را ببینی .
Swaggerباه برنااماهنویس کماک میکنا اا باا سااارعات و دقات ،برای APIهاای خود داکیومنات
ولی کن .
Web API ماا می وانی باا اسااات ااد از لکی ،Swashbuckleباه راحتی Swaggerرا در لروه
اضافه کنی .
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
: 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");
});
: 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
حااال اللیکیشااان را اجرا کنیا و در یاک مرورگر. این ماام کااری اسااات کاه باایا انجاام میدادی
. آدرس لایین را وارد کنی
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داشته باشی .
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ابت ا بای وکن بگیری .لس اکشان api/authentication/loginرا باز
را کپی کنی . و بع از وارد کرد Userو ،Passwordوکن دریافت ش
344
خب حاال روی دکمه Authorizationریکوئست api/companies/کلیک کنی .
این وکن را در مقابل Bearerلیست و روی Authorizeکلیک کنی . در کادر باز ش
لس از کلیک بر روی دکمه Authorizeبر روی دکمه Closeکلیک کنی .
345
. را ست کنیAPI حاال می وانی این
Swagger تنظیمات
دارد که میخواه آ را باUI گوینههایی برای وساعه داکیومنت و سا ارشای کردSwagger
. ه بررسی کنی
. حاال بیایی برنامه را یک بار دیگر اجرا کنی و Swagger UIرا بررسی کنی
برای فعال کرد XML commentها بای مراحل زیر را انجام دهی .
گوینهی Propertiesرا انتخاب بر روی لروه اصالی راسات کلیک کنی و از منو باز شا •
نمایی .
در ب Buildچک باکس XML documentation fileرا یک بونی . •
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>()
}
});
});
}
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