<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های علی دوستکانی</title><link>https://virgool.ir/feed/@ali-doustkani</link><description>برنامه نویس!</description><language>fa</language><pubDate>2026-11-11 10:28:32</pubDate><image><url>https://files.virgool.ir/upload/users/73739/avatar/UlREGf.png</url><title>علی دوستکانی</title><link>https://virgool.ir/@ali-doustkani</link></image><item><title>DLL Injection</title><link>https://virgool.ir/@ali-doustkani/dll-injection-azqn9uzyt1sr</link><description>مقاله‌ی زیر از وبلاگ شخصی‌ام کپی شده، برای استفاده از مثال و خوانایی بهتر لطفا آن را در این لینک بخوانیدمقدمهتمام برنامه‌ها در ویندوز در قالب پردازه‌هایی اجرا می‌شوند که دارای مرز و محدوده‌اند. مرزی که با نحوه‌ی مدیریتِ حافظه‌ در ویندوز با فضای آدرسِ مجازی ایجاد می‌شود. به طور دقیق‌تر می‌‎توان گفت؛ در ویندوز یک پردازه به حافظه‌ی پردازه‌های دیگر دسترسی ندارد بنابراین هیچ‌گاه نمی‌توان از یک پردازه تابعی را که در پردازه‌ای دیگر قرار دارد فراخوانی کرد. با این تفاسیر فرض کنید به هر بهانه‌ای، مثل اتوماسیون، تستِ امنیتی، هک و غیره، نیاز به انجام چنین کاری دارید. بنابراین باید راهی یافت تا کدهای خودمان را در پردازه‌ی دیگر قرار دهیم تا در نهایت اجرا شود. در برنامه‌نویسیِ ویندوز DLL Injection تکنیک شناخته شده‌ای است که این کار را محقق می‌کند. با استفاده از تکنیکِ DLL Injection می‌توان یک DLL را که حاوی کدهای مطبوع‌مان است در فضای آدرسِ پردازه‌ای دیگر بارگزاری کرد تا اجرا شود. این مقاله به یکی از روش‌های پیاده‌سازیِ این تکنیک می‌پردازد. مقاله ابتدا با چند پاراگراف درباره‌ی Kernel Objectها آغاز می‌شود و سپس با بررسیHookها، ‏DLL Injection و مثالی در این زمینه به پایان می‌رسد.Kernel Objectsبه نظر من هر برنامه‌نویسی که قصد دارد کدِ نزدیک به سیستم‌عامل بنویسد باید درکِ درستی از Kernel Objectها داشته باشد چراکه بسیاری از توابعِ ارائه شده در API ِ ویندوز با آن‌ها سروکار دارند. به طور کل ویندوز را می‌توان سیستم‌عاملی objectمحور شناخت و منظور از objectمحور، object oriented نیست. در این مبحث object را متغیری از جنسِ یک structure تصور کنید (مثل ++C) که توسط سیستم نگهداری می‌شود یعنی؛ به صورت مستقیم و با استفاده از اشاره‌گرها توسط پردازه‌های دیگر قابل دسترسی نیست و اصلا به همین دلیل است که به آن Kernel Object می‌گویند. یعنی objectهای متعلق به kernel یا هسته‌ی سیستم‌عامل. برای نمونه می‌توان به این objectها اشاره کرد: File، ‏Job، ‏Thread، ‏Process، ‏Semaphore.Kernel Object ِ مربوط به هر منبع (Resource) اطلاعاتی آماری درباره‌ی آن منبع نگهداری می‌کند. مثلا با ایجاد هر پردازه (Process) ویندوز یک object برای آن می‌سازد که اطلاعاتی مثل ID، ‏UsageCount و ‏SecurityDescriptor را در بر دارد. برخی از این اطلاعات در تمام objectها مشترک‌اند - مثل UsageCount - و برخی تنها به object ِ مربوط به پردازه تعلق دارند - مثل ID. همان‌طور که گفته شد دسترسی مستقیم به objectهای ویندوز ممکن نیست و خواندن یا نوشتن در این objectها تنها از طریق API ِ ویندوز امکان‌پذیر است. به این طریق ویندوز از حفظ یکپارچگی در داده‌های این objectها اطمینان حاصل می‌کند چراکه این اطلاعات تنها از طریق توابعی از قبل مشخص شده و با وظایفی تعریف شده قابل تغیراند. استفاده از توابعی که با kernel objectها سر و کار دارند به روش خاصی انجام می‌شود و برای کار با آنها باید از موجودیتی به نامِ Handle استفاده کرد. می توانید Handle را شناسه‌ی یک object تصور کنید. شناسه‌ای که با استفاده از آن یک object را ایجاد (create) یا باز (open) می‌کنیم، از آن استفاده می‌کنیم و در نهایت آن را می‌بندیم (close). مثال زیر از فایل که یکی از معروف‌ترین objectهای ویندوز است، استفاده می‌کند. در این مثال ابتدا یک فایل در دیسک می‌سازیم و سپس مقداری در آن می‌نویسیم. تابعِ CreateFile در ویندوز وظیفه‌ی ایجاد یک object ِ فایل را بر عهده دارد. با فراخوانی این تابع ویندوز یک object برای نگهداریِ اطلاعاتی مثل محل ذخیره یا حالتِ دسترسی به یک فایل ایجاد می‌کند، آدرسِ object را در جدول پردازه درج می‌کند و در نهایت handle ِ مربوط را بازگشت می‌دهد. از این به بعد می‌توان از آن handle برای انجام کارهای مختلفی مثل نوشتن در فایل استفاده کرد. پس از اتمام نوشتن در فایل، handle را با استفاده از تابعِ CloseHandle از بین می‌بریم. دقت کنید بستنِ handle به معنای از بین رفتنِ object نیست. همان‌طور که در ابتدا بیان شد مالکِ تمام objectها خودِ سیستم‌عامل است، پس بستنِ یک handle یا بسته شدنِ پردازه‌ی مالکِ آن handle لزوما به از بین رفتن objectهای سیستم‌عامل نمی‌انجامد بلکه امکان دارد object همچنان به حیات خود ادامه دهد و توسط پردازه‌های دیگر مورد استفاده قرار گیرد. در قسمت بعدی بیشتر به این بحث می‌پردازم.#include &lt;Windows.h&gt; &#xA;int main() {  &#xA;  HANDLE hFile = CreateFile(L&#34;C:\\myfile.txt&#34;, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); &#xA;  char data[] = &#34;Hello Kernel Objects!&#34; &#xA;  DWORD numberOfBytesToWrite = strlen(data); &#xA;  DWORD numberOfBytesWritten = 0;  WriteFile(hFile, data, numberOfBytesToWrite, &amp;numberOfBytesWritten, NULL); &#xA;  CloseHandle(hFile); &#xA;}Handleهمان‌طور که بیان شد hanlde شناسه‌ای است برای ارجاع به یک object ِ خاص. فهم این موضوع بسیار مهم است که حوزه‌ی اعتبارِ handle پردازه است، یعنی؛ مالکیتِ تمام handleهای موجود در سیستم بر عهده‌ی پردازه‌هاست. به این صورت که سیستم‌عامل آدرسِ حافظه‌ی objectهای مورد نیازِ یک پردازه را، همراه با اطلاعاتی دیگر که نوع آن object را تعین می‌کنند، در جدولی به نام Process Handle Table قرار می‌دهد و برای هر ردیفِ این جدول شناسه‌ای تعین می‌کند که همان handle است. جدول handleهای پردازه در قسمتی از حافظه‌ی متعلق به آن پردازه قرار دارد بنابراین بدیهی است که مقادیرِ handleها خارج از فضای پردازه اعتباری ندارند؛ استفاده از hanldeی که متعلق به پردازه‌ی A است در پردازه‌ی B امکان‌پذیر نیست. با این تفاسیر اگر چندین پردازه‌ی مختلف قصد استفاده از یک object ِ واحد را داشته باشند، هر یک ردیفی برای آن در جدولشان نگهداری می‌کنند و برای هر ردیف handleی تعریف می‌کنند. برای به اشتراک‌گزاری handleها بین پردازه‌های مختلف راه‌هایی وجود دارد که دغدغه‌ی این مقاله نیست.فراخوانیِ تابع CreateFile یک فایل در مسیر مشخص شده و handle ِ آن را می‌سازد. سپس مقدارِ handle به تابعِ WriteFile پاس داده می‌شود. عمکلرد تابعِ WriteFile با handle ِ ‏hFile به این شکل است: ابتدا با استفاده از handle به سراغ ردیف مربوطه در جدولِ handleها می‌رود و از آنجا آدرسِ object ِ ‏hFile را پیدا می‌کند و سپس با استفاده از آن به object ِ اصلی در سیستم‌عامل دسترسی می‌یابد. سیستم‌عامل اطلاعات تکمیلی بسیاری را درباره‌ی فایل در داخل object ِ آن نگهداری می‌کند. تابع WriteFile با استفاده از این اطلاعات مختصات فیزیکی فایل را بدست می‌آورد و در نهایت مبادرت به نوشتن اطلاعات در دیسک می‌کند. با این تفاسیر پردازه بدون اینکه درگیر پیچیدگی‌های مربوط به objectهای ویندوز شود تنها با استفاده از مقداری از نوع HANDLE درخواستش را برای خواندن/تغیر یک object به توابع ویندوز ارائه می‌دهد. تصویر زیر نحوه‌ی تعامل چندین پردازه‌ی مختلف با یک object ِ خاص را نشان می‌دهد:به یاد داشته باشید که Handleها باید بسته شوند. ویندوز برای مدیریت طول‌عمر objectها از روش شمارش استفاده می‌کند به این معنی که هر بار پردازه‌ای اقدام به بازکردن یک object کند مقدارِ UsageCount ِ آن یکی زیاد خواهد شد و با بستنِ هر Handle عکس آن. با این روش هنگامی که مقدارِ UsageCount به صفر برسد ویندوز اقدام به از بین بردن object از حافظه می‌کند. اگر پردازه‌ای handleهای باز داشته باشد، ویندوز قبل از بستن پردازه تمام آنها را می‌بندد. بستنِ handleها معمولا با استفاده از تابع CloseHandle انجام می‌شود اما ویندوز برای برخی objectهای خاص توابع دیگری نیز دارد.Hookinghook به قطعه‌کدی گفته می‌شود که توسط یک عامل خارجی و برای دریافت رویدادهای سیستم نصب می‌شود. بدیهی است که عامل خارجی و سیستم در اینجا مفاهیمی نسبی هستند که گاهی می‌توانند به بزرگیِ یک سیستم‌عامل و گاهی به کوچکیِ یک object باشند. مثلا اگر از react استفاده کرده باشید این مفهوم با استفاده از توابعی مثل useState یا useEffect پیاده‌سازی شده است. یا مثالی دیگر مکانیزمِ event handling در زبانِ #C است که با استفاده از عملگرِ =+ کار می‌کند. در مثالِ react عامل خارجی، استفاده کننده‌ی framework است و در #C استفاده کننده‌ی یک object. در تمام سیستم‌هایی که امکان hooking را فراهم می‌کنند مکانیزمی برای ثبت اطلاعاتِ قطعه‌کدها، که معمولا تابع هستند، وجود دارد. مکانیزم مشابه در بین این‌گونه سیستم‌ها این است که اطلاعاتِ توابع را در جایی نگهداری می‌کنند و هنگام رخ‌دادنِ رویداد مورد نظر، توابع ثبت شده را یکی پس از دیگری فراخوانی می‌کنند. ویندوز هم از این قاعده مستثنی نیست. ویندوز برای اینکه بتواند توابعِ متقاضی را نسبت به رویدادهای مربوط به windowها مطلع کند از مکانیزمِ hooking استفاده می‌کند. در این مکانیزم برای هر یک از انواعِ hook صفی به نام hook chain تعریف می‌شود که وظیفه‌ی نگهداریِ لیستی از توابع را دارد. توابعِ موجود در hook chain هنگام رخ‌دادن رویداد مورد نظر یکی پس از دیگری فراخوانی می‌شوند. در ادبیات ویندوز به این توابع اصطلاحا hook procedure می‌گویند که ساختارشان همواره ثابت و به این شکل است:LRESULT CALLBACK HookProc(int code, WPARAM wparam, LPARAM lparam){&#xA;  // rokhdad ra dar inja barresi konid&#xA;  return CallNextHookEx(NULL, code, wparam, lparam);&#xA;}قطعه کد بالا ساختارِ یک تابعِ ثبت شده در hook chain را نشان می‌دهد. پارامترِ code حاوی نوعِ hook است؛ مثل WH_KEYBOARD. اگر اطلاعاتی اضافی در رابطه با رویدادِ مورد نظر وجود داشته باشد سیستم آنها را در پارامترهای دوم و سوم قرار می‌دهد؛ مثلا کلیدی از کیبورد که فشار داده شده. به فراخوانی تابع CallNextHookEx در انتها دقت کنید. هر تابعِ حاضر در hook chain وظیفه‌ی فراخوانی تابعِ بعدی را دارد. در صورتی که این کار انجام نشود در حقیقت پردازش hook chain به پایان می‌رسد. البته این شکلِ اجرا در hook chainهای حساس صدق نمی‌کند ولی بهتر است همیشه از CallNextHookEx در انتهای تابع استفاده کنیم. همان‌طور که بیان شد در ویندوز برای هر نوع hook یک hook chain ِ مجزا وجود دارد. برای مثال می‌توان به WH_CALLWNDPROC و WH_KEYBOARD اشاره کرد که به ترتیب مسئول پردازش پیغام های ارسالی به windowها و پیغام‌های کیبورد هستند:برای ثبت کردنِ یک تابع در یک hook chain از تابع SetWindowsHookEx استفاده می‌کنیم. ساختارِ این تابع به شکل زیر است و برای درک کامل آن هنوز چند پارامتر دیگر باید توضیح داده شود.HHOOK SetWindowsHookExA(&#xA;  int                    idHook,&#xA;  HOOKPROC   lpfn,&#xA;  HINSTANCE  hmod,&#xA;  DWORD          dwThreadId &#xA;);SetWindowHookEx همیشه تابعِ متقاضی را در ابتدای hook chain می‌گذارد و چگونگی اجرای آن را نسبت به مقدار پارامترها تنظیم می‌کند. با استفاده از پارامتر اول، idHook، تعین می‌کنیم که قصد استفاده از کدام نوع hook را داریم؛ برای مثال WH_KEYBOARD. با پارامتر دوم، lpfn، اشاره‌گری به تابعِ متقاضی برای اجرا را تعین می‌کنیم که همان تابعِ متقاضی برای فراخوانی هنگام رویداد سیستم است. سیستم این اشاره‌گر را در ابتدای hook chain قرار می‌دهد تا هنگام رخ‌دادنِ رویدادی که پارامتر اول مشخص کرده است، آن را اجرا کند. تابعی که در اینجا قرار می‌دهیم باید ساختاری (signature) مشابهِ آنچه قبلا ذکر شد داشته باشد. پارامتر سوم، hmode، ‏handle ِ مربوط به ماژولی است که تابعِ متقاضی در آن قرار دارد. توجه کنید که تفاوت یک فایل DLL با ماژول در این است که ماژول، موجودیتی لود شده در حافظه است که با ساختاری مشخص آماده‌ی اجراست. پارامتر چهارم، dwThreadId، شناسه‌ی نخی است که تابع را اجرا خواهد کرد. نحوه‌ی استفاده از SetWindowsHookEx بر اساس شرایط می‌تواند تغیر کند، به خصوص دو پارامتر آخر، ولی من در اینجا فقط گونه‌ای را توضیح می‌دهم که برای این مقاله کاربرد دارد. برای اطلاعات بیشتر به توضیحات مایکروسافت مراجعه کنید. در قسمت بعدی با ذکر مثالی چگونگی استفاده از این تابع برایتان روشن خواهد شد.DLL Injectionسیستم‌عامل ویندوز به هر پردازه فضای آدرس خودش را اختصاص می‌دهد و پردازه‌ها اجازه‌ی تجاوز از این فضا و استفاده از فضاهای دیگر را ندارند. با این حال اگر نیاز باشد به فضای آدرس پردازه‌های دیگر را دسترسی داشته باشیم می‌توانیم با اجرای تکنیکی معروف به DLL Injection به هدفمان برسیم. پیاده‌سازی این تکنیک به روش‌های مختلفی انجام می‌شود اما در اینجا از روشی hooking به windowهای پردازه‌ی هدف استفاده می‌کنیم. بنابراین بدیهی است که این روش فقط زمانی کاربرد دارد که پردازه‌ی هدف دارای window است. بنابراین اگر پردازه‌ی A یک تابعِ موجود در یک DLL را به رویدادی در پردازه‌ی B، ‏hook کند، عملا ماژولی که تابع در آن قرار دارد را وارد فضای آدرس پردازه‌ی دیگر کرده است. البته استفاده از SetWindowsHookEx تنها راه ممکن برای پیاده سازیِ DLL Injection نیست ولی در این مقاله تنها همین روش بررسی خواهد شد. توجه کنید که تمام ماژول‌ها و پردازه‌های درگیر در این فرآیند، یعنی پردازه‌ی inject کننده، ماژولی که قرار است inject شود و در نهایت پردازه‌ای که بدان hook می‌کنیم باید از یک platform برخوردار باشند؛ x86 یا 64.</description><category>علی دوستکانی</category><author>علی دوستکانی</author><pubDate>Mon, 13 Sep 2021 01:35:37 +0430</pubDate></item><item><title>Memory Mapped Files</title><link>https://virgool.ir/@ali-doustkani/memory-mapped-files-gjdxqzq7f02c</link><description>مقاله‌ی زیر از وبلاگ شخصی‌ام کپی شده، برای فرمت و خوانایی بهتر می‌توانید آن را در این لینک بخوانیدPatrick Lindenbergمقدمهبهانه‌ی نوشتن این پست برای من امکان جایگزینیِ IPC با Memory Mapped File بود، گرچه با وجود سرعت بالای آن به‌نظر بهترین راه‌حل برای این جایگزینی نیست. البته Memory Mapped File کاربردهای دیگری هم دارد که چه‌بسا مهم‌ترند، بنابراین دانستنِ آن بی‌فایده نیست. همان‌طور که در ادامه خواهید دید استفاده از این قابلیتِ ویندوز در dotnet به هیچ وجه کار سختی نیست، اما فهمِ چگونگیِ عملکردِ سیستم‌عامل در لایه‌های پایین‌تر باعث درکِ درستِ موضوع می‌شود. به همین خاطر متن با معرفی مختصری از معماری حافظه در ویندوز آغاز می‌شود و در ادامه به توضیحِ Memory Mapped Fileها می‌پردازد و در نهایت با چند مثال و کاربردِ مختلف به زبان #C خاتمه می‌یابد.سیستم‌عامل و مدیریت حافظهسیستم‌عامل طبق وظیفه‌اش قسمتی از حافظه را در اختیار پردازه (Process) قرار می‌دهد. پردازه هم خود می‌داند از این حافظه چگونه استفاده کند که این آگاهی گاه توسط برنامه‌نویس و گاه توسط زبان، Runtime یا Framework فراهم می‌شود. برای مثال سیستم‌عامل 4 گیگابایت حافظه در اختیار یک پردازه می‌گزارد اما اینکه طول عمر متغیرها در حافظه چقدر باشد و موضوعاتی از این قبیل، به عهده‌ی خود پردازه است. اما در بین امکانات مربوط به حافظه که از طرف سیستم‌عامل - مشخصا ویندوز - ارائه می‌شود، یکی می‌تواند در برنامه‌های سطح بالا همچنان کاربردی باشد: Memory Mapped File. ‏Memory Mapped Fileها یکی از امکاناتِ سیستمیِ ویندوز است که مبتنی بر ساختارِ مدیریت حافظه‌ی ویندوز ارائه شده است و کاربردهای متفاوتی دارد. مثلا کار با فایل‌های بزرگ یا اشتراک داده بین پردازه‌های مختلف.همان طور که می‌دانیم در ویندوز واحد تخصیص منابع پردازه (Process) است. بنابراین حافظه در اختیار پردازه‌ها قرار می‌گیرد و هر پردازه تنها مجاز به دسترسی به حافظه‌ی خودش است. در ابتدایی‌ترین حالت، مدیریت حافظه به این شکل انجام می‌شود:در شکل بالا سیستم یک حافظه‌ی RAM با ظرفیت 1MB دارد. در این سیستم به هر پردازه 256KB حافظه‌ی فیزیکی اختصاص داده می‌شود و در حال حاضر 512KB از کل حافظه آزاد است. بدیهی است که در چنین شرایطی حداکثر 4 پردازه امکان فعالیتِ هم زمان دارند - با صرف‌نظر از حافظه‌ای که خود سیستم‌عامل برای بقا نیاز دارد. بنابراین برای باز کردنِ پردازه‌ی پنجم حتما باید به زندگی یکی از چهار پردازه‌ی باز پایان داد چراکه در سیستم‌عامل‌های قدیمی ظرفیتِ حافظه‌ی فیزیکی با ظرفیتِ RAM برابری می‌کرد و این واقعیت محدودیت‌های زیادی را باعث می‌شد! حتی با ظرفیت‌های امروزی میزانِ نیازِ پردازه‌ها در یک سیستم می‌تواند به مراتب بیشتر از RAM باشد. چنین مشکلاتی طراحان سیستم‌عامل را وادار به استفاده از مدلِ دیگری از مدیریت حافظه کرد که فهم آن پیش نیاز فهمِ درستِ Memory Mapped File است: حافظه‌ی مجازی.حافظه‌ی مجازیجمله‌ای قدیمی هست که &#34;وقتی مامان نباشه باید با زن بابا ساخت!&#34;وقتی حافظه‌ی اصلی به اندازه‌ی کافی موجود نبود باید از حافظه‌ی دیگری استفاده کرد. مثل دیسک، یا به طور کل حافظه‌ی ثانویه. بنابراین تمامِ هنرِ سیستم‌عامل در مدیریت حافظه این است که به گونه‌ای از دیسک به عنوان حافظه‌ای پشتیبان استفاده کند که بشود بیشتر از آنچه ممکن است در RAM ذخیره کرد. چگونگی انجام این کار زیاد دور از تصور نیست؛ با جابجایی محتویاتِ موجود در RAM به دیسک، می‌توان فضای آن را آزاد و در آن اطلاعاتِ جدیدی ذخیره کرد. اینکه این محتویات دقیقا چه زمانی در دیسک نوشته می‌شود در حوصله‌ی این بحث نیست اما نکته‌ی مهم این است که در روش مدیریت حافظه‌ی مجازی از دیسک به عنوان پشتیبانِ RAM استفاده می‌شود. با استفاده از همین روش یک سیستمِ 32بیتی می‌تواند ظرفیتِ 4گیگابایت و یک سیستمِ 64بیتی 16اگزابایت حافظه‌ی مجازی در اختیار پردازه‌ها قرار دهد. با توجه به ظرفیت RAMها در این روزها شاید 4 گیگابایت آنچنان به چشم نیاید اما امکان تخصیصِ 16اگزابایت به یک پردازه چیز کمی نیست! برای اینکه بهتر متوجه چگونگی رابطه‌ی یک پردازه با فضای آدرسش شویم به جزیات بیشتری اشاره می‌کنم؛ سیستم‌عامل هنگام ایجاد هر پردازه، موجودیتی به نام فضایِ آدرسِ مجازی (Virtual Address Space) به آن تخصیص می‌دهد. منظور از فضایِ آدرسِ مجازی در حقیقت مجموعه‌ای از آدرس‌هاست. مثل یک آرایه. آرایه‌ای را تصور کنید که مقدار هر عنصرِ آن یک آدرس است. هر کدام از این آدرس‌ها به محلی فیزیکی در RAM به صورت غیرمستقیم اشاره می‌کنند که در ادامه منظور از غیر مستقیم را توضیح خواهم داد. شکل زیر یک فضای آدرس مجازی را نشان می دهد:تا اینجا منظور از فضای آدرس به روشنی بیان شد؛ مجموعه آدرس‌هایی که به فضایی در RAM اشاره می‌کنند. اما منظور از مجازی چیست؟ منظور از مجازی این است که هیچ کدام از این آدرس‌ها به محلی واقعی در RAM اشاره نمی‌کنند و قبل از نوشتن/خواندن داده‌ها از این آدرس‌ها ابتدا باید یک نگاشت (Mapping) انجام شود. بنابراین هیچ بعید نیست که فضای آدرس دو پردازه به شکل زیر باشد:همان‌طور که در شکل بالا مشخص است هر دو پردازه‌ی A و B می توانند آدرسِ 0XCC2 را در فضای آدرسشان داشته باشند و بدون هیچ‌گونه تصادمی داده‌هایشان را داخل حافظه بنویسند/بخوانند. اما این دو آدرس، با وجود مقادیر یکسان، عملا به دو محلِ فیزیکی متفاوت در RAM اشاره می‌کنند. به خاطر اینکه قبل از انجام هر عمل خواندن/نوشتن ابتدا نگاشتی (Mapping) توسط سیستم‌عامل و CPU انجام می‌شود تا آدرس فیزیکی بدست‌آید. بنابراین پردازه اطلاعاتش را در حافظه‌ی مجازی می‌نویسد و در نهایت سیستم‌عامل تنها قسمتی از حافظه‌ی مجازی را که دارای اطلاعاتِ واقعی است به RAM منتقل می‌کند. به جزئیات و چگونگی این انتقال در ادامه می‌پردازیم. بنابراین تا اینجا آنچه بین پردازه و RAM اتفاق می‌افتد مشخص شد. حالا باید به این سوال پاسخ دهیم که اطلاعاتِ داخلِ RAM در کجای دیسک ذخیره می‌شود و به طور فنی‌تر منظور از جمله‌ی &#34;از دیسک به عنوان پشتیبانِ RAM استفاده می‌شود&#34; چیست.page و pagefileدر مدیریت حافظه به روش مجازی کوچک‌ترین واحد اطلاعات در RAM، ‏page است که قسمتی پیوسته و یک پارچه محسوب می‌شود. اندازه‌ی page در یک سیستم به معماری پردازنده‌ی آن بستگی دارد. سیستم‌عامل برای انتقالِ اطلاعاتِ اضافی از RAM به دیسک و برعکس با pageها سر‌و‌کار دارد. بنابراین وقتی نیاز است تا RAM خالی‌تر شود تا جا برای پردازه‌های دیگر باز شود سیستم‌عامل تعدادی از pageها را روی دیسک می‌نویسد و هنگامی که دوباره به آن pageها نیاز شد آنها را به RAM منتقل می‌کند. نام این فرآیند paging است و محلی از دیسک که pageها در آن نوشته می‌شوند pagefile نام دارد. بنابراین هر بار نخی در پردازه‌ای تقاضای دسترسی به آدرسی از حافظه را دارد سیستم عامل باید بررسی کند که آیا آن page در RAM قرار دارد یا در دیسک (pagingfile) و اگر در دیسک قرار داشته باشد ابتدا باید آن را در RAM لود کند تا قابل استفاده شود. البته که برای نخ/برنامه‌ای که تقاضای خواندن/نوشتن از حافظه را دارد تمام این مراحل پنهان است.کمی توجه به فرآیند paging لزوم استفاده از آدرس‌های مجازی را روشن‌تر می‌کند؛ هیچ تضمینی وجود ندارد که هنگامِ نیازِ مجدد به pageهای موجود در دیسک، آنها دوباره در همان محلی نوشته شوند که قبلا حضور داشته‌اند. این قابلیت به سیستم‌عامل آزادی کامل در مدیریت حافظه‌ی فیزیکی را می‌دهد. بنابراین با هر بار رفت‌و‌برگشتِ یک page امکان دارد محل جدیدی برای آن در RAM انتخاب شود. با این اوصاف آیا پردازه از تمام این اتفاقات مطلع است؟ باید گفت خیر. از نظر پردازه تمام اطلاعاتش در RAM همواره در یک آدرس ثابت نگهداری می‌شود که همان آدرسِ فضای مجازی است.چگونگی نوشتن حافظه‌ی مجازی به دیسکبیشتر فضایِ آدرسِ مجازیِ تخصیص داده شده به یک پردازه هنگام شروع آزاد یا unallocated است. برای استفاده‌ی واقعی از این فضای آدرس باید به آن region تخصیص داد.پردازه پس از تصمیم برای نوشتن در حافظه ابتدا قسمتی از فضای آدرس را به میزانی که نیاز دارد رزرو می کند. عمل رزرو با تابعِ سیستمیِ VirtualAlloc انجام می‌شود و هدف از آن این است که آدرس مورد نیاز در دفعات بعد توسط پردازه مصرف نشود. به قسمت رزرو شده اصطلاحا یک region می‌گویند. قبل از استفاده‌ی واقعی از یک region ابتدا باید حافظه‌ی فیزیکی به آن اختصاص داد. یعنی map کردن قسمتی از حافظه‌ی فیزیکی به region که به آن commit می‌گویند. commit کردن هم مثل عملِ رزرو با تابعِ VirtualAlloc انجام می‌شود. بعد از commit سیستم قسمتی از pagefile را بر اساس اندازه‌ی region به آن تخصیص می‌دهد. البته توجه کنید که این قسمت همیشه برابر با اندازه‌ی region نیست چرا که اندازه‌ی تخصیص داده شده از pagefile همیشه باید ضریبی از اندازه‌ی page باشد. حال امکانِ نوشتن در حافظه فراهم می‌شود و در نهایت هنگامی که دیگر به آن حافظه نیازی نیست باید آن region را decommit و در پی آن release کرد.هر بار نخی در پردازه تقاضای دسترسی به آدرسی از حافظه را دارد سیستم‌عامل باید برررسی کند که آیا آن page در RAM قرار دارد یا در دیسک. اگر در دیسک قرار داشته باشد ابتدا باید آن را لود کند تا قابل استفاده شود. با این اوصاف این سوال پیش می آیند که آیا هگام باز کردن یک برنامه امکان دارد محتویاتِ فایلِ exe در paingfile نوشته شود؟Memory Mapped Fileچگونگیِ کارکردِ Virtual Memory با تاکید بر یک جمله‌ی کلیدی تشریح شد؛ سیستم عامل برای پیاده سازیِ حافظه‌ی مجازی از دیسک به عنوان پشتیبانِ RAM استفاده می‌کند. یعنی سیستم‌عامل اطلاعاتِ پردازه را در هر دو حافظه‌ی اصلی و ثانویه قرار می‌دهد تا در صورت لزوم بتواند pageها را از RAM خارج کند تا فضا برای پردازه‌های دیگر آزاد شود. برای روشن‌تر شدن مسئله باید پرسید؛ منظور از اطلاعات پردازه چیست؟ تاکنون منظور فقط اطلاعاتی بود که هنگام اجرا در اثر پردازشِ ورودی ایجاد می‌شد. اما واضح است که اطلاعاتِ پردازه محدود به ورودی‌های آن نیست. کدها و داده‌های موجود در کد هم جزو این اطلاعات هستند. آیا کدهای یک پردازه، یا به طور دقیق‌تر، محتویات فایل اجرایی (exe یا dll) هم به pagefile منتقل می‌شوند؟ بدیهی است که چنین کاری به هیچ وجه کارآمد نیست چراکه نه تنها فایده‌ای ندارد، بلکه نوشتنِ اطلاعات از دیسک (فایلِ exe یا dll) به دیسک (pagefile) باعث از دست رفتنِ زمان و فضای دیسک هم می‌شود!اطلاعات اولیه‌ی یک پردازه از قبیلِ text. ،‏ bss. و ‏data. ابتدای اجرای برنامه مستقیما از فایلِ مربوطه (exe یا dll) به حافظه‌ی اصلی منتقل می‌شوند. در حقیقت این سناریو دلیلِ پیاده‌سازیِ قابلیتِ Memory Mapped File توسط مایکروسافت است. بنابراین زمانی که یک پردازه برای اولین بار باز می‌شود، سیستم یک region از فضای آدرس را به صورتِ اتوماتیک برای اطلاعاتِ اولیه‌ی آن رزرو و به فایل مربوطه map می‌کند. توجه کنید که در تمام نسخه‌های خانواده‌ی NT اطلاعاتِ پردازه مانندِ انواعِ دیگرِ اطلاعات در قالب pageها نگهداری می‌شوند تنها با این تفاوت که هنگام ذخیره در دیسک به جای قرارگیری در pagefile، در فایلِ exe یا dll جای دارند.پس از باز شدن پردازه، ویندوز مسئولیت تمام کارهای مربوط به آن را بر عهده دارد؛ از قبیلِ paging، ‏buffering یا caching. برای مثال اگر در اجرای پردازه سیستم‌عامل به دستورِ jump برخورد کند و page ِ مربوطه در حافظه موجود نباشد پردازه متوجه آن نخواهد شد، بلکه سیستم عامل آن را شناسایی و page ِ مربوطه را لود می‌کند.به طور کل وجود قابلیت Memory Mapped File امکانات زیادی را در سیستم عامل ایجاد می کند؛ مثلا به‌اشتراک‌گزاریِ اطلاعات اولیه‌ی یک برنامه بینِ پردازه‌های باز شده از آن. بنابراین اگر یک برنامه را بیشتر از یک بار باز کنید سیستم‌عامل به اطلاعاتِ اولیه‌ی هر کدام از آن‌ها حافظه‌ی فیزیکیِ مستقلی اختصاص نمی‌دهد. اطلاعاتِ اولیه یک‌بار در حافظه بارگزاری می‌شود و به دفعات بینِ پردازه‌های مختلف به اشتراک گزاشته می‌شود. البته این امکان وجود دارد که یکی از پردازه‌ها نیاز به تغیر مقادیری در اطلاعاتِ اولیه داشته باشد که در آن صورت ویندوز با استفاده از قابلیتِ copy-on-write آن را ممکن می‌کند. امکانی دیگر در ویندوز که بر اساسِ Memory Mapped File محقق می‌شود دسترسیِ غیرمستقیم به فایل‌ها از طریق حافظه‌ی اصلی است. ویندوز به پردازه‌ها این امکان را می‌دهد تا به فایل‌های موجود در دیسک دقیقا همان‌گونه دسترسی داشته باشند که به حافظه‌ی اصلی دسترسی دارند. اگر بخواهم به زبان ++C توضیح دهم، می‌توان گفت، در این روش دسترسی به فایل درست مثل dereference کردنِ یک اشاره‌گر است:*pMem = 23;و یا برای خواندن از فایل:value = *pMem;در حینِ اجرای تمامِ این دستورات پردازه نسبت به خواندن/نوشتن در فایل آگاه نیست و این هنرِ سیستم‌عامل است تا با ایجادِ یک لایه‌ی پنهان این فرآیند را عملی کند. فقط باید به این نکته توجه کرد که نوشتنِ فیزیکی در دیسک بلافاصله بعد از نوشتن در متغیر رخ نمی‌دهد.یکی دیگر از امکاناتی که Memory Mapped File فراهم می‌کند امکان تعامل بین پردازه‌هاست. در این کاربرد پای فایلی به طور مستقیم در میان نیست. بنابراین سیستم‌عامل به صورت خودکار از pagefile استفاده می‌کند بنابراین این طور به نظر می‌رسد که اصلا فایلی در فرآیند درگیر نیست. تعامل بین پردازه‌ها با این روش سریع‌ترین حالت ممکن است. با این تفاسیر می‌توان Memory Mapped Fileها را به دو گروه تقسیم‌بندی کرد. اول گروهی است که در آن برنامه‌نویس یک فایل را به عنوانِ پشتیبان به سیستم‌عامل معرفی می‌کند و دومی گروهی که در آن فایلی به صورت مستقیم وجود ندارد و همان طور که بیان شد سیستم‌عامل از pagefile به‌صورت اتوماتیک استفاده می‌کند. در ادامه مثال‌هایی برای استفاده از Memory Mapped File آورده شده است:مثال اول: خواندن و نوشتنِ فایل‌هادر مثال زیر یک میلیون رکوردِ دو‌بایتی به دو روشِ مختلف از یک فایل خوانده و سپس نوشته می‌شود تا هم چگونگیِ کارکردِ Memory Mapped File مشخص شود و هم منظور از &#34;سریع‌ترین روش برای نوشتن یا خواندن از فایل‌ها&#34;.class Program {&#xA;  const string FILE = &#34;data.txt&#34;&#xA;&#xA;  static void Main(string[] args) {&#xA;    CreateFileIfNotExist();&#xA;    var watch = Stopwatch.StartNew();&#xA;    UpdateRecordsUsingMemoryMappedFile();&#xA;    watch.Stop();&#xA;    Console.WriteLine($&#34;Memory Mapped File: {watch.Elapsed}&#34;);&#xA;    watch.Restart();&#xA;    UpdateRecordsUsingFileStream();&#xA;    watch.Stop();&#xA;    Console.WriteLine($&#34;File Stream: {watch.Elapsed}&#34;);&#xA;  }&#xA;&#xA;  // Create a dummy file of 2MB if needed&#xA;  static void CreateFileIfNotExist() {&#xA;    if (File.Exists(FILE)) {&#xA;      return;&#xA;    }&#xA;    var recSize = Marshal.SizeOf(typeof(Record));&#xA;    var fs = new FileStream(FILE, FileMode.CreateNew);&#xA;    fs.Seek(1000000 * recSize - 1, SeekOrigin.Begin);&#xA;    fs.WriteByte(0);&#xA;    fs.Close();&#xA;  }&#xA;&#xA;  static void UpdateRecordsUsingMemoryMappedFile() {&#xA;    long length = new FileInfo(FILE).Length;&#xA;    using (var mmf = MemoryMappedFile.CreateFromFile(FILE, FileMode.Open)) { &#xA;      using (var accessor = mmf.CreateViewAccessor()) { &#xA;        int recsz = Marshal.SizeOf(typeof(Record));&#xA;        Record rec;&#xA;        for (long i = 0; i &lt; length; i += recsz) {&#xA;          accessor.Read(i, out rec);&#xA;          rec.Update();&#xA;          accessor.Write(i, ref rec);&#xA;        }&#xA;      }&#xA;    }&#xA;  }&#xA;&#xA;  static void UpdateRecordsUsingFileStream() {&#xA;    long length = new FileInfo(FILE).Length;&#xA;    var fs = new FileStream(FILE, FileMode.Open);&#xA;    while (fs.Position &lt; length) {&#xA;      var buffer = new byte[2];&#xA;      fs.Read(buffer, 0, 2);&#xA;      Record rec = new Record(buffer);&#xA;      rec.Update();&#xA;      fs.Seek(-2, SeekOrigin.Current);&#xA;      fs.Write(rec.ToByte(), 0, 2);&#xA;    }&#xA;  }&#xA;}&#xA;&#xA;public struct Record {&#xA;  public byte value1;&#xA;  public byte value2;&#xA;  public Record(byte[] buffer) {&#xA;    value1 = buffer[0];&#xA;    value2 = buffer[1];&#xA;  }&#xA;  public void Update() {&#xA;    value1++;&#xA;    value2++;&#xA;  }&#xA;  public byte[] ToByte() =&gt; new[] { value1, value2 };&#xA;}اجرای کد بالا روی ماشینِ من خروجی زیر را تولید می‌کند. تفاوتِ سرعتِ دو روش کاملا آشکار است:Memory Mapped File: 00:00:00.1211754&#xA;File Stream: 00:00:20.1794723Memory Mapped Fileها از طریق objectهای view خوانده یا نوشته می‌شوند و مزیتِ وجودِ آن‌ها این است که می‌توان تنها قسمتی از فایل را به جای کلِ آن در view قرار داد. در کدِ بالا می‌توان به جای استفاده از ()MemoryMappedFile.CreateFromFile متدِ ()MemoryMappedFile.OpenExisting را به کار گرفت و از فایلی که در یک پردازه‌ی دیگر باز شده است استفاده کرد. این روش مبنای تعامل بین پردازه‌هاست.مثال دوم: تعامل بین دو پردازهدر مثال زیر دو پردازه از طریقِ Memory Mapped File تعامل می‌کنند. توجه کنید پیاده‌سازیِ یک روشِ همگام‌سازی بینِ دو پردازه ضروری است چراکه پردازه‌ی دوم هیچ آگاهی نسبت به نوشته‌شدنِ اطلاعات در map ندارد. مثالِ زیر EventWaitHandle را برای این منظور به کار گرفته.پردازه ی اول:static void Main() {&#xA;  var wait = new EventWaitHandle(false, EventResetMode.AutoReset, &#34;mywait&#34;);&#xA;  using (var mmf = MemoryMappedFile.CreateNew(&#34;mymap&#34;, 100)) {&#xA;    using (var stream = mmf.CreateViewStream()) {&#xA;      var writer = new BinaryWriter(stream);&#xA;      while (true) {&#xA;        Console.WriteLine(&#34;Enter a message:&#34;);&#xA;        var msg = Console.ReadLine();&#xA;        writer.Write(msg);&#xA;        wait.Set();    &#xA;      }&#xA;    }&#xA;  }&#xA;}پردازه‌ی دوم:static void Main() {&#xA;  var wait = EventWaitHandle.OpenExisting(&#34;mywait&#34;);&#xA;  using (var mmf = MemoryMappedFile.OpenExisting(&#34;mymap&#34;)) {&#xA;    using (var stream = mmf.CreateViewStream()) {&#xA;      var reader = new BinaryReader(stream);&#xA;      while (true) {&#xA;      wait.WaitOne();&#xA;      Console.WriteLine(reader.ReadString());&#xA;      }&#xA;    }&#xA;  }&#xA;}&#xA;</description><category>علی دوستکانی</category><author>علی دوستکانی</author><pubDate>Sun, 08 Aug 2021 16:24:00 +0430</pubDate></item><item><title>AppDomain: چرا و چگونه</title><link>https://virgool.ir/@ali-doustkani/appdomain-چرا-و-چگونه-wzjqgzgudot9</link><description>Shane - Unsplashمقدمهاصولا استفاده از AppDomain توی پروژه ها چیز رایجی نیست. منم برام پیش نیومده بود که نیازی بهش داشته باشم، تا همین چند وقت پیش که برای انجام کاری مجبور شدم مستقیما باهاش سر و کله ی جانانه ای بزنم. البته به نظرم از AppDomain نوشتن تو سال 2020 آنچنان موضوعیتی نداره، به خصوص اینکه توی net core. مایکروسافت چیزی به اسم AppDomain ارائه نداده - چه بهتر - بنابراین می تونیم بگیم یه مفهوم منسوخ شده ست. و اگرم نباشه توی این بیست سال حتما به اندازه ی کافی مطلب در موردش نوشته شده. ولی به هر حال من تصمیم گرفتم هر چی بلدم رو جمع و جور کنم تا یه پستی از توش دربیارم؛ عشقِ Blogging!!متن کامل مقاله را در اینجا بخوانید</description><category>علی دوستکانی</category><author>علی دوستکانی</author><pubDate>Sun, 11 Oct 2020 13:28:08 +0330</pubDate></item><item><title>پروتکل OAuth2.0: مفاهیم</title><link>https://virgool.ir/@ali-doustkani/پروتکل-oauth20-مفاهیم-sgesvlr6w7gp</link><description>Photo by Matthew Henry on Unsplashمقدمهیکی از تکراری ترین مولفه هایی که در تمامِ برنامه ها باید پیاده کنیم دسترسیِ کاربرهاست. رسم به اینه که کاربر اول باید عضو بشه، بعد می تونه از امکاناتِ برنامه استفاده کنه، تازه اونم نه از تمام امکانات! بلکه فقط اونایی که اجازشو داره. مثلاً در یک فروشگاهِ اینترنتی کاربرای عادی (خریدار) می تونن بعد از عضو شدن خرید انجام بدن و کاربرهای ادمین می تونن کالا اضافه کنن، قیمت ها رو تغیر بدن و غیره. پروتکلِ OAuth (بخونید اُ آث) برای همین  منظور تعریف شده. یعنی اختیاراتِ کاربرها در یک محیطِ شبکه ای.بررسیِ Authentication و Authorizationهمین مثالِ ساده ی فروشگاهِ اینترنتی رو در نظر بگیرید. در این مثال دو نوع کاربر داریم: کاربرِ ادمین و کاربرِ خریدار. این دو نوع کاربر از نظر اختیارات با هم تفاوت هایی دارن، اما یک چیز بین شون مشترکه: هر دو قبل از استفاده از سیستم باید خودشون رو معرفی کنن، یه به زبونی ساده تر؛ باید وارد سیستم بشن. به فرآیندِ &#34;وارد شدن&#34; در حوزه ی امنیت Authentication می گن. بعد از ورودِ کاربر به سیستم، هر کاربری بر اساس مجوزهایی که داره می تونه کارهای مشخصی انجام بده. به فرآیندِ &#34;تعین اختیارات&#34; هم Authorization گفته می شه.Authentication چیست؟ فرآیندی که در اون هویتِ کاربر مشخص می شه. مثل &#34;ورود&#34; در خیلی از سایت ها. همیشه هم با صفحه ی Login اتفاق نمی افته. شاید با یک دستگاهِ اثرِ انگشت انجام بشه.Authorization چیست؟ فرآیندی که در اون اختیاراتِ کاربر مشخص می شه. مثل زمانی که کاربرِ &#34;خریدار&#34; اختیارِ خرید کردن رو داره و کاربرِ &#34;ادمین&#34; اختیارِ وارد کردنِ کالاهای جدید به سایت.پروتکل OAuth2.0ورودِ تجهیزات و تکنولوژی های Cloud صنعتِ نرم افزار رو خیلی متحول کرد. قبل از ورودِ Cloud به صنعتِ نرم افزار اگر می خواستیم کارهایی مثل &#34;ورودِ کاربرها&#34; و &#34;مدیریتِ مجوزها&#34; و غیره رو انجام ندیم مجبور بودیم از Libraryها و ابزارهای آماده استفاده کنیم. اما با فراگیر شدن راهکارها و نرم افزارهایی که به صورت سرویس ارائه می شن این دغدغه رفع و رجوع شده. با استفاده از سرویس های امنیتی می تونیم این دغدغه ها رو از برنامه ی خودمون خارج کنیم.پروتکل OAuth در چنین محیطی تعریف شد. هدف اصلی این پروتکل انجام Delegated Authorization است. به فارسی می شه گفت &#34;اختیاردهی وکالتی&#34;. در ادامه منظور از &#34;اختیاردهی وکالتی&#34; رو کامل روشن می کنم.تاریخ این پروتکل بر می گرده به زمانی که شرکت Twitter برای اولین بار می خواست به برنامه های جانبیش امکان استفاده از اطلاعات کاربرها رو بده. بدیهیه که برای این کار اول باید از کاربر اجازه گرفت. بدون اجازه که نمی شه! بنابراین باید روندی تعریف می شد که در اون سه تا مولفه با هم درگیر باشن: کاربر، Twitter و برنامه ی جانبی (Third Party).اجازه برای استفاده از اطلاعات کاربرانِ Twitter&#xA;توجیه کنید که این رابطه به هیچ شکل دیگه ای نمی تونه باشه. برنامه ی جانبی نمی تونه بین کاربر و Twitter قرار بگیره. Twitter باید به صورت مستقیم از خودِ کاربر بپرسه که آیا به فلان برنامه اجازه ی دسترسی به اطلاعاتش رو می ده یا نه. این نیازِ خاص در نهایت منجر به تعریف پروتکلی به نام OAuth شد. اولین بار نسخه ی اولِ این پروتکل در سال 2010 منتشر شد و نسخه ی دوم هم که بسیار کامل تر بود و باعث منسوخ شدنِ نسخه ی اول شد، در سال 2012 انتشار یافت.از سال 2010 کم کم تکرارِ تصویرِ زیر در سایت های مختلف زیاد شد. بعد از Twitter،‏ Google و دیگران هم این جور سرویس ها رو ارائه دادن، تا به امروز... امروز حضور پروتکل های OAuth2.0 و OpenID Connect کمکِ بزرگی به همه ی برنامه نویس ها کرده. برنامه نویس ها می تونن بدون اینکه اصلا درگیر کد نوشتن برای قابلیت های Authentication و Authorization بشن از سرویس های آماده در این حوزه به راحتی استفاده کنن. واقعیت امر اینه که برای استفاده از این سرویس ها حتی نیاز به دونستنِ پروتکل هم نیست، اما به نظرم داشتنِ یه کم اطلاعات بد نیست!ورود با Facebook و Google&#xA;نقش هاتا اینجای کار فهمیدیم که OAuth2.0 پروتکلیه برای ایجاد امکان دسترسی محدود برای برنامه ها از منابع خاص. برای فهمِ بیش تر باید نقشِ هر کدوم از مولفه های موجود در این پروتکل رو خوب شناخت. مولفه ها رو همراه با همون مثال فروشگاهِ اینترنتی توضیح می دم. فرض کنید می خواید در یک فروشگاهِ اینترنتی به نام Coolest Shop عضو بشید و ازش خرید کنید. سایت بهتون امکان عضویت با حسابِ Google رو می ده. یعنی چیزی شبیه تصویر بالا. این سایت رو توی ذهنتون داشته باشید تا هر کدوم از مولفه های پروتکلِ OAuth2.0 رو توضیح بدم.یک) Resourceمنبعی که قراره به صورتِ کنترل شده در اختیار دیگران قرار بگیره. در سایتِ Coolest Shop حضورِ دکمه ی Sign in with Google به این معنیه که اگر شما بخواید و اجازش رو بدید، سایت می تونه اطلاعاتتون رو از Google دریافت کنه. مثل نام، عکس پروفایل، contactها و غیره. بنابراین اطلاعاتِ شما در این مثال Resource هستن. یا به بیان فنی تر Resourceها همون APIهایی هستن که Google از طریقشون این اطلاعات رو در اختیار دیگران می زاره.دو) Resource Ownerصاحبِ resourceها. در مثال فروشگاه صاحبِ resource شما هستید. یعنی شما مشخص می کنید که آیا Coolest Shop اجازه داره اطلاعاتتون رو از Google بخونه یا نه.سه) Clientهمون برنامه ای که قصد دسترسی به resource رو داره، یعنی سایتِ Coolest Shop.چهار) Authorization Serverسیستمی که در نهایت مجوزِ دسترسی رو با اجازه ی Resource Owner صادر می کنه. در این مثال Google این نقش رو داره.پنج) Resource Serverسیستمی که resource رو ارائه می ده. در این مثال این نقش رو هم Google بازی می کنه. توجه کنید لزوماً قرار نیست سیستمی که مجوز رو صادر می کنه (Authorization Server) با سیستمی که APIها رو در اختیار داره (Resource Server) یکی باشن.شش) Authorization Grantهمون مجوزی که Authorization Server صادر می کنه.اول به تصویر زیر دقت کنید. کلیاتِ ماجرا به همراهِ نقشِ هر کدوم از مولفه ها در این تصویر تشریح شده:عملکردِ OAuth2.0 به صورت کلی&#xA;توجه کنید که این تصویر فقط بیانی شماتیک از چگونگیِ کارکردِ این پروتکله. به صورت اجرایی و فنی شکلِ کارکردِ پروتکلِ OAuth2.0 شبیه این نیست! برای درک چگونگیِ کارکردِ باید با جزئیاتِ بیش تری آشنا شد. اولیش درکِ Flow یا Grant Type است.بررسیِ Flow یا Grant Typeدر پروتکلِ OAuth2.0 می شه با ترکیب های مختلف روندِ تعامل مولفه ها رو تغیر داد تا طبق نیازمون کار کنن. یه جورایی شبیه به یک پازل! به هر کدوم از این روندها یک flow می گن. در واقع flow مشخص می کنه که هر مولفه ای چگونه نقشش رو بازی می کنه. در پروتکلِ OAuth2.0 تعدادی flow تعریف شده که من فقط سه تاشون رو اینجا بررسی می کنم. باقیشون رو بلد نیستم!بررسیِ Authorization Codeفهمیدن این flow فهم باقی flowها رو راحت تر می کنه. نام دیگه ی این flow،‏ Code هم هست و بیش ترین کاربرد بین flowها رو داره. استفاده ی اصلیش هم برای برنامه های تحت وبه.فرض کنید قراره Coolest Shop با استفاده از این flow با Google کار کنه. اولین کار اینه که سایت رو به عنوان Client (به معنایی که در قسمتِ نقش ها توضیح دادم) به Google معرفی کنیم. بعد از انجامِ ثبتِ نام، Google دو تا مقدارِ مهم در اختیارمون قرار می ده: ClientID و Client Secret.‏ ClientID شناسه اییه که معرفِ سایته و Client Secret هم یه جورایی نقش گذرواژه رو بازی می کنه:ClientID=123&#xA;Client_Secret=abcبا انجام این کار Google سایتِ Coolest Shop رو به عنوان کسی که قراره از طرف دیگران به اطلاعات شون دسترسی داشته باشه، به رسمیت می شناسه. یعنی همون &#34;اختیارات وکالتی&#34; که در بالا گفتم. یعنی سایت Coolest Shop می تونه با اجازه ی کاربرها، اطلاعات شون رو از APIهای Google بخونه.مرحله ی بعد فرستادنِ یک پیام به سمتِ Google است مبنی بر اینکه فلان کاربر قصد داره به سایت Coolest Shop اجازه ی خوندن اطلاعاتش رو بده. زیاد سخت نیست. کافیه لینکی مثل زیر برای دکمه ی Sign in with Google بنویسیم:http://api.google.com/oauth/v2/auth?client_id=123&amp;redirect_uri=https://coolestshop.com/signinwithgoogle&amp;scope=contacts.read&amp;response_type=codeبعد از کلیک روی دکمه، مرورگر کاربر رو به صفحه ای برای وارد کردنِ اطلاعاتش راهنمایی می کنه که بهش می گن Consent Screen. حتما تا حالا هزار بار دیدید:Consent Screen&#xA;بعد از ورودِ اطلاعات توسط کاربر و کلیک روی دکمه ی Allow،‏ Google نام کاربری و گذرواژه رو در Authorization Server بررسی می کنه و بعد از اون مرورگر رو به redirect_uri هدایت می کنه و مقدار code رو هم به صورت query ارسال می کنه. مقدار redirect_uri در لینک بالا مشخص شده. در واقع خودمون به Google می گیم که بعد از انجامِ کار مرورگر رو به چه آدرسی هدایت کنه (3XX HTTP Response).دریافتِ access token به وسیله ی code&#xA;پارامترِ code چیست؟با redirect شدن عملا مرورگر آدرسِ ارسال شده از سمتِ Google رو برای سرورِ Coolest Shop می فرسته. سرور مقدار code رو بر می داره و برای تکمیل فرآیند، اینبار خودش به صورت مستقیم با Authorization Server ارتباط برقرار می کنه. Authorization Server مقدارِ code رو بررسی می کنه و در ازاش یک access token برای سرورِ Coolest Shop ارسال می کنه. access token کلیدِ حلِ ماجراست. یعنی همون وکالتیه که کاربر از سمتِ خودش به Client‏ (Coolest Shop) داده. حالا سرورِ Coolest Shop با استفاده از access token می تونه به APIهای Resource Server دسترسی داشته باشه.دسترسی به Resource با داشتنِ access token&#xA;به این ترتیب سرورِ Coolest Shop از این به بعد مقدارِ access token رو همراه با درخواست هاش برای Resource Server ارسال می کنه، Resource Server هم با بررسی کردنِ tokenها در ازاش resource ِ مورد نظر رو در اختیارِ Coolest Shop قرار می ده.به زبون دیگه بین Google‏ (Resource Server) و Coolest Shop‏ (Client) دو حالت امکان بروز داره. در یکی Coolest Shop به Google مراجعه می کنه و می گه &#34;اطلاعات یکی از کاربرات به نام علی رو نیاز دارم&#34;، سرور درخواست رو بررسی می کنه و می گه &#34;شرمنده، اطلاعات علی متعلق به خودشه، کسِ دیگه ای نمی تونه بهشون دسترسی داشته باشه&#34;. در حالت دیگه Coolest Shop به Google مراجعه می کنه و می گه &#34;به اطلاعات علی نیاز دارم، علی خودش بهم وکالت داده تا اونارو بخونم، اینم وکالتش: TOK...&#34;احتمالا دیگه مفهومِ Delegated Authorization (اختیاردهی وکالتی) کاملا روشن شده. مقدارِ access token یه چیزیه شبیه به این رشته:MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3حالا چند تا سوال پیش میاد:چرا از همون اول Authorization Server مقدارِ access token رو به مرورگر نمی فرسته تا کار یک سره بشه؟ به خاطر امنیت. فرض کنید یک نفر به طریقی بتونه مقدارِ access token رو از مرورگرِ شما بخونه. با این کار می تونه اون رو به Resource Server بده و خودش رو جای Coolest Shop جا بزنه! در بحث امنیت شبکه دو تا مفهوم وجود داره: Front Channel و Back Channel. در این flow قسمتی از فرآیند که بین مرورگر و سرورها اتفاق می افته در Front Channel و قسمتی هم که بین Authorization Server و سرورِ Coolest Shop اتفاق می افته در Back Channel انجام می شه. بنابراین به قسمتی که کاربر بهش دسترسی داره Front Channel و به قسمتی که کاربر بهش دسترسی نداره Back Channel می گن. Flowی Authorization Code با بهره گیری از هر دو دنیای Front Channel و Back Channel امن ترین راه حل رو فراهم می کنه. با این تفاسیر فرض کنید یک نفر مقدارِ Authorization Code رو بخونه و برای تحویل گرفتنِ access token اون رو به Authorization Server بده. فقط موضوع اینجاست که با دزدیدنِ code به هیچ جایی نمی رسه چون همراه با code باید مقدارِ ClientID و Client Secret رو هم بفرسته! شاید بگید خوب می تونه از ClientID و Client Secret ِ خودش استفاده کنه! اما این کار هم غیر ممکنه چون Authorization Server بلافاصله می فهمه که code برای یک ClientID ِ دیگه صادر شده. بنابراین رسیدن به access token به هیچ وجه کار راحتی نیست چون قسمتی از فرآیند در Back Channel انجام می شه که دم دست نیست و همچنین نیاز به مقدار Client Secret داره که به دست آوردنش تقریبا غیر ممکنه.منظور از scope چیه؟ این مقدار که در لینک بالا مشخص شده در حقیقت نوعِ اختیارات (permission) رو مشخص می کنه. دسترسی به اطلاعات کاربری Google به همین راحتی هم نیست! فرض کنید علی با خوندنِ اطلاعاتش مشکلی نداشته باشه، اما دلش نخواد کسی بتونه اطلاعاتش رو تغیر بده. وقتی که علی روی لینک بالا کلیک می کنه scope ِ مورد نیازِ Coolest Shop از طریقِ query برای Google ارسال می شه. Google هم در صفحه ی Consent از علی می پرسه که &#34;سایت Coolest Shop نیاز به فلان مجوز داره، آیا باهاش مشکلی نداری؟&#34;. بنابراین Google برای APIها scopeهای مختلفی داره. مثلا: contact.read ،‏ contact.write و غیره.تکلیف access token بعد از استفاده چی می شه؟ access tokenها تاریخ انقضا دارن. بعد از اینکه تاریخشون گذشت دیگه قابل استفاده نیستن. بنابراین مادامی که token معتبره Client می تونه ازش استفاده کنه. البته یه راه هایی برای تمدید انقضاشون وجود داره (refresh token) ولی در حد این مقاله نیست.بعد از اینکه کاربر برای اولین بار واردِ صفحه ی Consent شد و اطلاعاتش رو وارد کرد، سرور چطور کاربر رو شناسایی می کنه؟ آیا برای هر درخواستی که از سمتِ Coolest Shop به سمتِ Resource Server ارسال می شه کاربر باید یک بار خودش رو معرفی کنه؟ خیر. وقتی برای اولین بار سرورِ Coolest Shop مقدار access token رو گرفت، اون رو جایی ذخیره می کنه تا دفعات بعد ازش استفاده کنه و در این مدت کاربر رو هم به وسیله ی cookie شناسایی می کنه. البته این موضوع به پیاده سازی Coolest Shop بستگی داره، اگه بخواد کاربر تا اطلاعِ ثانوی توی سایت به صورت &#34;وارد شده&#34; باشه، باید براش یک cookie ارسال کنه و session رو حفظ کنه.کدوم دسته از برنامه ها از این flow استفاده می کنن؟ Web Appها. مثل برنامه هایی که با ASP.NET یا PHP پیاده شدن.موجودیتِ access token چطوری در اختیار Resource Server قرار می گیره؟ با قرارگیری در HEADER ِ هر HTTP Request. مقدار token همراه با کلمه ی Bearer در کلیدِ Authorization نوشته می شه:GET https://google.com/api/contacts?userid=ali-doustkani&#xA;Authorization: Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3بررسیِ Implicit Flowامن ترین Flow بین Flowهای بیان شده Authorization Code است اما استفاده ازش همیشه هم ممکن نیست! مثلا وقتی برنامه به صورتِ SPA‏ (Single Page Application) نوشته شده. چراکه در SPAها سرورِ میانی امکانِ دسترسی و نگهداریِ access tokenها رو نداره. این جور برنامه ها باید از flowی Implicit استفاده کنن. flowی Implicit مدل ساده شده ی authorization code است. در این flow اصلا از code استفاده نمی شه و access token به صورت مستقیم در اختیارِ Client قرار می گیره.دریافتِ access token به وسیله ی مرورگر&#xA;همون طور که از شکلِ بالا مشخصه این flow فقط از امکاناتِ Front Channel استفاده می کنه. حالا سوال اینجاست که آیا برنامه هایی که از این flow استفاده می کنن مشکل امنیتی دارن؟ نه لزوماً. با ایجاد محدودیت هایی می شه ضعف های این flow رو جبران کرد. مثلاً با کمتر کردنِ طولِ عمرِ access tokenها. یا استفاده از SSL و گواهینامه های معتبر. با انجام این ملاحظاتِ امنیتی در نهایت به یک راه حلِ قابل قبول و امن می رسیم. جایی واسه نگرانی نیست.بررسیِ Client Credentials Flowاین flow برای شرایطی تعبیه شده که کاربری در کار نیست! مثل وقتی که سرور باید برای تکمیل کارش به یک API خاص متصل بشه، یا به بیانی دیگه زمانی که تعاملات بین ماشین هاست. مثل تعامل Microserviceها با هم. بنابراین سرور قرار نیست با وکالت از سمتِ کسی به API دسترسی داشته باشه بلکه خودش به صورت مستقیم از API استفاده می کنه.در این حالت Client به جای اینکه با وکالتِ کاربر access token دریافت کنه، با استفاده از ClientID و Client Secret ِ مختص به خودش یک access token از Authorization Server درخواست می کنه و سپس با استفاده از اون token به صورت مستقیم با Resource Server وارد تعامل می شه.دریافتِ access token توسط خودِ clientتعددِ Resource Serverهااکثرِ برنامه های مدرن نیاز به سرویس های متعددی دارن. حتی خودِ برنامه ها هم این روزا مثل گذشته به صورت تک واحدی (monolith) پیاده سازی نمی شن، بلکه به تعدادی سرویس های مشخص و کوچیک شکسته می شن (مثل معماری Microservices). این حرف به این معنیه که تعدادِ Resource Serverها در حال افزایشه و این می تونه دسترسی به Resourceها رو دچار سختی هایی کنه، چراکه استفاده از resource در هر کدوم از Resource Serverها بدون اجازه ممکن نیست. همین طور اینکه برنامه ها برای انجامِ یک کارِ ساده نیاز به استفاده از resourceهای متعددی دارن. شاید این مسئله در ابتدا سخت و غیر قابل پیاده سازی به نظر بیاد اما با وجود پروتکلِ OAuth2.0 سخت نیست. بنابراین با یک بار صدورِ access token برنامه می تونه از APIهای متعددی در Resource Serverهای متعدد استفاده کنه.پروتکلِ OpenID Connect یا OIDCبا گذشت زمان پروتکلِ OAuth2.0 معروف تر شد و استفاده ازش هم خیلی زیادتر. شرکت ها از این پروتکل نه تنها برای Authorization بلکه برای Authentication هم استفاده می کردن. در حالی که OAuth2.0 در اصل برای Authorization طراحی شده بود. همین اتفاق باعث شد بین پیاده سازی های مختلف شرکت ها تفاوت هایی وجود داشته باشه. به عنوان مثال اگر می خواستید Authentication ِ سایتِ Coolest Shop رو هم به Google و هم Twitter وصل کنید احتمالا با دو پیاده سازی متفاوت رو به رو بودید. Google فرمولِ خاصِ خودش رو داشت و Twitter هم فرمولِ خاصِ خودش. دلیل این تفاوت به ذات پروتکلِ OAuth2.0 بر می گرده چون دغدغه ی OAuth2.0 اصلاً Authentication نیست بلکه Delegated Authorization است. همون طور که در ابتدای مقاله نوشتم، در Authorization ما با اختیارات کاربر طرفیم و در Authentication با هویتش. یک ورودِ ساده به یک سایت مسئله ی اختیارات نیست، بلکه تنها مسئله ی هویته. به زبون دیگه، برای ورود به سایتِ Coolest Shop کاربر باید فقط خودش رو معرفی کنه. یعنی بگه &#34;من کی هستم&#34;. Coolest Shop هم برای بررسی هویت کاربر نیاز به اطلاعاتش داره. در صورتی که چیزی با عنوان &#34;اطلاعات کاربر&#34; اصلا در پروتکلِ OAuth2.0 تعریف نشده! دلیل عدم هماهنگی بین شرکت های مختلف هم دقیقاً همین موضوع بود. هر کدوم از این شرکتا باید یه طوری OAuth2.0 رو سفارشی می کردن تا بتونه به یک شکلی &#34;اطلاعات کاربر&#34; رو به دستِ Client برسونه.نکته: پروتکلِ OpenID با OpenID Connect یکی نیست.پروتکلِ OIDC راه حلِ این مشکل بود. OIDC در حقیقت پروتکلِ جداگانه ای نیست. فقط یک لایه ی خیلی نازک روی OAuth2.0 است که آن را برای کاربردهای Authentication به شکلی استاندارد سازگار می کنه. در این پروتکل سه تا مولفه به OAuth2.0 اضافه شده که عبارتند از:یک) ID Token:‏ tokenی که حاوی اطلاعات کاربره.دو) User info endpoint:‏ اگر اطلاعاتِ مورد نیازمون درباره ی کاربر در ID Token موجود نبود از این endpoint در خواست می کنیم.سه) scopeهای استاندارد: مثل openid،‏ profile و email که ازشون برای دریافت اطلاعات متفاوت کاربر استفاده می شه.برای درک بیشتر به لینک زیر دقت کنید. بین این لینک و لینکِ قبلی تفاوتی وجود نداره اما مقدار scope ِ اون نشون می ده که پروتکلِ مورد استفادش OIDC است:https://authserver.com?redirect_url=XXX&amp;response_type=code&amp;scope=profileبر اساس اینکه چه scopeی رو برای Authorization Server می فرستیم اطلاعات مختلفی در ID Token برای Client ارسال می شه. مثلا اگه scope ارسالی profile باشه اطلاعاتی مشابه زیر باید در token برگرده:name, family_name, given_name, middle_name, nickname, pictureبررسیِ ID Tokenپاسخِ درخواست OIDC،‏ access token نیست. بلکه ID Token است. تفاوت شان چیست؟ ID Tokenها از یک فرمت مشخص به نام JSON Web Token‏ (JWT - جات هم خونده می شه) ساخته می شن. هر JWT از سه قسمت تشکیل شده:یک) header: یک سری اطلاعاتِ کلی درباره ی token رو در خودش داره. مثل نامِ الگوریتمِ رمزنگاری و غیره.دو) payload: اطلاعات کاربر. اینکه چه اطلاعاتی در payload قرار می گیره بستگی به scope ِ درخواستی داره. به اطلاعاتِ کاربر در این بخش claim هم گفته می شه.سه) signature: امضای دیجیتال یا در حقیقت مقدارِ رمز شده ی کل token. به هر حال باید همیشه مطمئن بود که token ِ صادر شده واقعاً از طرف Authorization Server باشه وگرنه هر کسی می تونه با ایجادِ یک token ِ جعلی اطلاعاتِ کاربرِ دیگه ای رو به ما بده. چگونگی امضای token بستگی به الگوریتمِ رمزنگاری داره. این الگوریتم هم می تونه symmetric باشه و هم asymmetric، اما معمولاً از الگوریتم های asymmetric استفاده می شه تا دچار دردسرِ دسترسی به secret key نشیم.برای مثال یک JWT به این شکله:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cاین مقدار رو در سایت jwt.io کپی کنید تا محتویاتش رو ببینید. مقدار header و payload و signature با نقطه از هم جدا شدن.کلام آخرچیزی که توی این پست نوشتم کلیات کارکردِ OAuth2.0 و OIDC است. تقریبا به هر شکلی که بخواید از OAuth2.0 استفاده کنید اینا حداقل چیزیه که باید بدونید. از یه login ِ ساده با Google گرفته تا پیاده سازیِ یک Authorization Server.پیشنهاد من در رابطه با مسائل مربوط به مدیریت کاربرها و مسائل امنیتی اینه که تا حد ممکن این چیزا رو خودتون پیاده سازی نکنید و از سرویس های آماده مثل Auth0 استفاده کنید. به چند دلیل: یکی اینکه پیاده سازیِ پروتکل های امنیتی کارِ خیلی سختیه و احتمالِ اینکه اشتباهاتش جبران ناپذیر باشه خیلیه و دوم اینکه زمان و انرژی خودتون رو صرفِ مسئله ای می کنید که برنامه تون قراره حل کنه و با این کار کل مبحث مربوط به authorization رو برون سپاری می کنید.به هر حال اگر به دلیلی نیاز به پیاده سازی پروتکل OAuth2.0 داشتید حتما به farmeworkهایی مثل IdentityServer رو بررسی کنید.</description><category>علی دوستکانی</category><author>علی دوستکانی</author><pubDate>Wed, 02 Oct 2019 22:46:06 +0330</pubDate></item><item><title>استفاده از React و Parcel همراه با Razor</title><link>https://virgool.ir/@ali-doustkani/استفاده-از-react-و-parcel-همراه-با-razor-gcreqpv97ch9</link><description>حوصله ی خوندن نداری؟اگه حوصله نداری پست رو کامل بخونی، چکیدش به این قراره: برای ایجاد یک وب سایت که بعضی صفحاتش React باشه و بعضی Razor چند تا مسئله رو باید در نظر گرفت. یکی اینکه خروجیِ build ِ پروژه ی React باید در اختیار ASP.NET Core قرار بگیره تا مثل فایل های static اون ها رو در اختیار client بذاره. نکته ی بعد هم مربوط می شه به پیکربندی routing. باید مسیرها رو طوری تنظیم کرد تا فایل های خروجیِ React طبق مسیرهای تعریف شده در اختیار client قرار بگیرن. هر دوی این ها در فایل Startup.cs مشخص می شن. مسیر خروجی React در مشخصه ی RootPath تعریف شده و URL ِ دسترسی به آن ها هم توسط متدِ‏ ()Map مشخص می شه.مقدمهاین روزا تعداد سایت هایی که در سمتِ front-end مبتنی بر Javascript نوشته می شه مثل قارچ زیاد شده. دلایل مختلفی داره. مثلاً اینکه توقع آدما از front-end بیش تر شده. خیلی از کارهایی که این روزا سمتِ front-end انجام می شه قبلاً اصلا انجام نمی شد، یا کاملاً سمتِ سرور انجام می شد. اما اگه پیچیدگی و نیازهای خاص رو بذاریم کنار بازم با کلی سایت طرفیم که این طوری ساخته شدن. خیلی از این سایت ها حتی نیاز چندانی هم به اینکه مثلا از React استفاده کنن ندارن. کلی وبلاگ یا سایت هایی با محتوای ساده می تونید پیدا کنید که اینجوری هستن. به نظرم این به خاطر علاقه ی برنامه نویساست. این تکنولوژی ها (React, Vue, Angular و باقی) از محبوبیت بالایی برخوردارن. محبوبیت زیاد هم باعث استفاده ی زیاد می شه.اما گاهی هم پیش میاد که دوست داریم سمتِ front-end مثلاً با React ساخته بشه، اما امکانش نیست! مثل پروژه های قدیمی. مثل پروژه هایی که فقط قسمتی ازشون باید با Javascript پیاده بشه. در این پست یک مثال کوچیک از همچین حالتی رو اجرا کردم. البته انجام این کار اصلاً پیچیده نیست. اما شاید برای شروع یه مقدار گیج کننده باشه. حداقل برای من که این طور بود. در این پست یک مثال ساده رو با استفاده از React و ASP.NET Core MVC (یا همون Razor) انجام دادم.راه حلقبل از شروع به کد نوشتن یه کم توضیح درباره ی مشکل و راه حال های موجود بدم؛ برعکس صفحاتِ Razor که سرور اون ها رو هر وقت که درخواستی دریافت می کنه می سازه، صفحه/صفحاتِ React این جوری نیستن. در حقیقت محلِ render برای صفحاتِ ASP.NET Core سمتِ سروره و برای React سمتِ client. البته امکانِ render ِ صفحاتِ React در سمتِ سرور هم وجود داره اما خارج از حد این پسته. بنابراین برای حل این مسئله باید چند تا کار انجام بشه:یک پروژه ی React ِ مجزا از ASP.NET Core ایجاد می کنیم که خودش در یک صفحه ی HTML ِ جدا اجرا  بشه. فرض کنیم اسمش Index.html هست.تمام پروژه ی React رو build می کنیم. عملا با انجام این کار به یک فایلِ HTML (که build شده ی همون Index.html است) و یه مشت فایلِ دیگه (مثل js و css و غیره) می رسیم.فایل های build شده رو به عنوان فایل های static در اختیار ASP.NET Core قرار می دیم.باید ASP.NET Core رو طوری تنظیم می کنیم که در یک آدرسِ مشخص Index.html رو به عنوان صفحه ی اصلی بازگشت بده و برای آدرس های دیگه عملکردِ عادیِ Mvc رو داشته باشه.اجرای front-end چالش خاصی نداره. اما پیاده سازی قسمتِ ASP.NET Core یه کم غیر عادیه. چون به چند طریق می شه با فایل های build شده ی React برخورد کرد. اینجا راه حل هایی که به نظرم می رسه رو آوردم:1- استفاده از middleware ِ‏ StaticFiles و تلقی فایل های React مثل باقی فایل های static.2- استفاده از middlewareهای SpaStaticFiles و Spa.به گزینه ی اول ایراد خاصی وارد نیست اما موقع پیاده سازیش دچار مشکلی می شیم که حلش تقریبا کار سختیه. فرض کنید قراره تمام فایل های صفحه ی admin/ رو در مسیر wwwroot/admin کپی کنیم و بعدشم برنامه رو این طوری configure کنیم:public void Configure(IApplicationBuilder app, IHostingEnvironment env)&#xA;{&#xA;  if (env.IsDevelopment())&#xA;    {&#xA;      app.UseDeveloperExcepti();&#xA;    }&#xA;    app.UseStaticFiles();            &#xA;    app.UseMvcWithDefaultRoute();&#xA;}حالا هر وقت که کاربر توی مرورگرش آدرسِ admin/ رو وارد کنه سرور به طور پیش فرض فایلِ Index.html ِ موجود توی wwwroot/admin رو ارسال می کنه. تا اینجای کار به نظر می رسه که همه چیز مرتبه. اما موضوع اینجاست فایلِ Index.html تنها فایلی نیست که باید به مرورگرِ کاربر برسه! این فایل به فایل های دیگه ای ارجاع کرده که مرورگر بلافاصله اون ها رو تقاضا می کنه تا سرور براش ارسال کنه. به محتویاتِ فایلِ زیر و خطوطِ پررنگ شده دقت کنید:&lt;!DOCTYPE html&gt;&#xA;&lt;html lang=&#34;en&#34;&gt;&#xA;  &lt;head&gt;&#xA;      &lt;meta charset=&#34;UTF-8&#34;&gt;&#xA;          &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;&#xA;          &lt;meta http-equiv=&#34;X-UA-Compatible&#34; content=&#34;ie=edge&#34;&gt;&#xA;          &lt;title&gt;Blog Admin&lt;/title&gt;&#xA;          &lt;link rel=&#34;stylesheet&#34; href=&#34;/AdminClient.e31bb0bc.css&#34;&gt;&#xA;  &lt;/head&gt;&#xA;  &lt;body&gt;&#xA;    &lt;div id=&#34;root&#34;&gt;&lt;/div&gt;&#xA;    &lt;script src=&#34;/AdminClient.e31bb0bc.js&#34;&gt;&#xA;  &lt;/body&gt;&#xA;&lt;/html&gt;نکته: احتمال داره نامِ فایل های ایجاد شده روی ماشین شما متفاوت باشه. پسوندِ عددیِ توی نام فایل ها رو Parcel برای CacheBusting می سازه.وقتی مرورگر فایلِ Index.html رو دریافت می کنه طبق قاعده فایل های دیگه ای که برای اجرا به اون ها وابسته است رو هم از سرور درخواست می کنه. اما سرور نمی تونه پاسخی بده، چون اصلا فایلی با نامِ AdminClient.e31bb0bc.css در پوشه ی wwwroot وجود نداره! توجه کنید که تمام فایل ها (به خاطر ایجاد URL ِ‏ admin/) در داخل پوشه ی admin قرار دارن! برای اینکه با این مشکل کنار بیایم باید bundler رو طوری تنظیم کنیم که به جای تولید URLهای root آن ها رو طور دیگه ای تولید کنه. مثلا این طوری:&lt;link rel=&#34;stylesheet&#34; href=&#34;/admin/AdminClient.936ffee5.css&#34; /&gt;انجامِ این تنظیمات اولاً راحت نیست و دوماً با فرض اینکه انجام هم دادیم، با این کار تنظیماتِ مربوط به URLهای برنامه رو دو-دسته کردیم. یعنی هم در کدهای سمتِ سرور و هم در کدهای front-end. این کار احتمالا دردسرِ نگهداری ایجاد می کنه.گزینه ی دوم گزینه ی بهتریه. در ASP.NET Core دو تا middleware تعبیه شده که این مشکلات رو به راحتی حل می کنن. یکی SpaStaticFiles و دیگری Spa. اولی مشکل فایل های ارجاعی رو حل می کنه. یعنی فایلی که با URL ِ‏ root درخواست شده رو از پوشه ی مشخص شده از RootPath پیدا می کنه و اون رو برگشت می ده. دومی هم برای ارسال صفحه ی اصلی استفاده می شه.public void ConfigureServices(IServiceCollection services)&#xA;{&#xA;  services.AddMvc();&#xA;  services.AddSpaStaticFiles(spa =&gt; spa.RootPath = &#34;wwwroot/admin&#34;);&#xA;}&#xA;public void Configure(IApplicationBuilder app, IHostingEnvironment env)&#xA;{&#xA;  if (env.IsDevelopment())&#xA;  {&#xA;    app.UseDeveloperExcepti();&#xA;  }&#xA;  app.UseSpaStaticFiles();&#xA;  app.UseMvcWithDefaultRoute();&#xA;  app.Map(&#34;/admin&#34;, adminApp =&gt;&#xA;  {&#xA;    adminApp.UseSpa(spa =&gt; { });&#xA;  });&#xA;}از middleware ِ‏ Spa اساساً برای ساخت برنامه های Single Page استفاده می شه. یعنی با صرف نظر از آدرسی که کاربر در مرورگرش وارد می کنه این middleware همواره صفحه ی Index.html رو برگشت می ده. به این ترتیب فقط یک کار می مونه که باید انجام داد: این middleware فقط زمانی کار کنه که URL برابر با admin/ باشه. برای این کار از ()Map استفاده می کنیم که توضیحش در ادامه اومده.چه خواهیم ساخت؟یک وبلاگ ساده که صفحه ی اصلیش و صفحه ی مربوط به پست هاش به صورت عادی از ASP.NET Core MVC (یا در واقع Razor) استفاده می کنه و صفحه ی adminاش با React ساخته شده. برای اجرای React از Parcel استفاده کردم. البته از ابزارهای دیگه هم می شه استفاده کرد. به خودتون بستگی داره. Parcel همیشه انتخابِ اولِ منه چون خیلی سریع و ساده راه اندازی می شه. شما می تونید از Webpack،‏ Create-react-app،‏ nano-react-app یا هر چیز دیگه ای استفاده کنید. محدودیتی وجود نداره. در تصویر زیر شِمایی از چینشِ ابزارها کنار هم نشون داده شده. چگونگی چینش ابزارها کنار هم پیاده سازیقبلِ شروع، از نصبِ اینها مطمئن باشید: net core. ورژنِ 2.0 یا بیش تر، VSCode،‏ NodeJS و npm.اول یک پوشه با نام blog-example بسازید. داخلش هم یک پوشه ی دیگه به نام src. از سمتِ سرور شروع می کنیم؛ یعنی ساختِ Viewها، Controllerها و API. در شاخه ی blog-example/src دستور زیر رو اجرا کنید:dotnet new web --name Blogاین دستور یک پروژه ی ASP.NET Core ِ خام ایجاد می کنه. حالا شاخه ی src/Blog رو با VSCode باز کنید. اولین کار، تنظیم پیکربندیِ برنامه و مسیرهاست. فایل Startup.cs رو طبق کد زیر بنویسید:// Startup.cs File&#xA;using Microsoft.AspNetCore.Builder;&#xA;using Microsoft.AspNetCore.Hosting;&#xA;using Microsoft.Extensions.DependencyInjection;&#xA;&#xA;namespace Blog&#xA;{&#xA;  public class Startup&#xA;  {&#xA;    public void ConfigureServices(IServiceCollection services)&#xA;    {&#xA;      services.AddMvc();&#xA;      services.AddSpaStaticFiles(spa =&gt;&#xA;      {&#xA;        spa.RootPath = &#34;wwwroot/admin&#34;&#xA;      });&#xA;    }&#xA;&#xA;    public void Configure(IApplicationBuilder app, IHostingEnvironment env)&#xA;    {&#xA;      if (env.IsDevelopment())&#xA;      {&#xA;        app.UseDeveloperExcepti();&#xA;      }&#xA;      app.UseSpaStaticFiles();&#xA;      app.UseMvcWithDefaultRoute();&#xA;      app.Map(&#34;/admin&#34;, adminApp =&gt;&#xA;      {&#xA;        adminApp.UseSpa(spa =&gt; { });&#xA;      });&#xA;    }&#xA;  }&#xA;}در متدِ ConfigureServices مشخصه ی RootPath تعین شده. این مشخصه محل فایل های خروجیِ React رو تعین می کنه. من محل این فایل ها رو جایی داخل پوشه ی wwwroot مشخص کردم تا برای publish ِ برنامه دچار دشواری نشم. اما به هر حال محل این فایل ها می تونه هر جایی باشه.نکته: بهتره از هر نوع SCMی که استفاده می کنید، اون رو طوری تنظیم کنید که فایل های این پوشه هیچ وقت commit نشن.همون طور که گفتم متدِ ()Map نقشِ مهمی داره. وظیفه ی این متد ایجادِ یک شاخه ی فرعی داخلِ pipeline است. یعنی اگر یک request از middleware ِ‏ MVC گذشت و پردازش نشد، در این مرحله آدرسش بررسی می شه تا اگه URLاش admin/ بود به middleware ِ‏‏ Spa فرستاده بشه. اگر هم URL برابر admin/ نبود طبق قاعده 404 بازگشت داده بشه. در حقیقت با ایجاد شاخه های فرعی requestها به صورت مشروط توسط middlewareها پردازش می شن.نکته ی مهم اینه که بدون ایجاد شاخه ی فرعی هم می شه ()UseSpa رو نوشت، ولی اینجوری هر آدرسی که MVC نتونست شناسایی کنه باعث بازگشت صفحه ی admin/ می شه. بعید می دونم این حالت برای کسی مطلوب باشه، اما اگه این چیزیه که می خواید، نیازی به استفاده از ()Map ندارید...در متدِ ()UseSpa می شه تنظیماتی رو اعمال کرد. مثل استفاده از proxy برای اتصال به سرورِ development، اما من تمایلی برای ساخت برنامه به این شکل ندارم. بهتره که روندِ ساخت front-end و back-end رو طوری طراحی کنید که موقع نوشتن شون نیازی به حضور همدیگه نداشته باشن. برای عملی کردن این ایده نیاز دارید که برای front-end تست های unit و integration بنویسید. در نهایت برای اینکه از عملکردِ front-end و back-end در کنار هم مطمئن باشید چند تا تست end-to-end هم بنویسید.ساختِ Modelدر پوشه ی Blog یک پوشه ی جدید با نام Model ایجاد کنید و فایل های زیر رو داخلش بنویسید.فایل MemoryStore.cs:// MemoryStore.cs File&#xA;using System.Collections.Generic;&#xA;using System.Linq;&#xA;namespace Blog.Model&#xA;{&#xA;  public static class MemoryStore&#xA;  {&#xA;    static MemoryStore()&#xA;    {&#xA;      Store = new List&lt;Post&gt;{&#xA;        new Post {&#xA;          Id = 1,&#xA;          Title = &#34;Being friend with Regular Expressions&#34;,&#xA;          Tags = &#34;.net, C#, regex&#34;,&#xA;          Content = &#34;&lt;p&gt;Regular expressions are used for string processing.&lt;/p&gt;&#34;&#xA;        },&#xA;        new Post {&#xA;          Id = 2,&#xA;          Title = &#34;Using React Hooks&#34;,&#xA;          Tags = &#34;front-end, react, js&#34;,&#xA;          Content = &#34;&lt;p&gt;You can use react hooks to get the most out of functions in react.&lt;/p&gt;&#34;&#xA;          }&#xA;      };&#xA;    }&#xA;&#xA;    public static readonly List&lt;Post&gt; Store;&#xA;    &#xA;    public static void Add(Post post)&#xA;    {&#xA;      post.Id = Store.Max(x =&gt; x.Id) + 1;&#xA;      Store.Add(post);&#xA;    }&#xA;    &#xA;    public static void Update(Post post)&#xA;    {&#xA;      var target = Store.Single(x =&gt; x.Id == post.Id);&#xA;      target.Title = post.Title;&#xA;      target.Tags = post.Tags;&#xA;      target.Content = post.Content;&#xA;    }&#xA;&#xA;    public static void Remove(int id)&#xA;    {&#xA;      Store.RemoveAll(x =&gt; x.Id == id);&#xA;    }&#xA;    &#xA;  }&#xA;}فایل Post.cs:// Post.cs File&#xA;using System.Linq;&#xA;using System.Collections.Generic;&#xA;namespace Blog.Model&#xA;{&#xA;  public class Post&#xA;  {&#xA;    public int Id { get; set; }&#xA;    public string Title { get; set; }&#xA;    public string Tags { get; set; }&#xA;    public string Content { get; set; }&#xA;    public IEnumerable&lt;string&gt; GetTags() =&gt;&#xA;      string.IsNullOrEmpty(Tags) ?&#xA;      Enumerable.Empty&lt;string&gt;() :&#xA;      Tags.Split(&#39;,&#39;).Select(x =&gt; x.Trim());&#xA;  }&#xA;}برای ساده تر شدنِ کار، کلِ پست ها رو در یک لیستِ static ذخیره کردم. برای تمرین، خودتون این پروژه رو با EFCore پیاده کنید.ساختِ Controllerهایک پوشه ی دیگه به نام Controllers ایجاد کنید و فایل های زیر رو داخلش بنویسید:// HomeController.cs File&#xA;using Blog.Model;&#xA;using Microsoft.AspNetCore.Mvc;&#xA;using System.Linq;&#xA;&#xA;namespace Blog.Controllers&#xA;{&#xA;  public class HomeController : Controller&#xA;  {&#xA;    public IActionResult Index() =&gt; View(MemoryStore.Store);&#xA;    public IActionResult Post(int id) =&gt; View(MemoryStore.Store.Single(x =&gt; x.Id == id));&#xA;  }&#xA;}وظیفه ی این Controller ایجادِ صفحه ی اصلی و صفحه ی مربوط به نمایشِ پست هاست. کار خاصی نمی کنه فقط پست ها رو از MemoryStore می خونه و برای نمایش به View می ده.// PostController.cs File&#xA;using Blog.Model;&#xA;using Microsoft.AspNetCore.Mvc;&#xA;using System.Linq;&#xA;namespace Blog.Controllers&#xA;{&#xA;  [ApiController]&#xA;  [Route(&#34;/api/post&#34;)]&#xA;  public class PostController : ControllerBase&#xA;  {&#xA;    [HttpGet]&#xA;    public IActionResult GetAll() =&gt; Ok(MemoryStore.Store);&#xA;    &#xA;    [HttpGet(&#34;{id}&#34;)]&#xA;    public IActionResult Get(int id) =&gt; Ok(MemoryStore.Store.Single(x =&gt; x.Id == id));&#xA;    &#xA;    [HttpPut]&#xA;    public IActionResult SaveChanges(Post post)&#xA;    {&#xA;      if (post.Id == 0)&#xA;      {&#xA;        MemoryStore.Add(post);&#xA;        return CreatedAtAction(nameof(Get), new { Id = post.Id });&#xA;      }&#xA;      MemoryStore.Update(post);&#xA;      return Ok();&#xA;    }&#xA;  }&#xA;}این Controller،‏ API ِ مورد نیاز رو پیاده سازی می کنه. ساختارِ EndPoint به این شکله:GET /api/post : لیستی از پست ها رو برگشت می ده.GET /api/post/12 : پستی که شناسه اش 12 است رو بر می گردونه.PUT /api/post : یک پست رو درج/ویرایش می کنه؛ اگه شناسه ی پست ارسالی 0 بود اون رو درج می کنه و شناسه ی جدید رو با کدِ 201 پاسخ می ده و اگه شناسه 0 نبود اون رو ویرایش می کنه و کدِ 200 رو پاسخ می ده.ساختِ Viewهاپوشه ی Views/Home رو داخلِ Blog ایجاد کنید و بعد فایل های زیر رو بنویسید:// Index.cshtml File&#xA;@using Blog.Model&#xA;@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers&#xA;@model IEnumerable&lt;Post&gt;&#xA;&lt;!DOCTYPE html&gt;&#xA;&lt;html&gt;&#xA;  &lt;head&gt;&#xA;    &lt;title&gt;My Blog&lt;/title&gt;&#xA;    &lt;style&gt;&#xA;      section{&#xA;        width: 600px;&#xA;        margin: 0 auto;&#xA;      }&#xA;      .blog-container{&#xA;        display: block;&#xA;        background: #eee;&#xA;        padding: 10px 20px;&#xA;        font-size: 25px;&#xA;        margin-bottom: 2px;&#xA;        text-decoration: none;&#xA;      }&#xA;      span{&#xA;        font-size: 15px;&#xA;        background: #111;&#xA;        color: white;&#xA;        padding: 2px 5px;&#xA;      }&#xA;    &lt;/style&gt;&#xA;  &lt;/head&gt;&#xA;  &lt;body&gt;&#xA;    &lt;section&gt;&#xA;      &lt;h1&gt;Welcome to my blog!&lt;/h1&gt;&#xA;      @foreach(var post in Model)&#xA;      {&#xA;        &lt;a asp-action=&#34;Post&#34; asp-route-id=&#34;@post.Id&#34; class=&#34;blog-container&#34;&gt;&#xA;          &lt;div&gt;@post.Title&lt;/div&gt;&#xA;          @foreach(var tag in post.GetTags())&#xA;          {&#xA;            &lt;span&gt;@tag&lt;/span&gt;&#xA;          }&#xA;        &lt;/a&gt;&#xA;      }&#xA;    &lt;/section&gt;&#xA;  &lt;/body&gt;&#xA;&lt;/html&gt;وظیفه ی این فایل نمایشِ صفحه ی اصلیِ برنامه ست. کار سنگینی انجام نمی ده. فقط عنوان پست ها رو همراه با تگ هاشون لیست می کنه. یه کمی هم CSS داخلش نوشتم که کم تر زشت باشه!// Post.cshtml File&#xA;@using Blog.Model&#xA;@model Post&#xA;&lt;!DOCTYPE html&gt;&#xA;&lt;html&gt;&#xA;  &lt;head&gt;&#xA;    &lt;title&gt;@Model.Title&lt;/title&gt;&#xA;    &lt;style&gt;&#xA;      article{&#xA;        margin: 20px;&#xA;      }&#xA;          &lt;/style&gt;&#xA;  &lt;/head&gt;&#xA;  &lt;body&gt;&#xA;    &lt;article&gt;&#xA;      &lt;h1&gt;@Model.Title&lt;/h1&gt;&#xA;      @Html.Raw(Model.Content)&#xA;    &lt;/article&gt;&#xA;  &lt;/body&gt;&#xA;&lt;/html&gt;این View وظیفه ی نمایشِ محتویاتِ یک پست رو داره. با دستور زیر برنامه رو اجرا کنید و نتیجه رو ببینید:dotnet runساختِ صفحه ی Admin با Reactداخل پوشه ی src/ یک پوشه ی دیگه با نام AdminClient ایجاد کنید. این پوشه رو با VSCode باز کنید و دستور زیر رو داخل Terminal اجرا کنید تا فایلِ package.json ایجاد بشه و کتابخونه های مورد نیازش دانلود بشه:npm init -y&#xA;npm install parcel-bundler --save-dev&#xA;npm install react react-domحالا فایل های زیر رو بنویسید:&lt;!-- index.html File --&gt;&#xA;&lt;!DOCTYPE html&gt;&#xA;&lt;html lang=&#34;en&#34;&gt;&#xA;  &lt;head&gt;&#xA;      &lt;meta charset=&#34;UTF-8&#34; /&gt;&#xA;      &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34; /&gt;&#xA;      &lt;meta http-equiv=&#34;X-UA-Compatible&#34; content=&#34;ie=edge&#34; /&gt;&#xA;      &lt;title&gt;Blog Admin&lt;/title&gt;&#xA;  &lt;/head&gt;&#xA;  &lt;body&gt;&#xA;    &lt;div id=&#34;root&#34;&gt;&lt;/div&gt;&#xA;    &lt;script src=&#34;index.js&#34;&gt;&#xA;  &lt;/body&gt;&#xA;&lt;/html&gt;این فایل، فایلِ اصلیِ برنامه ست که توسط Parcel اجرا می شه. نتیجه ی build ِ این فایل توسط ASP.NET Core از آدرسِ admin/ بازگشت داده می شه. کار اصلیِ index.html ارجاع به فایلِ index.js است که به این شکله:// index.js File&#xA;import React from &#34;react&#34;&#xA;import ReactDOM from &#34;react-dom&#34;&#xA;import Post from &#34;./main&#34;&#xA;ReactDOM.render(&lt;Post /&gt;, document.getElementById(&#34;root&#34;));در این فایل component ِ‏ main خونده و render می شه. این برنامه از سه component تشکیل شده:editor: وظیفش ویرایش یا درج پست هاست.post-list: وظیفش نمایش لیستی از پست هاست. کاربر می تونه بین پست ها پیمایش کنه و هر کدوم که نیاز به تغیر داشتن رو در component ِ‏ editor ویرایش کنه.main: وظیفش دریافتِ اطلاعات از API و مدیریت کردنِ دو component ِ دیگه ست. البته main به صورت مستقیم با APIها درگیر نیست بلکه از طریق services.js این کار رو انجام می ده.در تصویر زیر شمایی از componentهای پروژه نمایش داده شده:componentهای React// editor.js File&#xA;import React, { useRef } from &#34;react&#34;&#xA;const Editor = ({ post, onSave,  }) =&gt; {&#xA;  const titleRef = useRef();&#xA;  const tagsRef = useRef();&#xA;  const contentRef = useRef();&#xA;  const save = e =&gt; {&#xA;    e.preventDefault();&#xA;    onSave({ &#xA;      id: post.id,&#xA;      title: titleRef.current.value,&#xA;      tags: tagsRef.current.value,&#xA;      content: contentRef.current.value&#xA;    });&#xA;  };&#xA;  const cancel = e =&gt; {&#xA;    e.preventDefault();&#xA;    ();&#xA;  };&#xA;  return (&#xA;    &lt;div className=&#34;form-container&#34;&gt;&#xA;    &lt;h1&gt;Enter Post&lt;/h1&gt;&#xA;    &lt;form&gt;&#xA;      &lt;div className=&#34;control-group&#34;&gt;&#xA;        &lt;label htmlFor=&#34;title&#34;&gt;Title&lt;/label&gt;&#xA;        &lt;input&#xA;          id=&#34;title&#34;&#xA;          defaultValue={post.title}&#xA;          ref={titleRef}&#xA;          placeholder=&#34;Enter the post title&#34;&#xA;          autoFocus/&gt;&#xA;      &lt;/div&gt;&#xA;      &lt;div className=&#34;control-group&#34;&gt;&#xA;        &lt;label htmlFor=&#34;tags&#34;&gt;Tags&lt;/label&gt;&#xA;        &lt;input id=&#34;tags&#34; defaultValue={post.tags} ref={tagsRef} /&gt;&#xA;      &lt;/div&gt;&#xA;      &lt;div className=&#34;control-group&#34;&gt;&#xA;        &lt;label htmlFor=&#34;content&#34;&gt;Content&lt;/label&gt;&#xA;        &lt;textarea id=&#34;content&#34; defaultValue={post.content} ref={contentRef} /&gt;&#xA;      &lt;/div&gt;&#xA;      &lt;button ={save}&gt;Save&lt;/button&gt;&#xA;      &lt;button ={cancel}&gt;Cancel&lt;/button&gt;&#xA;    &lt;/form&gt;&#xA;  &lt;/div&gt;&#xA;  );&#xA;};&#xA;export default Editor;فایل post-list.js:// post-list.js File&#xA;import React from &#34;react&#34;&#xA;const toTags = tagString =&gt;&#xA;  tagString&#xA;    .split(&#34;,&#34;)&#xA;    .map(x =&gt; x.trim())&#xA;    .map(x =&gt; &lt;span key={x}&gt;{x}&lt;/span&gt;);&#xA;const PostList = ({ posts, ed, onAddNew }) =&gt; (&#xA;  &lt;div className=&#34;item-container&#34;&gt;&#xA;    &lt;h1&gt;Posts&lt;/h1&gt;&#xA;    &lt;button ={onAddNew}&gt;Add New Post&lt;/button&gt;&#xA;    &lt;ul&gt;{posts.map(item =&gt; (&#xA;      &lt;li key={item.id} ={() =&gt; ed(item.id)}&gt;&#xA;        &lt;div&gt;{item.title}&lt;/div&gt;&#xA;        &lt;div&gt;{toTags(item.tags)}&lt;/div&gt;&#xA;      &lt;/li&gt; ))}&#xA;    &lt;/ul&gt;&#xA;  &lt;/div&gt;&#xA;);&#xA;export default PostList;فایل main.js:// main.js File&#xA;import React, { useEffect, useReducer } from &#34;react&#34;&#xA;import Editor from &#34;./editor&#34;&#xA;import PostList from &#34;./post-list&#34;&#xA;import { initial, reducer } from &#34;./state&#34;&#xA;import { getPostItems, saveChanges, newPost } from &#34;./services&#34;&#xA;&#xA;const Main = () =&gt; {&#xA;  const [state, dispatch] = useReducer(reducer, initial);&#xA;  useEffect(() =&gt; {&#xA;    (async () =&gt; dispatch({ type: &#34;START&#34;, posts: await getPostItems() }))();&#xA;  }, []);&#xA;  &#xA;  const showPost = async id =&gt;&#xA;    dispatch({ type: &#34;SHOW_POST&#34;, post: state.posts.find(x =&gt; x.id === id) });&#xA;  &#xA;  const addPost = () =&gt; dispatch({ type: &#34;SHOW_POST&#34;, post: newPost() });&#xA;  &#xA;  const saveEdit = async post =&gt; {&#xA;    const saveResult = await saveChanges(post);&#xA;    dispatch({ type: &#34;SAVE_DONE&#34;, saveResult });&#xA;  };&#xA;  &#xA;  const cancelEdit = () =&gt; dispatch({ type: &#34;CANCEL_EDIT&#34; });&#xA;  &#xA;  return state.page === &#34;POST_LIST&#34; ? (&#xA;    &lt;PostList posts={state.posts} ed={showPost} onAddNew={addPost} /&gt;&#xA;    ) : state.page === &#34;POST&#34; ? (&#xA;    &lt;Editor post={state.currentPost} onSave={saveEdit} ={cancelEdit} /&gt;&#xA;    ) : null;&#xA;  };&#xA;  &#xA;  export default Main;فایل services.js:// services.js File&#xA;const getPostItems = async () =&gt; {&#xA;  const response = await fetch(&#34;/api/post&#34;);&#xA;  return await response.json();&#xA;};&#xA;const saveChanges = async post =&gt; {&#xA;  const response = await fetch(&#34;/api/post&#34;, {&#xA;    method: &#34;put&#34;,&#xA;    body: JSON.stringify(post),&#xA;    headers: {&#xA;      &#34;Content-Type&#34;: &#34;application/json&#34;&#xA;    }&#xA;  });&#xA;  if (response.status === 201) {&#xA;    post.id = (await response.json()).id;&#xA;    return { type: &#34;add&#34;, post };&#xA;  } else if (response.status === 200) {&#xA;    return { type: &#34;update&#34;, post };&#xA;  } else {&#xA;    throw new Error(response.statusText);&#xA;  }&#xA;};&#xA;&#xA;const newPost = () =&gt; ({ id: 0, title: &#34;&#34;, tags: &#34;&#34;, content: &#34;&#34; });&#xA;&#xA;export { getPostItems, saveChanges, newPost };فایل state.js: // state.js File&#xA;const initial = {&#xA;  page: null,&#xA;  posts: [],&#xA;  currentPost: null&#xA;};&#xA;&#xA;const reducer = (state, action) =&gt; {&#xA;  switch (action.type) {&#xA;    case &#34;START&#34;:&#xA;      return { ...state, page: &#34;POST_LIST&#34;, posts: action.posts };&#xA;    case &#34;SHOW_POST&#34;:&#xA;      return { ...state, page: &#34;POST&#34;, currentPost: action.post };&#xA;    case &#34;SAVE_DONE&#34;:&#xA;      return { ...state, page: &#34;POST_LIST&#34;, posts: &#xA;        action.saveResult.type === &#34;update&#34; ? &#xA;        state.posts.map(p =&gt; p.id === action.saveResult.post.id ? action.saveResult.post : p) : &#xA;        [...state.posts, action.saveResult.post]&#xA;      };&#xA;    case &#34;CANCEL_EDIT&#34;:&#xA;      return { ...state, page: &#34;POST_LIST&#34;, currentPost: null };&#xA;    }&#xA;  };&#xA;  export { initial, reducer };در نهایت باید scriptها رو برای اجرا و build آماده کنیم. در فایلِ package.json تغیرات زیر رو اعمال کنید:{  &#34;name&#34;: &#34;adminclient&#34;, &#xA;   &#34;version&#34;: &#34;1.0.0&#34;,&#xA;   &#34;description&#34;: &#34;the admin front-end&#34;,&#xA;   &#34;main&#34;: &#34;index.js&#34;,&#xA;   &#34;scripts&#34;: {&#xA;     &#34;start&#34;: &#34;parcel index.html&#34;,&#xA;     &#34;build&#34;: &#34;parcel build index.html --out-dir ../Blog/wwwroot/admin&#34;&#xA;   },&#xA;   &#34;browserslist&#34;: [ &#34;chrome &gt; 55&#34; ],&#xA;   &#34;license&#34;: &#34;ISC&#34;,&#xA;   &#34;devDependencies&#34;: { &#xA;     &#34;parcel-bundler&#34;: &#34;^1.12.3&#34;&#xA;   },&#xA;   &#34;dependencies&#34;: {&#xA;     &#34;react&#34;: &#34;^16.8.6&#34;,&#xA;     &#34;react-dom&#34;: &#34;^16.8.6&#34;&#xA;   }&#xA; }دو تا script ِ مهم در این فایل تعریف شده:start: این script یک سرور برای محیط development اجرا می کنه.build: با اجرای این script تمام فایل ها آماده برای deploy در پوشه ی wwwroot/admin ساخته می شن. یعنی همون محلی که مشخصه ی RootPath در فایلِ Startup.cs بهش اشاره می کرد.قسمت browserslist هم یکی دیگه از تنظیماتِ مهمه. browserslist یک پکیجِ جداست که کارش ارائه ی لیستِ مرورگرها به همراهِ قابلیت هاییه که پشتیبانی می کنن. Parcel از این پکیج استفاده می کنه تا طبق مرورگرهایی که کاربر مشخص کرده build رو اجرا کنه. دلیل اینکه من مقدار chrome&gt;55 رو تعین کردم استفاده از async/await در برنامه است. مرورگرِ Chrome از ورژنِ 55 به بعد به صورتِ مستقیم از async/await پشتیبانی می کنه و نیازی به ایجادِ Polyfill نداره. بنابراین اگر فایل های build شده ی Parcel رو باز کنید کلماتِ async و await رو خواهید دید.کلام آخردر هر پروژه باید طبق نیاز از ترکیبِ ابزارهای مختلف استفاده کرد تا کار به بهترین حالت اجرا بشه. در این پست یکی از این ترکیب ها رو بررسی کردم. امیدوارم به کارتون بیاد. تمام کدهای نوشته شده رو می تونید با کمی تغیر از github بگیرید.</description><category>علی دوستکانی</category><author>علی دوستکانی</author><pubDate>Sun, 22 Sep 2019 14:48:46 +0330</pubDate></item></channel></rss>