قسمت قبلی رو در asp dotnet core (قسمت صفر) ببینید.
اگر پیشنهادی برای اصلاح پست دارید، میتونید از طریق دکمهی "پیشنهاد اصلاح متن" وارد ریپازیتوری وبلاگ در github بشید و تغییرات مورد نظرتون رو پیشنهاد بدید.
سرور
اپهای داتنتکور برای گرفتن درخواستها (request) و ارسال پاسخ (response) به سرور (server) نیاز دارن. سرورِ پیشفرض داتنتکور، kestrel است که میتونه در محیط توسعه (development) و واقعی (production) استفاده بشه، اما معمولا از kestrel به عنوان application server و از reverse proxy serverها مثل iis و nginx در محیط واقعی استفاده میکنیم.
در حالتی که فقط kestrel رو داشته باشیم، http requestها از شبکه محلی (local network) یا اینترنت به kestrel میرسن و اون هم درخواستها رو به شکل آبجکتهای httpcontext به application ارسال میکنه. application پردازشهای لازم رو انجام میده و جواب رو به kestrel میده تا اون، جواب رو به client برسونه. یک مسیرِ رفت و برگشتی.
اما kestrel هنوز خیلی از قابلیتهای مورد نیازِ این روزهای وب رو نداره، بنابراین لازمه از reverse proxy serverهایی مثل iis و nginx و… استفاده بشه تا امکاناتی مثل load balancing و url rewriting و caching و خیلی قابلیتهای دیگه رو در اختیار ما بذارن. بنابراین در محیط واقعی، یک ایستگاه قبل از kestrel اضافه میشه. اما برای محیط توسعه چطور؟ برای شبیهسازی قابلیتهای reverse proxy serverها در محیط توسعه، میتونیم از iis express استفاده کنیم که عملکرد iis رو شبیهسازی میکنه.
kestrel در حالت پیشفرض فعاله و اگر برنامه اجرا میشه و یک url باز میشه، یعنی داره درست کار میکنه. اما iis express چطور؟ لازمه که وارد فایل launchsettings.json (در فولدر properties) بشیم. فایل launchsettings.json یک فایل با فرمت json است که کانفیکهای پروژه در اون قرار داره. profileها مجموعهای از تنظیمات هستن که به یک سرور خاص اجازه میدن که برنامهمون رو اجرا کنه. مثلا اگه نام برنامه FirstApp باشه، دو پروفایل با نامهای FirstApp (که میشه تغییرش داد) و IIS Express خواهیم داشت. commandName، سروری که قراره برنامه روش اجرا بشه رو مشخص میکنه، مقدار Project به معنیِ استفاده از کسترل است. هنگام اجرای برنامه میشه انتخاب کرد که با کدوم پروفایل اجرا بشه. پروفایل پیشفرض، FirstApp است که kestrel رو برای اجرا انتخاب میکنه و ما هم با همین پروفایل پیش میریم.
http
حضور ما در وب، به کمک http امکانپذیر شده. http، مجموعه قوانینیه که برای ارسال درخواست از client به server و از server به client طراحی شده. به عنوان توسعهدهندهی وب نیازی به جزئیات کارکردش نداریم ولی لازمه کاربردهاش رو بلد باشیم. https هم همون http است که لایه امنیت (security) بهش اضافه شده.
درخواستها (request) رو میشه در مرورگر دید. کافیه در صفحهای که هستیم کلیک راست کنیم، inspect رو انتخاب کنیم، به تب network بریم و با زدن کلید F5 صفحه رو رفرش کنیم. هر http response شامل بخشهای Start Line و Response Headers و Response Body است. Start Line شامل مواردی مثل http version و status code است. یکی از status codeهای معروف 404 (به معنای پیدا نشدن چیزی که دنبالش بودیم) است. لیست همهی http status codeها اینجاست. Response Header شامل اطلاعاتی مثل date و server و… است که لیست کامل اونها اینجاست.
middleware
middlewareها، اجزایی هستند که در مسیر (pipeline) برنامه قرار میگیرند تا به درخواستها و پاسخها رسیدگی کنند. هر middleware، باید یک کار انجام بده (اصل S از اصول SOLID). middlewareها به شکل زنجیرهای، یکی پس از دیگری و به همون ترتیبی که در برنامه تعریف شده، اجرا میشن. ممکنه در مسیر برنامه، چند middleware داشته باشیم. هر کدوم از اونها، یا از نوع non-terminating هستن و request رو به middleware بعدی پاس میدن یا از نوع terminating هستن و request رو به middleware بعدی پاس نمیدن.
استفاده از middleware
middlewareها رو به دو روش میشه ساخت، نوشتن به صورت request delegate (استفاده از lambda expressionها (برای کارهای ساده)) یا نوشتن class (برای کارهای پیچیده). برای تعریف middlewareهای نوعِ non-terminating از متد Use و برای تعریف middleware نوعِ terminating از متد Run استفاده میکنیم. پارامتر context که در هر دو متد وجود داره، اطلاعات request رو در خودش داره. پارامتر next در واقع delegate بعدی در مسیر رو معرفی میکنه (اگر next رو در بدنهی middleware صدا نزنیم، عملا اون رو به middleware نوعِ terminating تبدیل کردهایم).
|
|
|
|
middlewareها به شکل زنجیرهای و یکی پس از دیگری و به صورت رفت و برگشتی کار میکنند. به همین دلیل در تصویر بالا before logic و after logic داریم. در مسیرِ رفت (از client به server)، before logic اجرا میشه و در مسیرِ برگشت (از server به client)، after logic اجرا میشه. این موضوع در کد زیر و خروجی اون قابل مشاهدهست.
|
|
|
|
علاوه بر متد Use که برای میدلورهای نوعِ non-terminating و متد Run که برای میدلورهای terminating استفاده میشه، متدهای دیگری هم داریم. متدی به نام UseWhen وجود داره که حالت شرطی داره، یعنی شرایطی رو بررسی میکنه و اگر اون شرایط برقرار بود، شاخه جدیدی در pipeline ایجاد میکنه و دوباره به pipeline برمیگرده.
|
|
ساخت middleware دلخواه
گاهی نیاز میشه middlewareی داشته باشیم که شامل چند دستور مختلفه. در این حالت نوشتن همهی دستورات در فایل Program.cs و به شکل lambda expression، خوانایی کد رو کم میکنه. بهتره custom middlewareای بنویسیم که در یک کلاس جدا قرار داره. میدلور دلخواه رو میشه به روشهای convention-based و factory-based ساخت. روش factory-based انعطافپذیری بیشتری داره. در روش convention-based، میدلورها در ابتدای اجرای برنامه ساخته میشن، در حالی که در روش factory-based به ازای هر درخواست ساخته میشن. (اطلاعات بیشتر)
در روش convention-based، کلاس جداگانهای به نام MiddlewareClassName برای middleware مینویسیم.
|
|
و به این شکل ازش استفاده میکنیم:
|
|
در روش factory-based، کلاس جداگانهای به نام MyCustomMiddleware برای middleware مینویسیم که اینترفیس IMiddleware رو پیادهسازی میکنه.
|
|
|
|
به کمک قابلیت extension method، تعریف یک middleware دلخواه به روش factory-based و معرفیِ اون در Program.cs میتونه سادهتر انجام بشه.
|
|
|
|
ترتیب اجرا
در داتنتکور، از قبل میدلورهایی تعریف شده، از طرفی ممکنه در پروژه نیاز به ساخت میدلورهای دلخواه هم داشته باشیم. با توجه به اینکه ترتیبِ اجرای میدلورها اهمیت داره، مایکروسافت ترتیب زیر رو پیشنهاد میکنه.
|
|
routing
به فرایندی که در طیِ اون، HTTP requestsها به endpointهای مربوطه ارتباط داده میشن، routing گفته میشه. این کار با بررسی HTTP method و url انجام میشه. مثلا وقتی کاربر آدرس alirsabet.com/home رو وارد کرد، باید بتونیم اون رو به endpoint مربوط به home بفرستیم تا صفحهی موردنظر رو ببینه. زمانی که یک میدلور بر اساس routing اجرا میشه بهش endpoint گفته میشه.
مسیریابی در داتنتکور به وسیلهی میدلورهای UseRouting و UseEndPoints انجام میشه. UseRouting، یک endpoint متناسب با HTTP method و url رو پیدا میکنه و UseEndPoints اون endpoint مناسب که توسط UseRouting پیدا شده رو اجرا میکنه.
|
|
mapها
Mapها چی هستن؟ قوانینی هستند برای اینکه requestهای دریافتی رو به بخشهای مختلف برنامه ارتباط بدن. Map برای همهی HTTP methodها کار میکنه اما میشه با MapGet فقط به درخواستهای Get و با MapPost فقط به درخواستهای Post پاسخ داد. مثلا در این تصویر، Map به همهی درخواستهایی که با “path” شروع میشن رسیدگی میکنه، MapGet به درخواستهای نوعِ Get که با “path” شروع میشن رسیدگی میکنه و MapPost به درخواستهای نوعِ Post که با “path” شروع میشن رسیدگی میکنه.
|
|
route parameters
آدرسهایی که ما رو به محتوای مورد نظرمون میرسونن، یک قسمت ثابت و یک قسمت متغیر دارن، مثلا در یک سیستمِ نمایشِ اطلاعاتِ کارمندان، “employee/profile/ali” ما رو به اطلاعات پروفایل علی و “employee/profile/reza” ما رو به اطلاعات رضا میرسونه. بخشِ “employee/profile” در هر دو مشترکه. به اون قسمتهایی که میتونه تغییر کنه و مقادیر مختلف بگیره، route parameter میگن. route parameterها رو داخل {} میذاریم.
|
|
مثلا در کد زیر اگر آدرس وارد شده به صورت “files/sample.txt” باشه، وارد endpoint اول و اگه به صورت “employee/profile/ali” باشه وارد endpoint دوم میشیم.
|
|
default/optional value
وقتی الگویی برای url تعریف میکنیم، انتظار داریم url وارد شده دقیقا مطابق اون باشه. اما اگر به هر دلیلی مطابق نباشه چی؟ میشه برای پارامترها مقدار پیشفرض (default) گذاشت، یعنی اگر مقدار وارد نشده بود، مقدارِ پیشفرضِ default_value رو به جاش بذاره. مثلا در برنامه، لیستی از کالاها با شناسهی 1 تا 100 داریم. قصد داریم منطق برنامه به شکلی باشه که اگر کاربر در url، شناسهی id رو وارد کرد، اطلاعات کالای با شناسهی id رو ببینه، اما اگر شناسهی کالا رو وارد نکرد، اطلاعات کالای با شناسهی 1 رو ببینه.
|
|
راهِ دیگرِ هندل کردن این موضوع، استفاده از مقدار اختیاری (optional) است. مثلا قصد داریم پارامتر id اختیاری باشه.
|
|
controllerها
در یک پروژهی واقعی نمیشه همهی actionهایی که نیاز است رو در فایل Program وارد کنیم. Controller، کلاسیه که action methodهای مرتبط به هم رو در اون گروهبندی میکنیم. زمانی که request میاد، action methodها یک کارِ خاص رو انجام میدن و response رو برمیگردونن.
کنترلرها معمولا 4 وظیفه اصلی دارن:
- reading requests: خواندن requestها و استخراج مقادیر از اونها، مثل query string و headers و body و cookie و…
- validation: اعتبارسنجی requestها
- invoking models: فراخوانی منطق بیزنس (که بهشون service میگیم)
- preparing response: انتخاب پاسخ (action result) مناسب و ارسال اون به کاربر
کنترلرها به یک یا دو روش زیر شناخته میشن:
- نامِ کلاس، پسوند Controller داشته باشه، مثلا HomeController
- ویژگی (attribute) [Controller] روی اون یا base classش اعمال بشه
|
|
بهتره کنترلرها رو در فولدر Controllers قرار بدیم، یک کار اختیاری اما خوب اینه که کلاس از نوع public باشه و از Microsoft.AspNetCore.Mvc.Controller ارثبری کنه. در کنترلر زیر، اگه requestای از نوع GET به آدرس “/Home/” بیاد (مثلا کاربر در مرورگر آدرس وارد کنه)، وارد متد Index میشیم و اگه “/Home/Welcome/” رو وارد کنه، وارد متد Welcome میشیم.
|
|
اگر کنترلر بالا رو تعریف کرده باشیم و هر یک از آدرسهای بیان شده رو وارد کنیم، برنامه کار نخواهد کرد، به 2 دلیل:
- کلاس کنترلر در داتنتکور، جزو سرویسهاست و باید به عنوان service class به برنامه معرفی بشه (DI)
- قابلیت routing باید برای متدهای کلاس کنترلر تعریف بشه
|
|
در تصویر بالا خط 2 همهی کنترلرهای برنامه رو به عنوان service معرفی میکنه و زمانی که یک endpoint به اونها نیاز داشته باشه، قابل دسترس خواهند بود. خط 4 همهی action methodها رو به عنوان endpoint معرفی میکنه و عملا نیاز نیست تکتک مشخص کنیم که هر endpoint به کجا بره، کاری که در شکل زیر انجام شده و توصیه نمیشه.
|
|
حالا باید قالبی برای urlها تعریف کنیم و به برنامه بدیم تا بدونه urlهایی که میان به چه شکل هستن. روش attribute routing اینه که بالای هر action، دقیقا url رو بیاریم. الان با وارد کردن آدرس “/test/” وارد متد زیر میشیم.
|
|
روش بهتر اینه که قالب کلی رو مشخص کنیم.
انواع resultها
ContentResult
ContentResult میتونه هر نوع پاسخی باشه و بستگی به MIME type (مایمتایپ) داره. MIME type چیه؟ یک شناسهی دو قسمتی که فرمت فایلها رو مشخص میکنه، مثلا text/plain و text/html و application/json و…
|
|
JsonResult
JsonResult پاسخ (مثلا شیای از کلاس Person) رو در قالب فرمت json برمیگردونه.
|
|
FileResult
FileResult محتوای فایل رو به عنوان پاسخ برمیگردونه، مثلا pdf و txt و zip.
VirtualFileResult فایلی که در WebRoot (فولدر wwwroot) با زیرفولدرهاش قرار داره رو برمیگردونه. (قبلا باید app.UseStaticFiles رو صدا زده باشیم)
PhysicalFileResult فایلی که الزاما در WebRoot (فولدر wwwroot) قرار نداره رو برمیگردونه.
|
|
|
|
FileContentResult فایل رو به شکل آرایهای از بایتها برمیگردونه.
|
|
IActionResult
IActionResult، اینترفیسِ والدِ همهی کلاسهای action result مثل ContentResult و JsonResult و… است. بنابراین میتونیم نوع مقدار خروجیِ همهی action methodها رو IActionResult بذاریم.
استفاده از IActionResult برای مشخص کردن نوع خروجیِ action methodها پیشنهاد میشه. فرض کنید قصد داریم در action method زیر، اگر در query string مقدار bookid وارد شده باشه، فایل sample.pdf رو برگردونیم و در غیر این صورت، یک Content حاوی پیام مناسب برگردونیم. این کار به راحتی قابل انجامه.
|
|
StatusCodeResult
معمولا علاقهمندیم در پاسخی که برمیگردونیم، status code رو هم مشخص کنیم تا چک کردنش توسط client ساده باشه. معروفترین status codeها، 200 و 400 و 401 و 404 و 500 هستن. لیست کامل اینجاست.
|
|
|
|
|
|
|
|
Redirect Result
Redirect result، کد 301 یا 302 رو به مرورگر ارسال میکنه، با این هدف که به یک url یا action دیگه redirect بشه. چرا ممکنه به redirect نیاز پیدا کنیم؟ در یک سناریوی ساده فرض کنید قبلا فروشگاه اینترنتی کتاب داشتهاید که در آدرس “/bookstore/” قرار داشت،اما حالا قراره علاوه بر کتاب، کالاهای دیگری هم بفروشید و میخواهید دستهی کتابها رو به “/store/books/” منتقل کنید. این کار به راحتی امکانپذیره، اما اگر کسانی آدرس قبلی رو در مرورگر ذخیره (bookmark) باشند چی؟ اگر آدرس قبلی رو وارد کنند، باید اونها رو به آدرس جدید بفرستیم (redirect کنیم).
RedirectToActionResult بر اساس نام action و نام controller، از یک action method به یک action method دیگر redirect میکند.
|
|
LocalRedirectResult بر اساس یک url مشخص، از یک action method به یک action method دیگر redirect میکند.
|
|
RedirectResult از یک action method به هر url دیگری (داخل یا خارج از برنامه) redirect میکند.
|
|
model binding
در کنترلرها و ویوها به دادههایی که از http requestها میآیند نیاز داریم، بنابراین باید اونها تکبهتک در action methodها دریافت کنیم. کاری تکراری و سخت و احتمالا پر از خطا. Model Binding یکی از ویژگیهای asp.net core است که مقادیر را از http requestها میخواند و آنها را به عنوان ورودی (argument) به action methodها میدهد. در Model Binding، دادهها به ترتیبِ form fields و request body و route data و query string parameters خوانده میشن و این ترتیب مهمه.
[FromQuery] و [FromRoute]
اگر بخواهیم مقادیر رو از query string بخونیم از [FromQuery] و اگر بخواهیم مقادیر رو از route data بخونیم از [FromRoute] استفاده میکنیم (میشه از هر دو استفاده کرد و برای هر پارامتر ورودی چداگانه تعیین کنیم که [FromQuery] باشه یا [FromRoute]). چون ممکنه null باشن از ? استفاده میکنیم.
|
|
|
|
Model کلاسیه که از طریق propertyها، ساختار دادهای که قراره از طریق request بگیریم یا از طریق response بفرستیم رو مشخص میکنه و اون رو به نام POCO (Plain Old CLR Objects) هم میشناسیم.
|
|
form fields
گاهی کاربر یک فرم رو پُر میکنه و با کلیک روی دکمهی “ثبت”، اطلاعات در دیتابیس ذخیره میشه. این فرایند چطور کار میکنه؟ به کمک متد POST در HTTP و form fieldها. دو روش برای ثبت form fieldها داریم.
در روش form-urlencoded که روش پیشفرضِ HTML است، مقدار Content-Type در Request Headers به صورت زیر تعریف میشه و مقادیر فرم به صورت query string در Request Body قرار میگیره (شبیهسازی به کمک متد POST و روش x-www-form-urlencoded در Postman).
|
|
در روش form-data مقدار Content-Type در Request Headers به صورت زیر تعریف میشه و مقادیر فرم با فرمت نمایش داده شده در Request Body قرار میگیره (شبیهسازی به کمک متد POST و روش form-data در Postman).
|
|
معمولا در حالتی که فیلدهای کمی (مثلا 5 تا) داریم، روش form-urlencoded کار میکنه، اما در حالتی که فیلدها زیاد هستند و فایل هم در فرم داریم، از روش form-data استفاده میکنیم.
Model Validation
فرض کنید که model binding انجام شده و به مقادیرِ مدل دست پیدا کردهایم. چطور اونها رو اعتبارسنجی کنیم؟ مثلا انتظار داریم نام افراد فقط شامل حروف انگلیسی، ایمیلشون حتما شامل “@” و شماره موبایلشون 11 رقم باشه. به این کار Model Validation میگن. در این روش، به کمک [attribute]ها، قوانین مورد نظرمون رو برای هر property معرفی میکنیم.
|
|
مثلا در تصویر زیر، کلاس Person رو با چند property معرفی کردهایم و برای هر کدام، چند validation گذاشتهایم و در صورت عدم تطابق، پیام مناسب (ErrorMessage) به کاربر برمیگردونیم. لیست کامل attributeها اینجاست.
|
|
ModelState یکی از ویژگیهای ControllerBase است که در action methodها در دسترسه و میتونه وضعیتِ معتبر بودنِ مدل رو به ما اعلام کنه. ModelState سه property مهم داره.
- IsValid یک مقدار boolean است و مشخص میکنه که آیا مدل معتبر است یا نه. اگر یک یا چند خطا در اعتبارسنجی رخ داده باشه، false و در غیر اینصورت، true برمیگردونه
- ErrorCount تعداد خطاها در فرایند اعتبارسنجی رو نشون میده
- Values لیستی از خطاهای همهی propertyهای مدل رو نشون میده
|
|
Custom Validations
داتنتکور، attributeهای زیادی در اختیارمون گذاشته که به کمک اونها میشه validation انجام داد. اما اگر نیاز به یک validation خاص داشته باشیم چی؟ میتونیم Custom Validation بنویسیم.
|
|
فرض کنید قراره اعتبارسنجی جدیدی به برنامه اضافه کنیم تا مطمئن بشیم سال تولد افراد، قبل از 2000 است، بنابراین قصد داریم MinimumYearValidatorAttribute رو بسازیم. خوبه که فولدر جداگانهای به نام CustomValidators بسازیم و validatorمون رو در اون قرار بدیم. این کلاس باید از ValidationAttribute (که کلاسِ پایهی همهی attributeهاست) ارثبری کنه و متد IsValid رو override کنه.
|
|