مطالب دورهها
بررسی سیستم جدید گرید بوت استرپ 3
بوت استرپ با یک سیستم گرید 12 ستونی همراه است و بوت استرپ 3 یک mobile-first grid را بجای دو سیستم طرحبندی پیشین خود در بوت استرپ 2 ارائه میدهد. این گرید جدید، بجای دو سیستم متفاوت نگارش 2، اینبار در چهار اندازه مختلف ارائه میشود.
چهار اندازه متفاوت سیستم گرید بوت استرپ 3
الف) صفحات نمایش بسیار کوچک یا xs، مانند موبایلها (کمتر از 768 پیکسل)
ب) صفحات نمایش کوچک یا sm مانند تبلتها (بیشتر از 768 پیکسل و کمتر از 992 پیکسل)
ج) صفحات نمایش با اندازه متوسط یا md مانند سیستمهای دسکتاپ (بیشتر از 992 پیکسل و کمتر از 1200 پیکسل)
د) صفحات نمایش با اندازه بزرگ یا lg مانند سیستمهای خاص دسکتاپ (بیشتر از 1200 پیکسل)
نحوه تنظیم این چهار اندازه را در تصویر ذیل مشاهده میکنید:
تصویری را که ملاحظه میکنید، در اندازهی مرورگر بالای 1200 پیکسل تهیه شده است. در این حالت، تمام گریدهای تعریف شده به صورت افقی، در عرض صفحه نمایش داده میشوند. برای اینکه واکنشگرا بودن این سیستم طرحبندی را مشاهده کنید، عرض نمایشی مرورگر خود را کاهش دهید.
در این حین که عرض مرورگر را تغییر میدهید، به سطر اول، بیش از بقیه توجه کنید. این سطر طوری طراحی شده است که در اندازههای مختلف صفحه، اطلاعات متفاوتی را نمایش میدهد. همچنین سلولهای گریدهای پایین صفحه به صورت عمودی بر روی هم قرار خواهند گرفت.
- اکنون بر اساس اندازه دستگاهی که قرار است سیستم را مطابق آن طراحی یا بهینه سازی کنیم، میتوان از چهار اندازه یاد شده استفاده کرد. ستونهای col-xs به معنای extra small یا بسیار کوچک هستند. ستونهای دارای کلاس col-sm دارای اندازه کوچک یا small میباشند. ستونهای col-md برای حالت medium devices طراحی شدهاند و col-lg برای حالت large devices و صفحات عریض کاربرد دارند.
بنابراین در بوت استرپ 3 بر اساس اندازه غالب صفحه مرورگر کاربران برنامه میتوان سیستم گرید را بهینه سازی کرد.
- اعدادی که پس از نامهای یاد شده میآیند، جمعشان باید 12 بشود. برای مثال در سطر آخر، سه col-lg-4 داریم و در سطرهای دیگر نیز به همین ترتیب، جمع اعداد ستونها، عدد 12 را تشکیل میدهند.
- اگر نیاز است اطلاعاتی جهت اندازه خاصی نمایش داده شود، مانند سطر اول، از کلاسهایی مانند visible-lg میتوان استفاده کرد.
- style ابتدای مثال نیز صرفا برای رنگی نمایش دادن این سیستم گرید و ارائه توضیحات واضحتری در مورد آن تعریف شدهاند.
نکات تکمیلی سیستم گریدهای بوت استرپ 3
1) ترکیب اندازههای مختلف گریدها با هم
فرض کنید یک ردیف را با چهار ستون col-md-3 طراحی کردهاید. اندازهی صفحه که اندکی کوچکتر شود، تمام این ستونها تبدیل به 4 ردیف خواهند شد و شاید در این حالت بجای داشتن یک سیستم تک ستونی چهار ردیفه، سیستمی 2 ردیفه با 2 ستون، مطلوب کار ما باشد و به این ترتیب قسمت عمدهای از صفحه خالی باقی نماند.
برای رسیدن به یک چنین طراحی خاصی، تنها کافی است در هر ستون، دو نوع اندازه را در کلاسهای مرتبط قید کنیم. در این حالت از اندازههای md و xs استفاده شده است. برای حالت xs نیازی نیست تا جمع اندازه ستونها حتما 12 باشد. این مورد به کرات در مستندات بوت استرپ 3 بکار گرفته شده است.
در مثال فوق، اگر اندازه صفحه برای حالت md مناسب باشد، 4 ستونه نمایش داده میشود. اگر اندازه اندکی کوچکتر گردد، 2 ستونه میشود؛ بجای تک ستونه صرف حالت col-md.
2) استفاده از div محصور کننده container
اگر کلیه سطرهای طرحبندی جاری را در یک div با class مساوی container محصور کنیم، به این ترتیب محتوای صفحه به میانه آن منتقل شده و حالت شکیلتری را پیدا میکند و نیازی به تنظیمات بیشتری از این لحاظ نخواهد داشت. هرچند استفاده از آن اختیاری است.
3) ایجاد فاصله بین ستونها
اگر علاقمند باشید تا بین ستونهای یک گرید فاصله ایجاد کنید، باید از offset استفاده کرد. یک مثال:
اگر در حالت معمولی، دو ستون با تعاریف col-lg-3 و col-lg-9 تعریف شدهاند، میتوان از ستون دوم یک واحد کم کرد و یک واحد به آفست آن افزود تا از ستون کناری فاصله بگیرد. آفست از سمت چپ ستون عمل میکند و اگر از نسخه RTL استفاده میکنید، از سمت راست.
علت اینکه در اینجا هم از col-lg استفاده شده و هم از col-sm، در قسمت 1 توضیح داده شد. میخواهیم این ردیف حتی در بازه sm نیز دو ستونی نمایش داده شود.
4) تعیین ترتیب ستونها
تعیین ترتیب ستونها نیز یکی دیگر از قابلیتهای جدید گرید بوت استرپ 3 است. مثلا در مثال 3 فوق، با کاهش عرض مرورگر، بالاخره زمانی فرا میرسد که تمام ستونها در قالب یک ردیف نمایش داده خواهند شد. در این حالت اگر ستون سمت راست را منو و ستون سمت چپ را محتوای صفحه فرض کنیم، شاید علاقمند باشیم که بجای اینکه ابتدا منو نمایش داده شود و سپس در ردیف زیرین، محتوای صفحه، این ترتیب معکوس گردد. برای این منظور میتوان از push و pull استفاده کرد:
در اینجا در div اول به ازای هر کدام از حالتهای sm و lg مدنظر، یک push اضافه شده است و در div دوم یک pull.
push سبب میشود تا div اول به سمت راست صفحه هدایت گردد و pull باعث خواهد شد تا div دوم به سمت چپ رانده شود (برای آزمایش این مساله یکبار push مربوط به div اول را حذف کنید و نتیجه را در مروگر بررسی کنید و سپس یکبار pull اضافه شده به div دوم را به صورت موقت حذف نمائید).
5) ایجاد ردیفهای تو در تو
یکی از امکانات پیش فرض گریدهای بوت استرپ، امکان قرار دادن کل محتوای یک ردیف داخل ردیف یا ستونی دیگر است. برای مثال محتوایی در ستون دوم نمایش داده میشود و قصد داریم دقیقا در ذیل آن یک ردیف 4 ستونه داشته باشیم. در این حالت تنها کافی است این ردیف را داخل ستون دوم ایجاد کنیم.
6) قابلیتی به نام جامبوترون!
حتما بسیاری از سایتها را دیدهاید که در ابتدای صفحه اول خود، قسمت عمدهای را در بالای صفحه به نمایش یک عکس بزرگ با چند سطر متن داخل آن اختصاص دادهاند. به این کار در بوت استرپ، جامبوترون میگویند. برای تدارک آن نیز باید از یک ردیف 12 ستونی کامل بدون ستون استفاده کرد. یعنی فقط یک row باید ذکر شود. اما بجای row میتوان از کلاس مخصوص دیگری استفاده کرد:
در اینجا اگر تصویری را نیز قرار دادید، با استفاده از کلاسهای pull-left یا pull-right میتوان موقعیت تصویر را نیز تغییر داد.
فایلهای نهایی این قسمت را از اینجا نیز میتوانید دریافت کنید:
bs3-sample02.zip
چهار اندازه متفاوت سیستم گرید بوت استرپ 3
الف) صفحات نمایش بسیار کوچک یا xs، مانند موبایلها (کمتر از 768 پیکسل)
ب) صفحات نمایش کوچک یا sm مانند تبلتها (بیشتر از 768 پیکسل و کمتر از 992 پیکسل)
ج) صفحات نمایش با اندازه متوسط یا md مانند سیستمهای دسکتاپ (بیشتر از 992 پیکسل و کمتر از 1200 پیکسل)
د) صفحات نمایش با اندازه بزرگ یا lg مانند سیستمهای خاص دسکتاپ (بیشتر از 1200 پیکسل)
نحوه تنظیم این چهار اندازه را در تصویر ذیل مشاهده میکنید:
با کدهای کامل زیر:
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Website</title> <link href="Content/css/bootstrap-rtl.css" rel="stylesheet"> <link href="Content/css/custom.css" rel="stylesheet"> <style> body { padding: 0 16px; } .container { padding: 0 1em; } h4 { margin-top: 1.5em; } .row { margin-bottom: 1.5em; } .row .row { margin-top: 0.8em; margin-bottom: 0; } [class*="col-"] { padding: 1em 0; background-color: rgba(255,195,13,.3); border: 1px solid rgba(255,195,13,.4); } </style> <!--[if lt IE 9]> <script src="Scripts/respond.min.js"></script> <![endif]--> </head> <body> <div class="container"> <h1>master example grid</h1> <div class="row"> <div class="col-lg-4 col-md-1 col-sm-5 col-xs-5"> <span class="visible-lg">.col-lg-4</span> <span class="visible-md">.col-md-1</span> <span class="visible-sm">.col-sm-5</span> <span class="visible-xs">.col-xs-5</span> </div> <div class="col-lg-4 col-md-5 col-sm-1 col-xs-6"> <span class="visible-lg">.col-lg-4</span> <span class="visible-md">.col-md-5</span> <span class="visible-sm">.col-sm-1</span> <span class="visible-xs">.col-xs-6</span> </div> <div class="col-lg-4 col-md-6 col-sm-6 col-xs-1"> <span class="visible-lg">.col-lg-4</span> <span class="visible-md">.col-md-6</span> <span class="visible-sm">.col-sm-6</span> <span class="visible-xs">.col-xs-1</span> </div> </div> <!-- end row --> <h2>xs Grid</h2> <div class="row"> <div class="col-xs-5"> <p>.col-xs-5</p> </div> <div class="col-xs-6"> <p>.col-xs-6</p> </div> <div class="col-xs-1"> <p>.col-xs-1</p> </div> </div> <!-- end row --> <h2>sm Grid</h2> <div class="row"> <div class="col-sm-5"> <p>.col-sm-5</p> </div> <div class="col-sm-1"> <p>.col-sm-1</p> </div> <div class="col-sm-6"> <p>.col-sm-6</p> </div> </div> <!-- end row --> <h2>md Grid</h2> <div class="row"> <div class="col-md-1"> <p>.col-md-1</p> </div> <div class="col-md-5"> <p>.col-md-5</p> </div> <div class="col-md-6"> <p>.col-md-6</p> </div> </div> <!-- end row --> <h2>lg Grid</h2> <div class="row"> <div class="col-lg-4"> <p>.col-lg-4</p> </div> <div class="col-lg-4"> <p>.col-lg-4</p> </div> <div class="col-lg-4"> <p>.col-lg-4</p> </div> </div> <!-- end row --> </div> <!-- /container --> <script src="Scripts/jquery-1.10.2.min.js"></script> <script src="Scripts/bootstrap-rtl.js"></script> </body> </html>
در این حین که عرض مرورگر را تغییر میدهید، به سطر اول، بیش از بقیه توجه کنید. این سطر طوری طراحی شده است که در اندازههای مختلف صفحه، اطلاعات متفاوتی را نمایش میدهد. همچنین سلولهای گریدهای پایین صفحه به صورت عمودی بر روی هم قرار خواهند گرفت.
- در این مثال هر ردیف 12 ستونی، با یک div دارای کلاس row شروع میشود.
- اکنون بر اساس اندازه دستگاهی که قرار است سیستم را مطابق آن طراحی یا بهینه سازی کنیم، میتوان از چهار اندازه یاد شده استفاده کرد. ستونهای col-xs به معنای extra small یا بسیار کوچک هستند. ستونهای دارای کلاس col-sm دارای اندازه کوچک یا small میباشند. ستونهای col-md برای حالت medium devices طراحی شدهاند و col-lg برای حالت large devices و صفحات عریض کاربرد دارند.
بنابراین در بوت استرپ 3 بر اساس اندازه غالب صفحه مرورگر کاربران برنامه میتوان سیستم گرید را بهینه سازی کرد.
- اعدادی که پس از نامهای یاد شده میآیند، جمعشان باید 12 بشود. برای مثال در سطر آخر، سه col-lg-4 داریم و در سطرهای دیگر نیز به همین ترتیب، جمع اعداد ستونها، عدد 12 را تشکیل میدهند.
- اگر نیاز است اطلاعاتی جهت اندازه خاصی نمایش داده شود، مانند سطر اول، از کلاسهایی مانند visible-lg میتوان استفاده کرد.
- style ابتدای مثال نیز صرفا برای رنگی نمایش دادن این سیستم گرید و ارائه توضیحات واضحتری در مورد آن تعریف شدهاند.
نکات تکمیلی سیستم گریدهای بوت استرپ 3
1) ترکیب اندازههای مختلف گریدها با هم
فرض کنید یک ردیف را با چهار ستون col-md-3 طراحی کردهاید. اندازهی صفحه که اندکی کوچکتر شود، تمام این ستونها تبدیل به 4 ردیف خواهند شد و شاید در این حالت بجای داشتن یک سیستم تک ستونی چهار ردیفه، سیستمی 2 ردیفه با 2 ستون، مطلوب کار ما باشد و به این ترتیب قسمت عمدهای از صفحه خالی باقی نماند.
<div class="row"> <div class="col-md-3 col-xs-6"> </div> <div class="col-md-3 col-xs-6"> </div> <div class="col-md-3 col-xs-6"> </div> <div class="col-md-3 col-xs-6"> </div> </div>
در مثال فوق، اگر اندازه صفحه برای حالت md مناسب باشد، 4 ستونه نمایش داده میشود. اگر اندازه اندکی کوچکتر گردد، 2 ستونه میشود؛ بجای تک ستونه صرف حالت col-md.
2) استفاده از div محصور کننده container
<div class="container"> </div>
3) ایجاد فاصله بین ستونها
اگر علاقمند باشید تا بین ستونهای یک گرید فاصله ایجاد کنید، باید از offset استفاده کرد. یک مثال:
<div class="container"> <h4 class="alert alert-info">ایجاد فاصله بین ستونها</h4> <div class="row"> <div class="col-lg-3 col-sm-4"> col-lg-3 col-sm-4 </div> <div class="col-lg-8 col-lg-offset-1 col-sm-7 col-sm-offset-1"> col-lg-8 col-lg-offset-1 col-sm-7 col-sm-offset-1 </div> </div> <!-- end row --> </div> <!-- /container -->
اگر در حالت معمولی، دو ستون با تعاریف col-lg-3 و col-lg-9 تعریف شدهاند، میتوان از ستون دوم یک واحد کم کرد و یک واحد به آفست آن افزود تا از ستون کناری فاصله بگیرد. آفست از سمت چپ ستون عمل میکند و اگر از نسخه RTL استفاده میکنید، از سمت راست.
علت اینکه در اینجا هم از col-lg استفاده شده و هم از col-sm، در قسمت 1 توضیح داده شد. میخواهیم این ردیف حتی در بازه sm نیز دو ستونی نمایش داده شود.
4) تعیین ترتیب ستونها
تعیین ترتیب ستونها نیز یکی دیگر از قابلیتهای جدید گرید بوت استرپ 3 است. مثلا در مثال 3 فوق، با کاهش عرض مرورگر، بالاخره زمانی فرا میرسد که تمام ستونها در قالب یک ردیف نمایش داده خواهند شد. در این حالت اگر ستون سمت راست را منو و ستون سمت چپ را محتوای صفحه فرض کنیم، شاید علاقمند باشیم که بجای اینکه ابتدا منو نمایش داده شود و سپس در ردیف زیرین، محتوای صفحه، این ترتیب معکوس گردد. برای این منظور میتوان از push و pull استفاده کرد:
<div class="container"> <h4 class="alert alert-info">تغییر ترتیب ستونها در اندازههای مختلف صفحه</h4> <div class="row"> <div class="col-lg-offset-1 col-sm-offset-1 col-lg-8 col-sm-7 col-lg-push-3 col-sm-push-4"> col-lg-offset-1 col-sm-offset-1 col-lg-8 col-sm-7 col-lg-push-3 col-sm-push-4 </div> <div class="col-lg-3 col-sm-4 col-lg-pull-9 col-sm-pull-8"> col-lg-3 col-sm-4 col-lg-pull-9 col-sm-pull-8 </div> </div> <!-- end row --> </div> <!-- /container -->
در اینجا در div اول به ازای هر کدام از حالتهای sm و lg مدنظر، یک push اضافه شده است و در div دوم یک pull.
push سبب میشود تا div اول به سمت راست صفحه هدایت گردد و pull باعث خواهد شد تا div دوم به سمت چپ رانده شود (برای آزمایش این مساله یکبار push مربوط به div اول را حذف کنید و نتیجه را در مروگر بررسی کنید و سپس یکبار pull اضافه شده به div دوم را به صورت موقت حذف نمائید).
5) ایجاد ردیفهای تو در تو
یکی از امکانات پیش فرض گریدهای بوت استرپ، امکان قرار دادن کل محتوای یک ردیف داخل ردیف یا ستونی دیگر است. برای مثال محتوایی در ستون دوم نمایش داده میشود و قصد داریم دقیقا در ذیل آن یک ردیف 4 ستونه داشته باشیم. در این حالت تنها کافی است این ردیف را داخل ستون دوم ایجاد کنیم.
6) قابلیتی به نام جامبوترون!
حتما بسیاری از سایتها را دیدهاید که در ابتدای صفحه اول خود، قسمت عمدهای را در بالای صفحه به نمایش یک عکس بزرگ با چند سطر متن داخل آن اختصاص دادهاند. به این کار در بوت استرپ، جامبوترون میگویند. برای تدارک آن نیز باید از یک ردیف 12 ستونی کامل بدون ستون استفاده کرد. یعنی فقط یک row باید ذکر شود. اما بجای row میتوان از کلاس مخصوص دیگری استفاده کرد:
<div class="container"> <h4 class="alert alert-info">جامبوترون!</h4> <div class="jumbotron"> jumbotron <br> jumbotron <br> jumbotron <br> </div> <!-- end row --> </div> <!-- /container -->
در اینجا اگر تصویری را نیز قرار دادید، با استفاده از کلاسهای pull-left یا pull-right میتوان موقعیت تصویر را نیز تغییر داد.
فایلهای نهایی این قسمت را از اینجا نیز میتوانید دریافت کنید:
bs3-sample02.zip
GitHub Actions، یک راهحل Continuous Integration است که توسط آن میتوان یکسری trigger workflowهایی را حین push کردن، ارسال PR و … اجرا کرد. برای کارهایی از قبیل اجرای تستهای خودکار، اجرای یکسری تست و همچنین deploy کردن از آن استفاده میشود. GitHub Actions در واقع یک managed serviceیی است که توسط GitHub ارائه میشود. به این معنا که نیازی نیست خودمان درگیر مدیریت منابع باشیم. همچنین تعداد زیادی اکشن توسط community برای استفاده توسعه داده شدهاند. در ادامه ابتدا مرور سریعی بر GitHub Actions خواهیم داشت، سپس یک مثال از آن را به همراه PowerShell بررسی خواهیم کرد.
ساختار یک اکشن
- Workflow: یکی از مفاهیمی که باید با آن آشنا باشیم workflowها هستند. یک workflow مجموعهایی از jobهایی هستند که در رخدادهای خاصی اجرا میشوند. در واقع یک workflow یک CI pipeline است که با کمک YAML آنها را تعریف میکنیم.
- Runner: اینها به اصطلاح compute machineهایی هستند که workflowها را اجرا میکنند. این runnerها هم میتوانند به صورت سفارشی باشند و هم سرویسهای ارائه شده توسط GitHub باشند.
- Job: مجموعهایی از مراحلی که درون یک runner workspace اجرا میشوند.
- Step: در نهایت stepها هستند که کوچکترین بخش GitHub Actions هستند. stepها میتوانند فایل اسکریپت، Dockerfile یا یک community action باشند.
نمونهی یک Workflow
در ادامه یک workflow را مشاهده میکنید. در اینجا نام آن را به Build Application Code تنظیم کردهایم. سپس با کمک on، تریگر اجرای این workflow را تعیین کردهایم. به این معنا که با push کردن بر روی ریپوزیتوری، workflow اجرا خواهد شد. سپس توسط job، لیست jobهایی را که میخواهیم این workflow اجرا کند، مشخص کردهایم. اولین jobی که اجرا خواهد شد، build است. این job قرار است بر روی یک ماشین با آخرین نگارش ابونتو اجرا شود. مراحل یا stepهای این job نیز به ترتیب، clone کردن سورسکد و سپس نصب وابستگیهای پروژه است. در نهایت job بعدی، test خواهد بود که با کمک needs تعیین کردهایم که ابتدا مرحلهی قبل یعنی build اجرا شود و سپس وارد این مرحله شود.
name: Build Application Code on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Install Libraries uses: pip install -r requirements.txt -t . test: runs-on: ubuntu-latest needs: build steps: ...
مثال PowerShell
هدف، پویا کردن قسمت README یک پروفایل GitHub است. برای این مثال من از پروفایل خودم استفاده خواهم کرد. درون فایل README میخواهم لیست آخرین بلاگپستهایی را که منتشر کردهام، به همراه یک کامپوننت، تعداد قدمهایی را که در طول روز پیادهروی میکنم، نمایش دهم. برای نمایش آخرین دیتای درون پروفایلم، نیاز به دو Action Workflow داشتیم که هر یک در تایم خاصی اجرا شده و اسکریپتهایی را که در ادامه توضیح خواهم داد، اجرا کنند. برای اینکار درون دایرکتوری مخصوص github.، ساختار زیر را ایجاد کردهام:
├── .github │ ├── scripts │ └── workflows ├── README.md ├── assets └── deps
name: Update Recent Blog Posts on: schedule: - cron: "0 0 * * 0" # Run once a week at 00:00 (midnight) on Sunday workflow_dispatch: jobs: update_posts: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching latest blog posts shell: pwsh run: | . ./.github/scripts/Get-Posts.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Get-Posts { Param ( [Parameter(Mandatory = $false)] [string]$rssUrl ) $posts = @() $feed = [xml](Invoke-WebRequest -Uri $rssUrl).Content $feed.rss.channel.item | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.title."#cdata-section" ?? $_.title Link = $_.link Description = $_.description."#cdata-section" ?? $_.description PubDate = $_.pubDate } $posts += $post } $posts } Function Get-DntipsPosts { $assemblyPath = "$(Get-Location)/deps/CodeHollow.FeedReader.dll" [Reflection.Assembly]::LoadFile($assemblyPath) $feed = [CodeHollow.FeedReader.FeedReader]::ReadAsync("https://www.dntips.ir/feed/author/%d8%b3%db%8c%d8%b1%d9%88%d8%a7%d9%86%20%d8%b9%d9%81%db%8c%d9%81%db%8c").Result $posts = @() $feed.Items | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.Title Link = $_.Link Description = $_.Description PubDate = $_.PublishingDate } $posts += $post } $posts } Function Set-Posts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSCustomObject[]]$posts, [Parameter(Mandatory = $false)] [string]$marker = "## Recent Blog Posts - English" ) Begin { $readMePath = "./README.md" $readmeContents = Get-Content -Path $readMePath -Raw $markdownTable = "| Link | Published At |`n" $markdownTable += "| --- | --- |`n" } Process { if ($null -eq $_.Title) { return } $date = Get-Date -Date $_.PubDate $link = "[$($_.Title)]($($_.Link))" $markdownTable += "| $($link) | $($date.ToString("dd/MM/yy")) |`n" } End { $updatedContent = $readmeContents -replace "$marker\n([\s\S]*?)(?=#| $)", "$marker`n$($markdownTable)`n" $updatedContent | Set-Content -Path $readMePath } } Function Set-Blogs { $recentBlogPostsStr = "## Recent blog posts -" Get-Posts("https://dev.to/feed/sirwanafifi") | Set-Posts -marker "$recentBlogPostsStr dev.to" Get-Posts("https://sirwan.infohttps://www.dntips.ir/rss.xml") | Set-Posts -marker "$recentBlogPostsStr sirwan.info" Get-DntipsPosts | Set-Posts -marker "$recentBlogPostsStr dntips.ir" } Set-Blogs
در اینجا تابع Set-Blogs فراخوانی خواهد شد. کاری که این تابع انجام میدهد، دریافت آخرین بلاگپستهایی که در جاهای مختلف منتشر کردهام و سپس آپدیت کردن فایل README با دیتای جدید است. همانطور که مشاهده میکنید برای خواندن فید سایت جاری، از پکیج FeedReader استفاده کردهام. در PowerShell توسط Invoke-WebRequest میتوانیم یک فید را پارز کنیم؛ اما برای سایت جاری با خطا روبرو شدم و در نهایت تصمیم گرفتم از یک پکیج داتنتی استفاده کنم. وابستگی موردنظر، درون دایرکتوری dep به صورت DLL قرار دارد. سپس از طریق PowerShell اسمبلی مربوطه بارگذاری شده و از کتابخانه موردنظر استفاده شدهاست. در نهایت برای آپدیت کردن فایل README.md یکسری marker تعیین کردهام که با یک جایگزینی محتویات موردنظر، آنجا قرار خواهند گرفت.
workflow بعدی نیز به صورت زیر میباشد که در پایان هر روز در یک ساعت مشخص اجرا خواهد شد:
name: Update Step Component on: schedule: - cron: "0 18 * * *" workflow_dispatch: jobs: update_steps: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching my latest steps shell: pwsh env: STEPS_URI: ${{ secrets.STEPS_URI }} run: | . ./.github/scripts/Get-Steps.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Set-Steps { Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject]$json ) Write-Host ($json | ConvertTo-Json) $SvgPath = "$(Get-Location)/assets/step.svg" $SvgContent = Get-Content -Path $SvgPath -Raw $TextTags = @" <tspan id="step-count" font-weight="bold">$([System.String]::Format("{0:n0}", [int]$json.steps))</tspan> "@ $DatetimeTags = "<text id=""datetime"" x=""800"" y=""72"" font-size=""39"" fill="#99989E"">$($json.date)</text>" $SvgContent = $SvgContent -Replace '<tspan id="step-count" font-weight="bold">.*?</tspan>', $TextTags $SvgContent = $SvgContent -Replace '<text id="datetime" x="800" y="72" font-size="39" fill="#99989E">.*?</text>', $DatetimeTags $SvgContent | Set-Content -Path $SvgPath } Function Get-LatestSteps { Try { $Uri = $env:STEPS_URI Write-Host "Uri: $Uri" $JsonResult = (Invoke-WebRequest -Uri $Uri).Content | ConvertFrom-Json Write-Host "Steps: $($JsonResult.steps)" Return $JsonResult } Catch { Return @{ steps = 0 date = Get-Date -Format "yyyy-MM-dd" } } } Write-Host "Getting latest steps..." Get-LatestSteps | Set-Steps Write-Host "Done!"
خروجی در نهایت، اینچنین خواهد بود:
شاید برای شما هم پیش آمده باشد که با Webhookها کار کنید و یا در حین اجرای پروژهی وب خودتان بخواهید خروجی آن را به اطرافیان خود نشان دهید. یکی از راهها این است که پروژه را بر روی یک مخزن Git ارسال کنید و سپس دوستان خودتان را اضافه کنید تا بتوانند پروژه را دریافت و اجرا کنند. البته در این حالت شاید نخواهید کسی سورس شما را ببیند! روش دیگر این است که هاست و دامین بخرید و پروژه را بر روی آن آپلود کنید و در مرحلهی آخر، آدرس وبسایت را برای اطرافیان خود بفرستید که این راه، هم پرهزینه هست و هم ممکن است پروژه کامل نشده باشد که بخواهید آنرا آپلود کنید. اینجاست که Ngrok وارد شده و پروژهی لوکال شما را بر روی Https ارائه میکند.
شروع به کار با ngrok
- قبل از ادامهی این آموزش، وارد سایت https://ngrok.com شده و بعد از ثبت نام، مطابق سیستم عاملی که دارید، فایل مخصوص آن را دریافت کنید.
- بعد از دریافت، فایل زیپ را از حالت فشرده خارج کنید. به محل فایل استخراج شده رفته و در یک مکان خالی در پوشهی جاری، دکمهی Shift را فشرده و سپس کلیک راست کنید و در آخر گزینهی Open Command Window Here را انتخاب نمائید و یا کلا از طریق cmd وارد پوشهی مربوطه شوید.
- سپس پروژهی خود را توسط ویژوال استودیو (IIS Express) و یا بر روی IIS لوکال خود، اجرا کنید.
توجه! این پروژهی آزمایشی صرفا یک صفحهی HTML خیلی ساده بوده که فقط عبارت ngrok را نشان میدهد.
- حال به cmd برمیگردیم و سپس با دستور زیر میتوانیم لوکالهاست خود را بر روی https به اشتراک بگذاریم:
اگر کار را به درستی انجام داده باشید، خروجی شبیه به تصویر زیر را خواهید داشت:
همانطور که ملاحظه میفرمائید، هم http و هم https را در اختیار ما قرار میدهد. برای مثال اگر نیاز است با webhookهای تلگرام کار کنید، باید از آدرس https آن استفاده کنید.
در اینجا در قسمت HTTP Requests میتوانید درخواستهایی را که فرستاده میشوند، ببینید و یا میتوانید با رفتن به آدرس زیر، دستورات بالا را در نمایی گرافیکی و بصورت کامل نظارهگر باشید:
برای نمونه، خروجی آن در گوشی و مرورگر آن، به شکل زیر است:
شروع به کار با ngrok
- قبل از ادامهی این آموزش، وارد سایت https://ngrok.com شده و بعد از ثبت نام، مطابق سیستم عاملی که دارید، فایل مخصوص آن را دریافت کنید.
- بعد از دریافت، فایل زیپ را از حالت فشرده خارج کنید. به محل فایل استخراج شده رفته و در یک مکان خالی در پوشهی جاری، دکمهی Shift را فشرده و سپس کلیک راست کنید و در آخر گزینهی Open Command Window Here را انتخاب نمائید و یا کلا از طریق cmd وارد پوشهی مربوطه شوید.
- سپس پروژهی خود را توسط ویژوال استودیو (IIS Express) و یا بر روی IIS لوکال خود، اجرا کنید.
- همانطور که در تصویر مشاهده میکنید؛ آدرس پروژهی لوکال ما به شکل زیر میباشد:
http://localhost:51095/Home/Index
- حال به cmd برمیگردیم و سپس با دستور زیر میتوانیم لوکالهاست خود را بر روی https به اشتراک بگذاریم:
ngrok http [port] -host-header="localhost:[port]"
همانطور که ملاحظه میفرمائید، هم http و هم https را در اختیار ما قرار میدهد. برای مثال اگر نیاز است با webhookهای تلگرام کار کنید، باید از آدرس https آن استفاده کنید.
در اینجا در قسمت HTTP Requests میتوانید درخواستهایی را که فرستاده میشوند، ببینید و یا میتوانید با رفتن به آدرس زیر، دستورات بالا را در نمایی گرافیکی و بصورت کامل نظارهگر باشید:
http://127.0.0.1:4040
برای نمونه، خروجی آن در گوشی و مرورگر آن، به شکل زیر است:
در قسمت آخر این سری، نگاهی خواهیم داشت به نحوهی توزیع برنامههای 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
در انتهای مطلب « تزریق خودکار وابستگیها در برنامههای ASP.NET MVC » اشارهای کوتاه به روش DependencyResolver توکار Web API شد که این روش پس از بررسیهای بیشتر (^ و ^) به دلیل ماهیت service locator بودن آن و همچنین از دست دادن Context جاری Web API، مردود اعلام شده و استفاده از IHttpControllerActivator توصیه میگردد. در ادامه این روش را توسط Structure map 3 پیاده سازی خواهیم کرد.
پیش نیازها
- شروع یک پروژهی جدید وب با پشتیبانی از Web API
- نصب دو بستهی نیوگت مرتبط با Structure map 3
پیاده سازی IHttpControllerActivator توسط Structure map
در اینجا نحوهی پیاده سازی IHttpControllerActivator را توسط StructureMap ملاحظه میکنید.
نکتهی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب میشود تا کلیه کلاسهای IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.
معرفی StructureMapHttpControllerActivator به برنامه
فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
در ابتدا تنظیمات متداول کلاسها و اینترفیسها صورت میگیرد. سپس نحوهی معرفی StructureMapHttpControllerActivator را به GlobalConfiguration.Configuration.Services مخصوص Web API ملاحظه میکنید. این مورد سبب میشود تا به صورت خودکار کلیه وابستگیهای مورد نیاز یک Web API Controller به آن تزریق شوند.
تهیه سرویسی برای آزمایش برنامه
در اینجا یک سرویس ساده ارسال ایمیل را بدون پیاده سازی خاصی مشاهده میکنید.
نکتهی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شدهاست). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.
استفاده از سرویس تعریف شده در یک Web API Controller
در اینجا مثال سادهای را از نحوهی تزریق سرویس ارسال ایمیل را در ValuesController مشاهده میکنید.
تزریق وهلهی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت میگیرد.
فراخوانی متد Get آنرا نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
درون متد Get کنترلر، یک break point قرار دهید. همچنین داخل متد Dispose لایه سرویس نیز جهت بررسی بیشتر یک break point قرار دهید.
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهلهی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
WebApiDISample.zip
پیش نیازها
- شروع یک پروژهی جدید وب با پشتیبانی از Web API
- نصب دو بستهی نیوگت مرتبط با Structure map 3
PM>install-package structuremap PM>install-package structuremap.web
پیاده سازی IHttpControllerActivator توسط Structure map
using System; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using StructureMap; namespace WebApiDISample.Core { public class StructureMapHttpControllerActivator : IHttpControllerActivator { private readonly IContainer _container; public StructureMapHttpControllerActivator(IContainer container) { _container = container; } public IHttpController Create( HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { var nestedContainer = _container.GetNestedContainer(); request.RegisterForDispose(nestedContainer); return (IHttpController)nestedContainer.GetInstance(controllerType); } } }
نکتهی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب میشود تا کلیه کلاسهای IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.
معرفی StructureMapHttpControllerActivator به برنامه
فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http; using System.Web.Http.Dispatcher; using StructureMap; using WebApiDISample.Core; using WebApiDISample.Services; namespace WebApiDISample { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // IoC Config ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>()); var container = ObjectFactory.Container; GlobalConfiguration.Configuration.Services.Replace( typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
تهیه سرویسی برای آزمایش برنامه
namespace WebApiDISample.Services { public interface IEmailsService { void SendEmail(); } } using System; namespace WebApiDISample.Services { /// <summary> /// سرویسی که دارای قسمت دیسپوز نیز هست /// </summary> public class EmailsService : IEmailsService, IDisposable { private bool _disposed; ~EmailsService() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void SendEmail() { //todo: send email! } protected virtual void Dispose(bool disposeManagedResources) { if (_disposed) return; if (!disposeManagedResources) return; //todo: clean up resources here ... _disposed = true; } } }
نکتهی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شدهاست). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.
استفاده از سرویس تعریف شده در یک Web API Controller
using System.Web.Http; using WebApiDISample.Services; namespace WebApiDISample.Controllers { public class ValuesController : ApiController { private readonly IEmailsService _emailsService; public ValuesController(IEmailsService emailsService) { _emailsService = emailsService; } // GET api/values/5 public string Get(int id) { _emailsService.SendEmail(); return "_emailsService.SendEmail(); called!"; } } }
تزریق وهلهی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت میگیرد.
فراخوانی متد Get آنرا نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2> @section scripts { <script type="text/javascript"> $(function () { $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) { alert(data); }); }); </script> }
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهلهی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
WebApiDISample.zip
در ادامه سری مقالات مرتبط با برنامه نویسی تابعی ، قصد دارم به استفاده کردن یا نکردن از نوعهای داده اولیه (Primitive Types) را بررسی کنیم. پیشنهاد میکنم در صورتی که قسمتهای قبلی را مطالعه نکرده اید ابتدا قسمتهای قبل را بخوانید.
در طراحی مدل دامین، بیشتر مواقع از نوعهای اولیه مانند int , string,… استفاده میکنیم و به عبارتی میتوانیم بگوییم در استفاده از این نوع داده وسواس داریم. قطعه کد زیر را در نظر بگیرید:
کلاس UserFactory، یک متد به نام CreateUser دارد که یک رشته را به عنوان ورودی میگیرد و یک شیء از کلاس User را بر میگرداند. خوب مشکل این متد کجاست؟
اگر به خاطر داشته باشید، در قسمتهای قبلی در مورد مفهومی به نام Honesty صحبت کردیم. به طور ساده باید بتوانیم از روی امضای تابع، کاری را که تابع انجام میدهد و خروجی آن را ببینیم. این تابع Honest نیست؛ شرایطی که string میتواند درست نباشد، خالی باشد، طول غیر مجاز داشته باشد و ... را نمیتوانیم از امضای تابع حدس بزنیم.
برای روشنتر شدن بحث، مثال بالا را همیشه در ذهن خود داشته باشید. در این مثال، در تابع Divide که عمل تقسیم را انجام میدهد، پارامتر y که یک عدد از نوع int است، میتواند مقدار صفر را داشته باشد و باعث یک exception شود.و از آنجائیکه نوع خروجی این متد هم int است، انتظار دریافت یک exception را نداریم. در مورد exceptionها به طول مفصل در قسمت قبلی صحبت کردیم. در مثال بالا تصور کنید که بجای یک ایمیل، از چند ایمیل به عنوان ورودی میخواهید استفاده کنید. آیا منطق Validation را به ازای هر پارامتر ورودی باید تکرار کنید؟
به طور کلی استفادهی نابجا و بیش از حد از نوعهای دادهی اولیه، باعث میشود تا Honesty متدها را از دست بدهیم و قاعدهی DRY را نقض کنیم.
به جای نوعهای اولیه از چی استفاده کنیم؟
جواب خیلی سادهاست؛ شما نیاز دارید تا یک Type اختصاصی را ایجاد کنید. برای مثال بجای استفاده از نوع string برای یک ایمیل، میتوانید یک کلاس را به عنوان Email ایجاد کنید که مشخصهای به نام Value دارد. این کار به روشهای مختلفی قابل انجام است؛ اما پیشنهاد من استفاده از این روش هست:
در این روش، یک کلاس را به عنوان Value Object ایجاد کردهایم. این کلاس، نوع اولیهای را که با آن سر و کار داریم، در بر خواهد گرفت و منطق مربوط به مقایسه، همچنین عملگرهای == و != را هم از طریق Equals و GetHashCode، پیاده سازی کرده. برای مثال جهت کلاس ایمیل میتوانیم به صورت زیر عمل کنیم:
همچنین برای مقدار دهی این کلاس میتوانید به صورت زیر عمل کنید:
برای مثالهای پیچیدهتر مانند آدرس، که شامل آدرس، کد پستی و … میباشد، میتوانید با استفاده از امکان Tupleها که از سی شارپ 7 به بعد معرفی شده، مانند مثال زیر عمل کنید:
و در نهایت برای نوشتن منطق مربوط به validation میتوانید متد Validate را Override کنید و قاعدهی DRY را هم نقض نکنید.
روش معرفی شدهی در این مقاله، صرفا جهت آشنایی بیشتر شما و داشتن کدی تمیزتر از طریق مفاهیم برنامه نویسی تابعی خواهد بود. در دنیای واقعی، احتمالا مسائلی را برای ذخیره سازی این آبجکتها و یا کار با کتابخانههایی مانند Entity Framework خواهید داشت که به سادگی قابل حل است.
در صورتیکه مشکلی در پیاده سازی داشتید، میتوانید مشکل خود را زیر همین مطلب و یا بر روی gist آن کامنت کنید.
در طراحی مدل دامین، بیشتر مواقع از نوعهای اولیه مانند int , string,… استفاده میکنیم و به عبارتی میتوانیم بگوییم در استفاده از این نوع داده وسواس داریم. قطعه کد زیر را در نظر بگیرید:
public class UserFactory { public User CreateUser(string email) { return new User(email); } }
اگر به خاطر داشته باشید، در قسمتهای قبلی در مورد مفهومی به نام Honesty صحبت کردیم. به طور ساده باید بتوانیم از روی امضای تابع، کاری را که تابع انجام میدهد و خروجی آن را ببینیم. این تابع Honest نیست؛ شرایطی که string میتواند درست نباشد، خالی باشد، طول غیر مجاز داشته باشد و ... را نمیتوانیم از امضای تابع حدس بزنیم.
صحبت در مورد استفاده کردن یا نکردن، جنبههای زیادی دارد و یکی از مواردی است که در معماری DDD تحت عنوان Value Object به آن پرداخته شده. هدف ما در این قسمت از مقاله، صرفا پرداختن به گوشهای از این مورد هست. ولی شما میتوانید برای مطالعه بیشتر و اطلاعات تکمیلی کتاب Domain-Driven Design: Tackling Complexity in the Heart of Software نوشته Eric Evans را مطالعه کنید.
جواب خیلی سادهاست؛ شما نیاز دارید تا یک Type اختصاصی را ایجاد کنید. برای مثال بجای استفاده از نوع string برای یک ایمیل، میتوانید یک کلاس را به عنوان Email ایجاد کنید که مشخصهای به نام Value دارد. این کار به روشهای مختلفی قابل انجام است؛ اما پیشنهاد من استفاده از این روش هست:
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ValueOf { public class ValueOf<TValue, TThis> where TThis : ValueOf<TValue, TThis>, new() { private static readonly Func<TThis> Factory; /// <summary> /// WARNING - THIS FEATURE IS EXPERIMENTAL. I may change it to do /// validation in a different way. /// Right now, override this method, and throw any exceptions you need to. /// Access this.Value to check the value /// </summary> protected virtual void Validate() { } static ValueOf() { ConstructorInfo ctor = typeof(TThis) .GetTypeInfo() .DeclaredConstructors .First(); var argsExp = new Expression[0]; NewExpression newExp = Expression.New(ctor, argsExp); LambdaExpression lambda = Expression.Lambda(typeof(Func<TThis>), newExp); Factory = (Func<TThis>)lambda.Compile(); } public TValue Value { get; protected set; } public static TThis From(TValue item) { TThis x = Factory(); x.Value = item; x.Validate(); return x; } protected virtual bool Equals(ValueOf<TValue, TThis> other) { return EqualityComparer<TValue>.Default.Equals(Value, other.Value); } public override bool Equals(object obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; return obj.GetType() == GetType() && Equals((ValueOf<TValue, TThis>)obj); } public override int GetHashCode() { return EqualityComparer<TValue>.Default.GetHashCode(Value); } public static bool operator ==(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b) { if (a is null && b is null) return true; if (a is null || b is null) return false; return a.Equals(b); } public static bool operator !=(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b) { return !(a == b); } public override string ToString() { return Value.ToString(); } } }
public class EmailAddress : ValueOf<string, EmailAddress> { }
EmailAddress emailAddress = EmailAddress.From("foo@bar.com");
public class Address : ValueOf<(string firstLine, string secondLine, Postcode postcode), Address> { }
روش معرفی شدهی در این مقاله، صرفا جهت آشنایی بیشتر شما و داشتن کدی تمیزتر از طریق مفاهیم برنامه نویسی تابعی خواهد بود. در دنیای واقعی، احتمالا مسائلی را برای ذخیره سازی این آبجکتها و یا کار با کتابخانههایی مانند Entity Framework خواهید داشت که به سادگی قابل حل است.
در صورتیکه مشکلی در پیاده سازی داشتید، میتوانید مشکل خود را زیر همین مطلب و یا بر روی gist آن کامنت کنید.
نظرات مطالب
خلاصه اشتراکهای روز جمعه 4 آذر 1390
یک سری توهماتی این دور و اطراف هست مانند اینکه لینوکس نیازی به ویروس یاب نداره، لینوکس امنه، لینوکس خیلی گله! بی نظیرتر از اون نیست؛ ولی واقعیت این است که این سیستم عامل زمانیکه از اون حد کاربران یک یا چند درصدی دستکاپ خودش فاصله میگیره و مثلا میشه Android، تازه مشخص میشه که ای بابا! ویندوز هم امنه! اگر کاربری نمیدونه هر فایلی رو نباید اجرا کنه، هر دسترسی رو نباید بده این مشکل سیستم عامل نیست. ولی خوب این هجمه سیاسی و عموما کودکانه علیه ویندوز وجود داره و گاهی هم لازم هست اینها رو کمی عیانتر بیان کرد با مثالهای دنیای واقعی تا کمی از توهمات دور بشیم که بله؛ اگر از کاربران حرفهای سرورها فاصله بگیریم میرسیم به «عوام». میرسیم به دنیایی خارج از یک یا چند درصد کاربری دسکتاپ. اینجا است که حالا باید دید سیستم عامل تا چه حد میتونه مؤثر واقع بشه، یا اینکه رکورد بزنه تا تاریخ امروز.
نظرات مطالب
بررسی دقیقتر صفحات آبی ویندوز
مشکل شما به احتمال زیاد سخت افزاری است و نرم افزاری نیست مثل مثالهای قبل.
دو مورد را باید بررسی کنید:
-رم معیوب
-مشکل هارد یا اتصالات مشکل دار آن (کابل قطعی دار). تعدادی پیغام در مورد fltmgr.sys و Ntfs.sys و fileinfo.sys در این دامپها بود که مربوط است به مشکلات هارد یا مشکلات اتصالات مرتبط.
برای تست رم از برنامه زیر استفاده کنید. یک سی دی bootable برای شما درست میکند که در حین راه اندازی اولیه به دور از هر چیزی دیگری شروع به تست RAM خواهد کرد و به این صورت دقیقا مشخص میشود که رم معیوب است یا خیر:
http://www.memtest86.com/download.html
- ضمنا یکی دو مورد هم nvlddmkm.sys و dxgkrnl.sys در این دامپها بود. این عموما ناشی از نصب داریور قدیمی کارت گرافیک روی سیستم عامل جدید است (که رسما سیستم عامل را معیوب میکند). بنابراین قبل از اینکه دست به ترکیب سخت افزاری بزنید، یک سیستم عامل جدید به علاوه آخرین درایورهای همین امسال را نصب کنید.
البته تست RAM و تست هارد و کابلها اول باید انجام شود.
- همچنین در یکی از دامپها مشکلات avipbb.sys هم موجود بود که مربوط است به آنتی ویروس avira. اگر این نصب است پاکش کنید.
دو مورد را باید بررسی کنید:
-رم معیوب
-مشکل هارد یا اتصالات مشکل دار آن (کابل قطعی دار). تعدادی پیغام در مورد fltmgr.sys و Ntfs.sys و fileinfo.sys در این دامپها بود که مربوط است به مشکلات هارد یا مشکلات اتصالات مرتبط.
برای تست رم از برنامه زیر استفاده کنید. یک سی دی bootable برای شما درست میکند که در حین راه اندازی اولیه به دور از هر چیزی دیگری شروع به تست RAM خواهد کرد و به این صورت دقیقا مشخص میشود که رم معیوب است یا خیر:
http://www.memtest86.com/download.html
- ضمنا یکی دو مورد هم nvlddmkm.sys و dxgkrnl.sys در این دامپها بود. این عموما ناشی از نصب داریور قدیمی کارت گرافیک روی سیستم عامل جدید است (که رسما سیستم عامل را معیوب میکند). بنابراین قبل از اینکه دست به ترکیب سخت افزاری بزنید، یک سیستم عامل جدید به علاوه آخرین درایورهای همین امسال را نصب کنید.
البته تست RAM و تست هارد و کابلها اول باید انجام شود.
- همچنین در یکی از دامپها مشکلات avipbb.sys هم موجود بود که مربوط است به آنتی ویروس avira. اگر این نصب است پاکش کنید.
در سلسله مقالات قبلی ما فصل اول از بخش اول را به پایان بردیم و مبحث آشنایی با CLR و نحوهی اجرای برنامه را یاد گرفتیم. در این سلسله مقالات که مربوط به فصل دوم از بخش اول است، در مورد نحوهی ساخت و توزیع برنامه صحبت میکنیم.
در طی این سالها ویندوز به ناپایداری و پپیچیدگی متهم شده است. صرف نظر از این که ویندوز شایستگی این اتهامات را دارد یاخیر، این اتهامات نتیجهی چند عامل است:
اول از همه برنامهها از dll هایی استفاده میکنند که بسیاری از آنها نوشتهی برنامه نویسانشان نیست و توسط توسعه دهندگان دیگر ارائه شدهاند و توسعه دهندگان مربوطه نمیتوانند صد در صد مطمئن شوند که افراد دیگر، به چه نحوی از dll آنها استفاده میکنند و در عمل ممکن هست باعث دردسرهای زیادی شود که البته این نوع مشکلات عموما از قبل خودشان را نشان نمیدهند، چرا که توسط سازندهی برنامه تست و دیباگ شدهاند.
موقعی کاربرها بیشتر دچار دردسر میگردند که برنامههای خودشان را به روز میکنند و عموما شرکتها در آپدیتها، فایلهای جدید زیادی را روی سیستم کاربر منتقل میکنند که ممکن هست سازگاری با فایلهای قبلی موجود نداشته باشند و از آنجا که همیشه تست این مورد برای توسعه دهنده امکان ندارد، به مشکلاتی بر میخورند و نمیتوانند صد در صد مطمئن باشند که تغییرات جدید باعث تاثیر ناخوشایند نمیشود.
مطمئن هستم شما بسیاری از این مشکلات را دیدهاید که کاربری یک برنامه را نصب میکند و شما متوجه میشوید که یک برنامهی از قبل نصب شده به خاطر آن دچار مشکل میشود و این مورد به DLL hell مشهور هست. این مورد باعث ایجاد ترس و لرز برای کاربر شده تا با دقت بیشتری به نصب برنامهها بپردازد.
دومین مورد مربوط به نصب برنامهها است که متهم به پیچیدگی است. امروزه هر برنامهای که روی سیستم نصب میشود، بر همه جای سیستم تاثیر میگذارد. یک برنامه را نصب میکنید و به هر دایرکتوری تعدادی فایل کپی میشود. تنظیمات ریجستری را آپدیت میکند، یک آیکن روی دسکتاپ و یکی هم start menu یا مترو را اضافه میکند. به این معنی که یک نصب کننده به عنوان یک موجودیت واحد شناخته نمیشود. شما نمیتونید راحت از یک برنامه بکاپ بگیرید. باید فایلهای مختلفش را جمع آوری کنید و تنظیمات ریجیستری را ذخیره کنید. عدم امکان انتقال یک برنامه به یک سیستم دیگر هم وجود دارد که باید مجدد برنامه را نصب کنید و نکتهی نهایی، حذف برنامه که گاهی اوقات حذف کامل نیست و به شکل نامنظم و کثیفی اثراتش را به جا میگذارد.
سومین مورد امنیت هست. موقعی که کاربر برنامهای را نصب میکند انواع فایلها از شرکت و تولید کنندههای مختلف روی سیستم نصب میشوند. گاهی اوقات برنامهها بعضی از فایل هایشان را از روی اینترنت دریافت میکنند و کاربر اصلا متوجه موضوع نمیشود و این فایلها میتوانند هر کاری از حذف فایل از روی سیستم گرفته تا ارسال ایمیل را انجام بدهند که این موارد باعث وحشت کاربرها از نصب یک برنامهی جدید میشود که این مورد را با قرار دادن یک سیستم امنیت داخلی با اجازه و عدم اجازه کاربر میشود تا حدی رفع کرد.
دات نت فریمورک هم این معضل را به طور عادی در زمینهی DLL hellدارد که در فصل آتی حل آن بررسی خواهد شد. ولی بر خلاف COM، نوعهای موجود در دات نت نیازی به ذخیره تنظیمات در ریجستری ندارند؛ ولی متاسفانه لینکهای میانبر هنوز وجود دارند. در زمینه امنیت دات نت شامل یک مدل امنیتی به نام Code Access security میباشد؛ از آنجا که امنیت ویندوز بر اساس هویت کاربر تامین میشود. code access security به برنامههای میزبان مثل sql server اجازه میدهد که مجوز مربوطه را خودشان بدهند تا بدین صورت بر اعمال کامپوننتهای بار شده نظارت داشته باشند که البته این مجوزها در حد معمولی و اندک هست. ولی اگر برنامه خود میزبان که به طور محلی روی سیستم نصب میشوند، باشد دسترسی کاملب به مجوزها را دارد. پس بدین صورت کاربر این اجازه را دارد که بر آن چیزی که روی سیستم نصب یا اجرا میشود، نظارت داشته باشه تا کنترل سیستم به طور کامل در اختیار او باشد.
در قسمت بعدی با نحوه توزیع برنامه آشنا خواهیم شد.
در طی این سالها ویندوز به ناپایداری و پپیچیدگی متهم شده است. صرف نظر از این که ویندوز شایستگی این اتهامات را دارد یاخیر، این اتهامات نتیجهی چند عامل است:
اول از همه برنامهها از dll هایی استفاده میکنند که بسیاری از آنها نوشتهی برنامه نویسانشان نیست و توسط توسعه دهندگان دیگر ارائه شدهاند و توسعه دهندگان مربوطه نمیتوانند صد در صد مطمئن شوند که افراد دیگر، به چه نحوی از dll آنها استفاده میکنند و در عمل ممکن هست باعث دردسرهای زیادی شود که البته این نوع مشکلات عموما از قبل خودشان را نشان نمیدهند، چرا که توسط سازندهی برنامه تست و دیباگ شدهاند.
موقعی کاربرها بیشتر دچار دردسر میگردند که برنامههای خودشان را به روز میکنند و عموما شرکتها در آپدیتها، فایلهای جدید زیادی را روی سیستم کاربر منتقل میکنند که ممکن هست سازگاری با فایلهای قبلی موجود نداشته باشند و از آنجا که همیشه تست این مورد برای توسعه دهنده امکان ندارد، به مشکلاتی بر میخورند و نمیتوانند صد در صد مطمئن باشند که تغییرات جدید باعث تاثیر ناخوشایند نمیشود.
مطمئن هستم شما بسیاری از این مشکلات را دیدهاید که کاربری یک برنامه را نصب میکند و شما متوجه میشوید که یک برنامهی از قبل نصب شده به خاطر آن دچار مشکل میشود و این مورد به DLL hell مشهور هست. این مورد باعث ایجاد ترس و لرز برای کاربر شده تا با دقت بیشتری به نصب برنامهها بپردازد.
دومین مورد مربوط به نصب برنامهها است که متهم به پیچیدگی است. امروزه هر برنامهای که روی سیستم نصب میشود، بر همه جای سیستم تاثیر میگذارد. یک برنامه را نصب میکنید و به هر دایرکتوری تعدادی فایل کپی میشود. تنظیمات ریجستری را آپدیت میکند، یک آیکن روی دسکتاپ و یکی هم start menu یا مترو را اضافه میکند. به این معنی که یک نصب کننده به عنوان یک موجودیت واحد شناخته نمیشود. شما نمیتونید راحت از یک برنامه بکاپ بگیرید. باید فایلهای مختلفش را جمع آوری کنید و تنظیمات ریجیستری را ذخیره کنید. عدم امکان انتقال یک برنامه به یک سیستم دیگر هم وجود دارد که باید مجدد برنامه را نصب کنید و نکتهی نهایی، حذف برنامه که گاهی اوقات حذف کامل نیست و به شکل نامنظم و کثیفی اثراتش را به جا میگذارد.
سومین مورد امنیت هست. موقعی که کاربر برنامهای را نصب میکند انواع فایلها از شرکت و تولید کنندههای مختلف روی سیستم نصب میشوند. گاهی اوقات برنامهها بعضی از فایل هایشان را از روی اینترنت دریافت میکنند و کاربر اصلا متوجه موضوع نمیشود و این فایلها میتوانند هر کاری از حذف فایل از روی سیستم گرفته تا ارسال ایمیل را انجام بدهند که این موارد باعث وحشت کاربرها از نصب یک برنامهی جدید میشود که این مورد را با قرار دادن یک سیستم امنیت داخلی با اجازه و عدم اجازه کاربر میشود تا حدی رفع کرد.
دات نت فریمورک هم این معضل را به طور عادی در زمینهی DLL hellدارد که در فصل آتی حل آن بررسی خواهد شد. ولی بر خلاف COM، نوعهای موجود در دات نت نیازی به ذخیره تنظیمات در ریجستری ندارند؛ ولی متاسفانه لینکهای میانبر هنوز وجود دارند. در زمینه امنیت دات نت شامل یک مدل امنیتی به نام Code Access security میباشد؛ از آنجا که امنیت ویندوز بر اساس هویت کاربر تامین میشود. code access security به برنامههای میزبان مثل sql server اجازه میدهد که مجوز مربوطه را خودشان بدهند تا بدین صورت بر اعمال کامپوننتهای بار شده نظارت داشته باشند که البته این مجوزها در حد معمولی و اندک هست. ولی اگر برنامه خود میزبان که به طور محلی روی سیستم نصب میشوند، باشد دسترسی کاملب به مجوزها را دارد. پس بدین صورت کاربر این اجازه را دارد که بر آن چیزی که روی سیستم نصب یا اجرا میشود، نظارت داشته باشه تا کنترل سیستم به طور کامل در اختیار او باشد.
در قسمت بعدی با نحوه توزیع برنامه آشنا خواهیم شد.