به برگه سورس پروژه مراجعه کنید.
فایلهای پروژهها
DynamicSearch-1.rar
سورس + پروژه نمونه
VSCode به همراه امکانات یکپارچهای، جهت کار با یک مخزن کد مبتنی بر Git است و در ادامه بررسی خواهیم کرد که اگر مخزنی در GitHub وجود داشت، چگونه میتوان آنرا تبدیل به یک پروژهی VSCode کرد و سپس با آن کار نمود.
ایجاد یک مخزن کد آزمایشی در GitHub
برای تکمیل و بررسی مباحث این مطلب، یک مخزن کد جدید را در GitHub آغاز میکنیم:
در مرحلهی بعد، آدرس Clone این مخزن کد را کپی میکنیم:
ایجاد یک Clone از مخزن موجود GitHub توسط VSCode
پس از طی مراحلی که عنوان شد، یک پوشهی جدید را ایجاد کرده و سپس با دستور خط فرمان . code، سبب اجرای VSCode و آغاز آن در این پوشه خواهیم شد.
سپس دکمههای ctrl+shift+p را فشرده و در منوی ظاهر شده، عبارت Git را جستجو کنید:
در اینجا گزینهی Git: Clone را انتخاب نمائید. بلافاصله آدرس مخزن کد مدنظر را درخواست میکند:
در این قسمت همان آدرسی را که از طریق دکمهی سبز رنگ Clone or Download گیتهاب دریافت کردیم، وارد میکنیم. پس از آن محل ذخیره سازی فایلها را درخواست میکند:
در اینجا میتوان هر پوشهی دلخواهی را وارد کرد و یا همان پوشهی جدیدی را که ایجاد کردیم، مسیردهی خواهیم کرد.
در آخر هم سؤال میکند که آیا میخواهید این مخزن را گشوده و مشغول به کار با آن شوید؟ با انتخاب گزینهی open repository، این پوشه در VSCode باز خواهد شد.
اعمال تغییرات و ارسال آنها به گیتهاب
پس از Clone یک مخزن کد، اکنون میتوان با آن شروع به کار کرد. برای مثال اگر کلمهای را به فایل readme آن اضافه کنیم، بلافاصله در برگهی Git آن ظاهر خواهد شد:
در اینجا با کلیک بر روی هر کدام از تغییرات تشخیص داده شده، میتوان نگارش فعلی را با آخرین نگارش مخزن کد مقایسه کرد. در سمت چپ، نگارش موجود در گیتهاب نمایش داده شدهاست و در سمت راست، نگارشی که ما مشغول به کار بر روی آن هستیم.
اگر از انجام این تغییر پشیمان شدهاید، فقط کافی است بر روی دکمهی discard changes آن کلیک کنید تا این فایل را به مرحلهی قبلی آن بازگشت دهد:
آیکن M در اینجا به معنای Modified است. اگر به Explorer آن برگشته و این فایل را حذف کنیم:
در تغییرات Git نمایش داده شده، اینبار آیکون D به معنای Deleted ظاهر میشود و اگر قصد بازیابی این فایل را داشته باشیم، باز هم میتوان بر روی Discard changes آن کلیک کرد.
در همینجا در نوار ابزار بالای قسمت Git، دکمهی check-mark برای ارسال تغییرات به مخزن کد است. دکمهی refresh برای هماهنگی با مخزن کد و سه نقطهی موجود، یک منوی تکمیلی را ظاهر میکند:
در همینجا اگر علاقمند بودید تا دستوراتی را که VSCode در پشت صحنه صادر میکند مشاهده کنید، بر روی گزینهی Show Git Output کلیک کنید.
کاری که در اینجا صورت میگیرد، یک commit محلی است. اکنون اگر به status bar آن دقت کنید:
مشاهده میکنید که عدد 1 و صفر ظاهر شدهاند. عدد 1 در اینجا به معنای آماده بودن ارسال یک commit به سرور و عدد صفر به معنای عدم تغییری در مخزن کد، توسط سایر توسعه دهندهها است و نیازی به هماهنگی با آن نیست.
در ادامه یا میتوان بر روی همین آیکن در status bar کلیک کرد و تغییرات را به سرور ارسال نمود و یا روش دیگر آن، همان کلیک بر روی دکمهی سه نقطهای قسمت Git که در بالا تصویر آن نمایش داده شد و سپس انتخاب گزینهی push آن است.
پس از این هماهنگی با سرور، آیکنهای 1 و صفر نمایش داده شدهی در status bar محو میشوند. به علاوه این تغییرات را در GitHub هم میتوان مشاهده کرد:
هماهنگ سازی با تغییرات انجام شدهی توسط سایر کاربران
در همانجا در GitHub میتوان یک فایل را دستی هم ویرایش کرد:
اینکار را از این جهت انجام میدهیم تا بتوان تغییرات انجام شدهی توسط سایر کاربران را شبیه سازی کرد. در ادامه اگر به status bar موجود در VSCode دقت کنید، اعداد صفر و یک نمایش داده میشوند. یعنی آیتمی برای ارسال به سرور وجود ندارد؛ اما یک تغییر در سمت سرور رخ دادهاست که نیاز است با آن هماهنگ شویم:
اینبار برای دریافت این تغییرات نیاز است گزینهی pull را انتخاب کنیم:
ایجاد یک مخزن کد آزمایشی در GitHub
برای تکمیل و بررسی مباحث این مطلب، یک مخزن کد جدید را در GitHub آغاز میکنیم:
در مرحلهی بعد، آدرس Clone این مخزن کد را کپی میکنیم:
ایجاد یک Clone از مخزن موجود GitHub توسط VSCode
پس از طی مراحلی که عنوان شد، یک پوشهی جدید را ایجاد کرده و سپس با دستور خط فرمان . code، سبب اجرای VSCode و آغاز آن در این پوشه خواهیم شد.
سپس دکمههای ctrl+shift+p را فشرده و در منوی ظاهر شده، عبارت Git را جستجو کنید:
در اینجا گزینهی Git: Clone را انتخاب نمائید. بلافاصله آدرس مخزن کد مدنظر را درخواست میکند:
در این قسمت همان آدرسی را که از طریق دکمهی سبز رنگ Clone or Download گیتهاب دریافت کردیم، وارد میکنیم. پس از آن محل ذخیره سازی فایلها را درخواست میکند:
در اینجا میتوان هر پوشهی دلخواهی را وارد کرد و یا همان پوشهی جدیدی را که ایجاد کردیم، مسیردهی خواهیم کرد.
در آخر هم سؤال میکند که آیا میخواهید این مخزن را گشوده و مشغول به کار با آن شوید؟ با انتخاب گزینهی open repository، این پوشه در VSCode باز خواهد شد.
اعمال تغییرات و ارسال آنها به گیتهاب
پس از Clone یک مخزن کد، اکنون میتوان با آن شروع به کار کرد. برای مثال اگر کلمهای را به فایل readme آن اضافه کنیم، بلافاصله در برگهی Git آن ظاهر خواهد شد:
در اینجا با کلیک بر روی هر کدام از تغییرات تشخیص داده شده، میتوان نگارش فعلی را با آخرین نگارش مخزن کد مقایسه کرد. در سمت چپ، نگارش موجود در گیتهاب نمایش داده شدهاست و در سمت راست، نگارشی که ما مشغول به کار بر روی آن هستیم.
اگر از انجام این تغییر پشیمان شدهاید، فقط کافی است بر روی دکمهی discard changes آن کلیک کنید تا این فایل را به مرحلهی قبلی آن بازگشت دهد:
آیکن M در اینجا به معنای Modified است. اگر به Explorer آن برگشته و این فایل را حذف کنیم:
در تغییرات Git نمایش داده شده، اینبار آیکون D به معنای Deleted ظاهر میشود و اگر قصد بازیابی این فایل را داشته باشیم، باز هم میتوان بر روی Discard changes آن کلیک کرد.
در همینجا در نوار ابزار بالای قسمت Git، دکمهی check-mark برای ارسال تغییرات به مخزن کد است. دکمهی refresh برای هماهنگی با مخزن کد و سه نقطهی موجود، یک منوی تکمیلی را ظاهر میکند:
در همینجا اگر علاقمند بودید تا دستوراتی را که VSCode در پشت صحنه صادر میکند مشاهده کنید، بر روی گزینهی Show Git Output کلیک کنید.
در آخر توضیحی را نوشته و بر روی دکمهی commit کلیک میکنیم:
کاری که در اینجا صورت میگیرد، یک commit محلی است. اکنون اگر به status bar آن دقت کنید:
مشاهده میکنید که عدد 1 و صفر ظاهر شدهاند. عدد 1 در اینجا به معنای آماده بودن ارسال یک commit به سرور و عدد صفر به معنای عدم تغییری در مخزن کد، توسط سایر توسعه دهندهها است و نیازی به هماهنگی با آن نیست.
در ادامه یا میتوان بر روی همین آیکن در status bar کلیک کرد و تغییرات را به سرور ارسال نمود و یا روش دیگر آن، همان کلیک بر روی دکمهی سه نقطهای قسمت Git که در بالا تصویر آن نمایش داده شد و سپس انتخاب گزینهی push آن است.
پس از این هماهنگی با سرور، آیکنهای 1 و صفر نمایش داده شدهی در status bar محو میشوند. به علاوه این تغییرات را در GitHub هم میتوان مشاهده کرد:
هماهنگ سازی با تغییرات انجام شدهی توسط سایر کاربران
در همانجا در GitHub میتوان یک فایل را دستی هم ویرایش کرد:
اینکار را از این جهت انجام میدهیم تا بتوان تغییرات انجام شدهی توسط سایر کاربران را شبیه سازی کرد. در ادامه اگر به status bar موجود در VSCode دقت کنید، اعداد صفر و یک نمایش داده میشوند. یعنی آیتمی برای ارسال به سرور وجود ندارد؛ اما یک تغییر در سمت سرور رخ دادهاست که نیاز است با آن هماهنگ شویم:
اینبار برای دریافت این تغییرات نیاز است گزینهی pull را انتخاب کنیم:
سلام، من پکیج Microsoft.jQuery.Unobtrusive.Validation رو از nuget دریافت کردم، ولی فایلهای js رو برای من اضافه نکرده.
آیا نیاز به پکیج دیگری هست؟
در قسمت آخر این سری، نگاهی خواهیم داشت به نحوهی توزیع برنامههای React و نکات مرتبط با آن.
افزودن متغیرهای محیطی
در برنامهی نمایش لیست فیلمهایی که تا قسمت 29 آنرا بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار میکند اما بر اساس محیطهای مختلف توسعه، متغیر نیست. اغلب برنامهها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام میشود، میخواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع میشود، داشته باشیم.
برای رفع این مشکل، برنامههایی که توسط create-react-app تولید میشوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشهی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایلهایی که فقط از یک پسوند تشکیل میشوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطهی نهایی را حذف میکند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده میکنید، نیازی به درج نقطهی انتهایی نیست. در این فایل environment ایجاد شده میتوان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیشفرض آنها درج کرد. همچنین میتوان این مقادیر پیشفرض را بر اساس محیطهای مختلف کاری، بازنویسی کرد. برای مثال میتوان فایل env.development. را اضافه کرد؛ به همراه فایلهای env.test. و env.production.
متغیرهای محیطی به صورت key=value درج میشوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیشفرض زیر را تعریف میکنیم:
اکنون برای خواندن این متغیرها برای مثال در فایل index.js (و یا هر فایل جاوا اسکریپتی دیگری در برنامه)، سطر زیر را درج میکنیم:
process به معنای پروسهی جاری برنامهاست (و مرتبط است به پروسهی node.js ای که برنامهی React را اجرا میکند) و خاصیت env، به همراه تمام متغیرهای محیطی برنامه میباشد. در این حالت اگر برنامه را اجرا کنیم، در کنسول توسعه دهندگان مرورگر، به یک چنین خروجی خواهیم رسید:
در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بستههای مخصوص ارائهی نهایی، به production تنظیم میشود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه میکنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه میتوان از مقدار process.env.REACT_APP_NAME استفاده کرد.
یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایلها به صورت خودکار ردیابی نمیشوند.
نحوهی پردازش متغیرهای محیطی درج شدهی در برنامه
اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
و برنامه را مجددا اجرا کنیم، با مراجعهی به برگهی Sources و انتخاب مسیر localhost:3000/static/js/main.chunk.js و سپس جستجوی "My App Name" ای که در اینجا اضافه کردیم (با فشردن دکمههای Ctrl+F)، به خروجی زیر خواهیم رسید:
همانطور که مشاهده میکنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شدهاست. بنابراین اگر در در حین توسعهی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آنها در حین پروسهی Build نهایی، جایگزین میشوند.
Build برنامههای React برای محیط تولید
اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه میشود که بهینه سازی نشدهاست و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ سادهتر برنامهاست. برای رسیدن به یک خروجی بهینه سازی شدهی مخصوص محیط تولید و ارائهی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشهی جدید build واقع در ریشهی پروژه، قرار میگیرد. اکنون میتوان کل محتویات این پوشه را جهت ارائهی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائهی آن توسط static server زیر نیز وجود دارد» ظاهر میشود:
اگر علاقمند باشید تا خروجی حالت production تولید شده را نیز به صورت محلی آزمایش کنید، ابتدا باید static server یاد شده را توسط دستور npm install فوق نصب کنید. سپس ریشهی پروژه را در خط فرمان باز کرده و دستور serve -s build را صادر کنید (البته اگر با خط فرمان به پوشهی build وارد شدید، دیگر نیازی به ذکر پوشهی build نخواهد بود). اکنون میتوانید برنامه را در آدرس http://localhost:5000 در مرورگر خود بررسی نمائید.
البته با توجه به اینکه backend سرور برنامههای ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، میتوان این پورت پیشفرض را با اجرای دستور serve -s build -l 1234 تغییر داد. اکنون میتوان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمیکند:
روش رفع این مشکل را در قسمت 23 بررسی کردیم و در اینجا جهت بهبود آن میتوان متد WithOrigins فایل Startup.cs را به صورت زیر تکمیل کرد:
یک نکته: زمانیکه از دستور npm start استفاده میشود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده میشود، این متغیرها از فایل env.production. تامین میشوند. در این حالتها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیشفرض موجود در فایل env. استفاده میگردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده میشود.
آماده سازی برنامهی React، برای توزیع نهایی
تا اینجا برنامهی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت میکند. اکنون میخواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه میکنیم:
البته فعلا همین متغیرها را به فایل env.development. نیز میتوان اضافه کرد؛ چون backend سرور ما در هر دو حالت، در این مثال، در آدرس فوق قرار دارد.
اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آنرا حذف میکنیم. با این تغییر، تمام آدرسهایی مانند:
را به صورت زیر ویرایش میکنیم:
در ادامه برای تامین مقدار apiUrl به صورت خودکار، به فایل src\services\httpService.js مراجعه کرده و در ابتدای آن، یک سطر زیر را اضافه میکنیم:
به این ترتیب تمام درخواستهای ارسالی توسط Axios، دارای baseURL ای خواهند شد که از فایل متغیر محیطی جاری تامین میشود. همانطور که پیشتر نیز عنوان شد، این مقدار در زمان Build، با مقدار ثابتی که از فایل env جاری خوانده میشود، جایگزین خواهد شد.
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین میکنیم:
پس از این تغییرات، نیاز است برای حالت توسعه، یکبار دیگر دستور npm start و یا برای حالت تولید، دستور npm run build را اجرا کرد تا اطلاعات درج شدهی در فایلهای env.، پردازش و جایگزین شوند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
افزودن متغیرهای محیطی
در برنامهی نمایش لیست فیلمهایی که تا قسمت 29 آنرا بررسی کردیم، از فایل src\config.json برای ذخیره سازی اطلاعات تنظیمات برنامه استفاده شد. هرچند این روش کار میکند اما بر اساس محیطهای مختلف توسعه، متغیر نیست. اغلب برنامهها باید بتوانند حداقل در سه محیط توسعه، آزمایش و تولید، بر اساس متغیرها و تنظیمات خاص هر کدام، کار کنند. برای مثال بر روی سیستمی که کار توسعه در آن انجام میشود، میخواهیم apiUrl متفاوتی را نسبت به حالتیکه برنامه توزیع میشود، داشته باشیم.
برای رفع این مشکل، برنامههایی که توسط create-react-app تولید میشوند، دارای پشتیبانی توکاری از متغیرهای محیطی هستند. برای این منظور نیاز است در ریشهی پروژه (جائیکه فایل package.json قرار دارد) فایل جدید env. را ایجاد کرد. در ویندوز برای ایجاد یک چنین فایلهایی که فقط از یک پسوند تشکیل میشوند، باید نام فایل را به صورت .env. وارد کرد؛ سپس خود ویندوز نقطهی نهایی را حذف میکند. البته اگر از ادیتور VSCode برای ایجاد این فایل استفاده میکنید، نیازی به درج نقطهی انتهایی نیست. در این فایل environment ایجاد شده میتوان تمام متغیرهای محیطی مورد نیاز را با مقادیر پیشفرض آنها درج کرد. همچنین میتوان این مقادیر پیشفرض را بر اساس محیطهای مختلف کاری، بازنویسی کرد. برای مثال میتوان فایل env.development. را اضافه کرد؛ به همراه فایلهای env.test. و env.production.
متغیرهای محیطی به صورت key=value درج میشوند. این کلیدها نیر باید با REACT_APP_ شروع شوند؛ در غیر اینصورت، کار نخواهند کرد. برای مثال در فایل env.، دو متغیر پیشفرض زیر را تعریف میکنیم:
REACT_APP_NAME=My App REACT_APP_VERSION=1
console.log(process.env);
در این خروجی، متغیر "NODE_ENV: "development به صورت خودکار با تولید بستههای مخصوص ارائهی نهایی، به production تنظیم میشود. سایر متغیرهای محیطی تعریف شده را نیز در اینجا ملاحظه میکنید. با توجه به خواص شیء env، برای مثال جهت دسترسی به نام برنامه میتوان از مقدار process.env.REACT_APP_NAME استفاده کرد.
یک نکته: با هر تغییری در مقادیر متغیرهای محیطی، نیاز است یکبار دیگر برنامه را از ابتدا توسط دستور npm start، راه اندازی مجدد کرد؛ چون این فایلها به صورت خودکار ردیابی نمیشوند.
نحوهی پردازش متغیرهای محیطی درج شدهی در برنامه
اگر همان سطر لاگ کردن خروجی process.env را به صورت زیر تغییر دهیم:
console.log("My App Name", process.env.REACT_APP_NAME);
همانطور که مشاهده میکنید، فراخوانی console.log ما، دیگر به همراه متغیر process.env.REACT_APP_NAME نیست؛ بلکه مقدار اصلی این متغیر در اینجا درج شدهاست. بنابراین اگر در در حین توسعهی برنامه، از متغیرهای محیطی استفاده شود، این متغیرها با مقادیر اصلی آنها در حین پروسهی Build نهایی، جایگزین میشوند.
Build برنامههای React برای محیط تولید
اجرای دستور npm start، سبب ایجاد یک Build مخصوص محیط توسعه میشود که بهینه سازی نشدهاست و به همراه اطلاعات اضافی قابل توجهی جهت دیباگ سادهتر برنامهاست. برای رسیدن به یک خروجی بهینه سازی شدهی مخصوص محیط تولید و ارائهی نهایی باید دستور npm run build را در خط فرمان اجرا کرد. خروجی نهایی این دستور، در پوشهی جدید build واقع در ریشهی پروژه، قرار میگیرد. اکنون میتوان کل محتویات این پوشه را جهت ارائهی نهایی در وب سرور خود، مورد استفاده قرار داد.
پس از پایان اجرای دستور npm run build، پیام «امکان ارائهی آن توسط static server زیر نیز وجود دارد» ظاهر میشود:
> npm install -g serve > serve -s build
البته با توجه به اینکه backend سرور برنامههای ما نیز در همین آدرس قرار دارد و در صورت ورود این آدرس، به صورت خودکار به https://localhost:5001/index.html هدایت خواهید شد، میتوان این پورت پیشفرض را با اجرای دستور serve -s build -l 1234 تغییر داد. اکنون میتوان آدرس جدید http://localhost:1234 را در مرورگر آزمایش کرد که ... با خطای زیر کار نمیکند:
Access to XMLHttpRequest at 'https://localhost:5001/api/genres' from origin 'http://localhost:1234' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
WithOrigins("http://localhost:3000", "http://localhost:1234")
یک نکته: زمانیکه از دستور npm start استفاده میشود، متغیرهای محیطی از فایل env.development. خوانده خواهند شد و زمانیکه از دستور npm run build استفاده میشود، این متغیرها از فایل env.production. تامین میشوند. در این حالتها اگر متغیری در این دو فایل درج نشده بود، از مقدار پیشفرض موجود در فایل env. استفاده میگردد. از فایل env.test. با اجرای دستور npm test، به صورت خودکار استفاده میشود.
آماده سازی برنامهی React، برای توزیع نهایی
تا اینجا برنامهی React تهیه شده، اطلاعات apiUrl خودش را از فایل config.json دریافت میکند. اکنون میخواهیم بر اساس حالات مختلف توسعه و تولید، از apiUrlهای متفاوتی استفاده شود. به همین جهت به فایل env.production. مراجعه کرده و تنظیمات ذیل را به آن اضافه میکنیم:
REACT_APP_API_URL=https://localhost:5001/api REACT_APP_ADMIN_ROLE_NAME=Admin
اکنون به برنامه مراجعه کرده و در هرجائی که ارجاعی به فایل config.json وجود دارد، سطر import آنرا حذف میکنیم. با این تغییر، تمام آدرسهایی مانند:
const apiEndpoint = apiUrl + "/users";
const apiEndpoint = "/users";
axios.defaults.baseURL = process.env.REACT_APP_API_URL;
همچنین adminRoleName مورد نیاز در فایل src\services\authService.js را نیز از همان فایل env جاری تامین میکنیم:
const adminRoleName = process.env.REACT_APP_ADMIN_ROLE_NAME;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-34-frontend.zip و sample-34-backend.zip
یکی از مهمترین تغییرات دات نت 6، ارائهی Minimal API's به همراه آن است که نسبت به MVC و سایر مشتقات ASP.NET Core، کمتر به همراه پیشفرضهای نظری خاص و بسیار مقید و متعصبانه (opinionated) است؛ که این مورد خود مزیتی است جهت انجام امور متداول، به نحوی دیگر و دلخواه و با آزادی عمل بیشتری در طراحی endpoints مورد نیاز و کل برنامه. خصوصا این سبک جدید، با معماری برشهای عمودی (vertical slices) ارائه شدهی توسط نویسندهی AutoMapper، هماهنگی خاصی دارد و اینطور به نظر میرسد که جهت ساده سازی طراحی برنامههای ASP.NET Core با معماری CQRS ارائه شدهاست. با وجود Minimal API's میتوان از دو لایهی متداول برنامهها رها شد: لایهی سرویسها و لایهی مخازن یا Repositories. در معماری برشهای عمودی، برنامه به ویژگیهای خاصی (Features) تقسیم شده و هر ویژگی، متکی به خود طراحی میشود. زمانیکه از هندلرها برای هر Command و Query معماری CQRS استفاده میکنیم، اینها مختص به یک ویژگی متکی به خود طراحی میشوند و به همراه تمام اطلاعات و اعمال مرتبط هستند و دیگر در این حالت، وجود لایههای سرویس و مخزن، بیمعنا و غیرضروری میشوند.
در ادامه قصد داریم تمام این موارد را مانند Minimal API's و معماری برشهای عمودی به همراه CQRS، در طی یک سری و یک پروژهی عملی ساخت یک Blog به نام MinimalBlog، بررسی کنیم. البته هدف ما در اینجا صرفا ساخت backend ساختار یافتهی این برنامهاست؛ منهای UI آن. هدف اصلی ما از این سری، ارائهی یک معماری، جهت کار با Minimal API's است.
دریافت کدهای کامل این سری
جهت مرور سریعتر و سادهتر این سری، کدهای کامل آنرا از اینجا میتوانید دریافت کنید: MinimalBlog.zip
پروژههایی که برنامهی MinimalBlog را تشکیل میدهند
برنامهی MinimalBlog، تنها از سه پروژهی زیر تشکیل میشود:
MinimalBlog.Api: این پروژه از نوع minimal API's است که توسط دستور جدید «dotnet new webapi --use-minimal-apis» آغاز خواهد شد و به صورت پیشفرض به همراه پشتیبانی از OpenAPI نیز هست. البته اگر از VS2022 استفاده میکنید، در حین آغاز یک پروژهی Web API جدید، تیک مربوط به use controllers را در UI بردارید تا از Minimal API's استفاده شود.
MinimalBlog.Dal: که Dal در اینجا مخفف data access layer است و یک class library میباشد و با دستور dotnet new classlib آغاز میشود.
MinimalBlog.Domain: نیز یک class library است و با دستور dotnet new classlib آغاز میشود.
همانطور که مشاهده میکنید، این طراحی جدید، بدون وجود لایهی متداول سرویسها و یا مخازن است.
بررسی ساختار ابتدایی پروژهی MinimalBlog.Api
در اینجا تنها تک فایل Program.cs، به همراه تنظیمات برنامه قابل مشاهدهاست و فایل Starup.cs از آن حذف شدهاست (اطلاعات بیشتر). این فایل نیز بر مبنای مفهوم top level programs طراحی شدهاست و به همراه تعریف class و یا فضای نامی نیست:
همانطور که ملاحظه میکنید، تمام اتفاقات در همین تک فایل رخ میدهند. برای مثال سرویسهای مورد نیاز برنامه به مجموعهی builder.Services اضافه میشوند؛ شبیه به کاری که پیشتر در فایل Startup.cs و متد ConfigureServices آن انجام میدادیم.
پس از آن به تعاریف زیر میرسیم؛ تعاریف میان افزارهایی که پیشتر در متد Configure کلاس Startup انجام میشدند، الان همگی در تک فایل Program.cs قرار دارند:
البته هنوز هم میتوان در صورت نیاز به همان ساختار کلاس Startup پیشین نیز رسید.
در انتهای این فایل نیز تعاریف پیشفرض زیر قرار دارند:
در اینجا متد متد MapGet یک endpoint را تعریف کرده و سپس اکشنی را به آن انتساب میدهد. یعنی اگر آدرس weatherforecast/ درخواست شود، lambda expression تعریف شده، اجرا میشود. هدف از ارائهی Minimal API نیز همین است تا بتوان با حداقل کدنویسی، سریعا به نتیجهی مدنظر خود رسید.
در همین حال اگر برنامهی Api را اجرا کنیم، به تصویر زیر خواهیم رسید:
در ادامه کدهای موجود در این فایل را Refactor کرده و به کلاسهای دیگری منتقل میکنیم؛ چون اگر قرار باشد در طول زمان تمام endpoints مدنظر را در همینجا تعریف کنیم، کنترل برنامه از دست خارج خواهد شد.
غنی سازی Solution و کامپایلر #C با استفاده از فایلهای editorconfig. و Directory.Build.props
در مورد این دو فایل در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها » بیشتر بحث شدهاست. هدف از آنها، اعمال یکسری تنظیمات سراسری، به تمام پروژههای یک solution به صورت یکدست است؛ مانند تنظیمات کامپایلر جهت نمایش اخطارها به صورت خطاها، تعریف usingهای سراسری سیشارپ 10 و یا اعمال Roslyn analyzers به تمام پروژهها. این دو فایل را به همراه پروژهی پیوست میتوانید دریافت کنید و ... باید جزء استاندارد تمام پروژههای جدید باشند. چون وجود آنها سبب خواهد شد که به شدت کیفیت کدهای نهایی افزایش یابند و مبتنی بر یکسری best practices شوند.
در ادامه قصد داریم تمام این موارد را مانند Minimal API's و معماری برشهای عمودی به همراه CQRS، در طی یک سری و یک پروژهی عملی ساخت یک Blog به نام MinimalBlog، بررسی کنیم. البته هدف ما در اینجا صرفا ساخت backend ساختار یافتهی این برنامهاست؛ منهای UI آن. هدف اصلی ما از این سری، ارائهی یک معماری، جهت کار با Minimal API's است.
دریافت کدهای کامل این سری
جهت مرور سریعتر و سادهتر این سری، کدهای کامل آنرا از اینجا میتوانید دریافت کنید: MinimalBlog.zip
پروژههایی که برنامهی MinimalBlog را تشکیل میدهند
برنامهی MinimalBlog، تنها از سه پروژهی زیر تشکیل میشود:
MinimalBlog.Api: این پروژه از نوع minimal API's است که توسط دستور جدید «dotnet new webapi --use-minimal-apis» آغاز خواهد شد و به صورت پیشفرض به همراه پشتیبانی از OpenAPI نیز هست. البته اگر از VS2022 استفاده میکنید، در حین آغاز یک پروژهی Web API جدید، تیک مربوط به use controllers را در UI بردارید تا از Minimal API's استفاده شود.
MinimalBlog.Dal: که Dal در اینجا مخفف data access layer است و یک class library میباشد و با دستور dotnet new classlib آغاز میشود.
MinimalBlog.Domain: نیز یک class library است و با دستور dotnet new classlib آغاز میشود.
همانطور که مشاهده میکنید، این طراحی جدید، بدون وجود لایهی متداول سرویسها و یا مخازن است.
بررسی ساختار ابتدایی پروژهی MinimalBlog.Api
در اینجا تنها تک فایل Program.cs، به همراه تنظیمات برنامه قابل مشاهدهاست و فایل Starup.cs از آن حذف شدهاست (اطلاعات بیشتر). این فایل نیز بر مبنای مفهوم top level programs طراحی شدهاست و به همراه تعریف class و یا فضای نامی نیست:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();
پس از آن به تعاریف زیر میرسیم؛ تعاریف میان افزارهایی که پیشتر در متد Configure کلاس Startup انجام میشدند، الان همگی در تک فایل Program.cs قرار دارند:
var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection();
در انتهای این فایل نیز تعاریف پیشفرض زیر قرار دارند:
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast"); app.Run(); record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); }
در همین حال اگر برنامهی Api را اجرا کنیم، به تصویر زیر خواهیم رسید:
در ادامه کدهای موجود در این فایل را Refactor کرده و به کلاسهای دیگری منتقل میکنیم؛ چون اگر قرار باشد در طول زمان تمام endpoints مدنظر را در همینجا تعریف کنیم، کنترل برنامه از دست خارج خواهد شد.
غنی سازی Solution و کامپایلر #C با استفاده از فایلهای editorconfig. و Directory.Build.props
در مورد این دو فایل در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها » بیشتر بحث شدهاست. هدف از آنها، اعمال یکسری تنظیمات سراسری، به تمام پروژههای یک solution به صورت یکدست است؛ مانند تنظیمات کامپایلر جهت نمایش اخطارها به صورت خطاها، تعریف usingهای سراسری سیشارپ 10 و یا اعمال Roslyn analyzers به تمام پروژهها. این دو فایل را به همراه پروژهی پیوست میتوانید دریافت کنید و ... باید جزء استاندارد تمام پروژههای جدید باشند. چون وجود آنها سبب خواهد شد که به شدت کیفیت کدهای نهایی افزایش یابند و مبتنی بر یکسری best practices شوند.
ترکیب ماژولها به قالب یک اسمبلی
فایل
Program.exe یک فایل PE با جداول متادیتا است که همچنین یک اسمبلی هم
میباشد. یک اسمبلی مجموعهای از یک یا چند فایل، شامل تعاریف نوع و منابع
(ریسورس) میباشد و یکی از فایلهای اسمبلی، برای نگهداری manifest انتخاب
میشود. این جدول مجموعهای است از جداول متادیتا که به طور کلی شامل نام فایلهایی است که قسمتی از اسمبلی را تشکیل میدهند. برای همین گفتیم که CLR با
اسمبلیها کار میکند. ابتدا جداول manifest را خوانده تا نام فایلها را
شناسایی کرده تا از آنها را به حافظه بارگزاری کند. اسمبلیها چند خصوصیت
دارند که باید آنها را بدانید:
- نوعهای با قابلیت استفادهی مجدد را تعریف میکنند.
- داری شمارهی نسخه version هستند.
- میتوانند شامل اطلاعات امنیتی باشند.
این
خواصی است که یک اسمبلی به همراه دارد و فایلهایی که شامل میشود،
نمیتوانند چنین خاصیتی را داشته باشند؛ مگر اینکه آن فایلها در متای خود جدول
manifest داشته باشند.
شما برای بسته
بندی، شماره نسخه، مباحث امنیتی و استفاده از نوعها، باید آنها
را داخل ماژولی قرار دهید که جزئی از اسمبلی است. یک فایل اسمبلی همانند
program.exe به عنوان یک فایل واحد شناخته میشود. با اینکه یک اسمبلی از
چند فایل تشکیل میشود، فایلهای PE به همراه جداول متادیتای آن و تعدادی
ریسورس مثل فایلهای gif و jpg است که به شما کمک میکند به همهی آنها به
عنوان یک فایل منطقی EXE یا dll نگاه کنید.
یکی
از دلایلی که در قسمت سوم گفتیم این بود که میتوانیم فایلهایی را که به ندرت
استفاده میشوند، از طریق اینترنت مورد استفاده قرار دهیم. در حالتیکه
نیاز به دسترسی به اسمبلیهای روی اینترنت دارید، CLR ابتدا کش را بررسی
میکند تا آیا فایل حاضر است یا خیر؟ اگر پاسخ مثبت بود، در حافظه قرار
میگیرد. ولی اگر پاسخ منفی بود، CLR به آدرسی که اسمبلی در آن قرار دارد،
رجوع کرده و آن را دانلود میکند و اگر فایل مد نظر یافت نشد، استثنای
FileNotFound را در حین اجرا صادر خواهد کرد.
آقای جفری ریچر در کتاب خود سه تا از دلایل استفادهی از اسمبلیهای چند فایله را بر میشمارد:
- جداسازی نوعها در فایلهای جداگانه که باعث کاهش حجم فایل از طریق اینترنت و بارگزاری حجم کمتر در حافظه میشوند.
- استفاده
از فایلهای منبع و دادهها در اسمبلی: فرض کنید نیاز به محاسبهی اطلاعات بیمه دارید و برای این کار به اطلاعات داخل یک جدول آماری احتیاج
دارید. این جدول آماری میتواند یک فایل متنی ساده یا یک صفحهی گسترده مثل
اکسل یا در قالب ورد و هر چیز دیگری باشد که به جای embed شدن این جداول در
سورس کد برنامه، آنها را با استفاده از ابزاری مثل Assembly Linker
-AL.exe میتوانید جزئی از اسمبلی کنید و فقط نیاز است که بدانید
چگونه آن فایل را پارس یا تبدیل کنید.
- استفاده
از انواع ایجاد شده در زبانهای مختلف. در این حالت شما مقداری از کد را
با استفاده از #C نوشته اید و مقداری از آن را با Visual Basic مینویسید و
هر کدام در نهایت به یک ماژول جداگانه کامپایل خواهند شد. ولی تبدیل آن به
یک واحد منطقی مثل اسمبلی ممکن است و از این نظر میتوانید روی ماژولهای
یک دسته کنترل داشته باشید.
اگر چندین
نوع دارید که شامل نسخه بندی و تنظیمات امنیتی مشترک هستند، بهتر است در
یک اسمبلی قرار گیرند تا اینکه در اسمبلیهای جداگانهایی قرار بگیرند. دلیل
این کار هم ایجاد performance یا کارآیی بهتر است. بارگذاری یک اسمبلی در
حافظه زمانی را برای یافتن آن از CLR و ویندوز میگیرد و سپس وارد بارگیری
آنها در حافظه و آماده سازی میشود. پس هر چه تعداد اسمبلیها کمتر باشد،
کارآیی بهتری خواهید داشت، چون کمتر شدن بارگیری برابر با کاهش صفحات کاری است و پراکندگی fragmentation فضای آدرس دهی آن فرایند را کاهش خواهد
داد. نهایتا Ngen میتواند در بهینه سازی فایلهای بزرگتر موفق باشد.
برای
ساخت اسمبلی، باید یکی از فایلهای PE را برای نگهداری جدول manifest
انتخاب کنید؛ یا خودتان یک فایل PE جدا درست کنید که تنها شامل جدول مانیفست
شود. جدول زیر قالبی از جداول مانیفست هست که بابت ماژولهای اضافه شده به
یک اسمبلی ایجاد میشوند.
AssemblyDef | شامل مدخل ورودی (آدرس شروع حافظه) برای اسمبلیهایی است که ماژول عضو آن است. این مدخل شامل نام اسمبلی (بدون مسیر و پسوند)، شماره نسخه یا ورژن، culture، فلگ، الگوریتم هش و کلید عمومی ناشر، که میتواند نال باشد، هست. |
FileDef | شامل
یک مدخل ورودی برای هر فایل PE و فایلهای ریسورسی است که قسمتی از اسمبلی
را تشکیل میدهند. این مدخل ورودی شامل نام و پسوند فایل (بدون ذکر مسیر)،
فلگ و مقدار هش میشود. اگر تنها یک اسمبلی وجود داشته باشد، این جدول هیچ
مدخلی نخواهد داشت. |
ManifestResourceDef | شامل
یک مدخل ورودی برای هر فایل ریسورس است. این مدخل شامل نام فایل ریسورس،
فلگ و یک اندیس به جدول FileDef است که در آن اشارهای به آن فایل ریسورس
یا استریم است. |
ExportedTypesDef | شامل
یک مدخل ورودی برای هر نوع عمومی است که از همه ماژولهای PE استخراج شده
است. هر مدخل شامل نام نوع و اندیسی به جدول FileDef و یک اندیس دیگر به
جدول TypeDef است. نکته: برای ذخیره سازی حافظه و کم حجم شدن فایلها، نوعهای استخراج شده از فایلی که شامل مانیفست است دیگر در جدول جاری نام نوعها ذکر نمیگردد؛ چرا که این اطلاعات در جدول TypeDef اسمبلی جاری موجود
است. |
نکته: اسمبلی که شامل مانیفست است، شامل یک جدول AssemblyRef نیز میگردد که به تمام اسمبلیهای ارجاع شده در آن اسمبلی اشاره میکند. با استفاده از ابزارهای موجود میتوان اسمبلی مدنظر را باز کرده و به این ترتیب لیستی از اسمبلیهای ارجاع شده را خواهید دید و بدین صورت این اسمبلی یک اسمبلی خود تعریف میشود.
کامپایلر سی شارپ با استفاده از سوئیچهای زیر یک اسمبلی را تولید میکند:
کامپایلر سی شارپ با استفاده از سوئیچهای زیر یک اسمبلی را تولید میکند:
/t[arget]:exe, /t[arget]:winexe, /t[arget]: appcontainerexe, /t[arget]: library, or /t[arget]:winmdobj
سوئئیچهای بالا باعث میشود که یک فایل PE با جدول مانیفست تولید گردد. در صورتیکه سوئیچ زیر را به کار ببرید، فایل تولید شده شامل جدول مانیفست نمیشود.
/t[arget]:module
این فایل PE تولید شده در قالب یک dll است که باید قبل از اینکه CLR به نوعهای داخل آن دسترسی پیدا کند، به یک اسمبلی اضافه گردد. موقعیکه شما از سوئیچ بالا استفاده میکنید، کامپایلر سی شارپ به طور پیش فرض از پسوند netmodule برای فایل خروجی استفاده میکند.
نکتهی پایانی: محیط توسعه ویژوال استادیو به طور پیش فرض از اسمبلیهای چند فایل پشتیبانی نمیکند، اگر میخواهید که اسمبلیهای چند فایله تولید کنید باید در سوئیچهای مورد استفاده آن تجدید نظری داشته باشید.
در مقاله آینده این روشها را بررسی خواهیم کرد...
اشتراکها
تسکولو!
مدیریت پروژهها
تسکولو یکراه حل کاملبرای شروع و مدیریت پروژههاست. به سادگی پروژه، لیست کاری و کار ایجاد و آنها را مدیریت کنید. با اعضای تیم خود لحظه به لحظه در ارتباط باشید و زمان کاری خود را ثبت کنید.
مدیریت تیم
به اعضای تیم خود نقشها و دسترسیهای مناسب بدهید تا محیط کاری خود را مرتب و کارآمد نگاه دارید و کارایی تیم خود را با قابلیتگفتگوی بیدرنگبالا ببرید.
سطوح دسترسی
برای مدیریت چیزی که به واقع یک پروژه است چند زیرپروژه ایجاد نکنید! با استفاده ازنقشهای کاربریوفازهابه سادگی دسترسی افراد تیم خود به بخشهای مختلف پروژه را کنترل کنید.
افزونهی #C مخصوص VSCode دقیقا همان روزی که NET Core 2.0. ارائه شد، به روز شدهاست. بنابراین پس از نصب SDK جدید، یکبار VSCode را بسته، به اینترنت متصل شوید، سپس VSCode را باز کنید. در برگهی افزونهها مشاهده خواهید کرد که این افزونه به روز شدهاست و باید صفحه را reload کنید. پس از آن یک فایل #C را هم باز کنید تا کار دریافت دیباگر جدید آن آغاز شود. اینجا است که کار به روز رسانی «دو مرحلهای» آن تکمیل میشود. پس از آن به ریشهی پروژه وارد شده و دستور dotnet restore را صادر کنید تا وابستگیهای شناسایی نشده، شناسایی شوند.