Unity 3 منتشر شد
با توجه به ابتدای آموزش، کدهای زیر را در ترمینال وارد میکنیم:
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list' sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 sudo apt-get update
در خط دوم، کلید اختصاصی این پکیج جهت اعتبارسنجی معرفی میگردد که در سایت جاری قبلا به آن پرداخته شده است. در خط آخر هم مخازن موجود را به روزرسانی میکنیم تا آماده استفاده شود.
در سایت مایکروسافت از شما خواسته میشود که کد زیر را وارد کنید:
sudo apt-get install dotnet-dev-1.0.0-preview1-002702
sudo apt-get install dotnet-dev-1.0.0-preview2-003096
The following packages have unmet dependencies: dotnet-dev-1.0.0-preview1-002702 : Depends: dotnet-sharedframework-microsoft.netcore.app-1.0.0-rc2-3002702 but it is not going to be installed E: Unable to correct problems, you have held broken packages.
sudo dpkg -i libicu52_52.1-3ubuntu0.4_amd64.deb
sudo apt-get install dotnet-sharedframework-microsoft.netcore.app-1.0.0-rc2-3002702 sudo apt-get install dotnet-dev-1.0.0-preview2-003096
dotnet --version
1.0.0-preview2-003096
کاربران امروزه با عناصری که به نحوی خاص درون صفحه عمل میکنند، آشنا شدهاند. بنابراین انتخاب مناسب برای اتخاذ این عناصر زمانی مناسب است که به تکمیل کارآیی و رضایت کاربر کمک کند.
یک کامپوننت در واقع از دو بخش تشکیل شدهاست:
1 - اول اینکه چگونه به نظر میرسد ( UI ).
2 – دوم اینکه چگونه کار میکند ( UX ).
این عناصر رابط ( component ) شامل :
Input Controls : check boxes, radio buttons, drop down lists, list boxes, buttons, toggles, text fields, date field
Navigational Components : breadcrumb, slider, search field, pagination, slider, tags, icons
Informational Components : tool tips, icons, progress bar, notifications, message boxes, modal windows
Containers : accordion
اما باید توجه داشت که فقط به این موارد محدود نمیشوند.
در این
قسمت به طور مختصر با این دست از کامپوننتها ( UI Component ) آشنا میشویم.
کامپوننت V-Alert برای انتقال اطلاعات مهم به کاربر مورد استفاده قرار میگیرد. این کامپوننت چهار نوع اطلاعات را به کاربر گوشزد میکند که شامل موفقیتها، اطلاعات، هشدارها و خطاها میباشد.
هشدارها میتوانند یک رنگ خاص را داشته باشند که به طور پیش فرض نمایش داده نمیشوند.
در مثال پایین، کامپوننت v-alert شامل دو مقدار است که برای آن تنظیم شدهاست. مقدار (value) که شامل یک مقدار Boolean است و مقدار (type) که مشخص کننده نوع هشدار است (موفقیت ، اطلاعات ، هشدار و خطا).
در قطعه کد پایین، این چهار نوع اطلاعات قابل نمایش به کاربر مشخص شدهاند:
<div id="app"> <v-app id="inspire"> <div> <v-alert :value="true" type="success"> This is a success alert. </v-alert> <v-alert :value="true" type="info"> This is a info alert. </v-alert> <v-alert :value="true" type="warning"> This is a warning alert. </v-alert> <v-alert :value="true" type="error"> This is a error alert. </v-alert> </div> </v-app> </div>
برای کامپوننت V-Alert میتوان propertiesهای مختلفی را مشخص نمود که از جمله آنها میتوان به موارد زیر اشاره کرد:
Color : به وسیله این property میتوان رنگ پیغام را مشخص نمود. هم به وسیله نام رنگ میتوان رنگ مورد نظر را مشخص کرد و هم به وسیلهی کد RGB این کار را انجام داد. dismissible : این تنظیم مشخص میکند که پیغام، قابلیت بسته شدن را دارد یا خیر که حاوی یک مقدار Boolean است.
icon : مشخص کننده یک نماد خاص است که درون جعبه پیغام قرار میگیرد.
type : مشخص کننده نوع پیام است که پیشتر در مورد آن توضیح داده شد.
کامپوننت v-avatar برای تغییر اندازه تصاویر مورد استفاده قرار میگیرد که معمولا جهت نمایش عکس پروفایل استفاده میشود.
طریقه استفاده :
avatar، دارای یک اندازهی پویا است که میتواند برای هر وضعیتی تغییر کند.
برای این کامپوننت سه properties قابل تنظیم است:
color : به وسیله این property میتوان رنگ دلخواهی را برای آواتار مشخص نمود. هم به وسیله نام رنگ میتوان رنگ مورد نظر را مشخص کرد و هم به وسیله کد RGB این کار را صورت داد.
size : به طور پیشفرض برای avatar، سایز 48 تنظیم شدهاست که میتوان این میزان را کم و یا زیاد کرد.
tile : همانند border radius در css عمل میکند. با تنظیم این گزینه میتوانیم یک آواتار گرد داشته باشیم.
به وسیله این کامپوننت میتوان نمادهایی را برای نمایش اطلاعاتی به کاربر یا جلب توجه کاربر به یک عنصر خاص، ایجاد نمود.
این کامپوننت نیز properties خاص خود را دارد که از جمله آن میتوان به color , left , mode , overlab و غیره اشاره کرد.
قطعه کد پایین نشان دهنده چگونگی عملکرد این کامپوننت است:
<div id="app"> <v-app id="inspire"> <div> <v-badge color="purple" left overlap> <template v-slot:badge> <v-icon dark small> done </v-icon> </template> <v-icon color="grey lighten-1" large> account_circle </v-icon> </v-badge> <v-badge overlap color="orange"> <template v-slot:badge> <v-icon dark small> notifications </v-icon> </template> <v-icon large color="grey darken-1"> account_box </v-icon> </v-badge> </div> </v-app> </div>
نتیجه قطعه کد بالا بدین ترتیب است:
این کامپوننت را میتوان جایگزین sidebarها نمود. این کامپوننت در درجه اول در موبایل مورد استفاده قرار میگیرد که میتواند شامل متن و یا آیکن باشد.
به وسیله این کامپوننت امکان انتقال از یک بخش از صفحه به بخشی دیگر امکان پذیر میشود.
این کامپوننت نیز properties خاص خود را دارد که از جمله آن میتوان به active-sync (برای نشان دادن فعال یا غیر فعال بودن گزینه انتخاب شده)، fixed ( برای مشخص کردن موقعیت کامپوننت در صفحه) و موارد دیگر اشاره کرد.
تقسیم بندی اجزاء این کامپوننت به شرح زیر است:
1 - محل قرار گیری کامپوننت
2- آیکن غیر فعال
3- برچسب غیر فعال
4 - آیکن فعال
5- برچسب فعال
قطعه کد پایین نشان دهنده چگونگی یک bottom navbar است:
<div id="app"> <v-app id="inspire"> <v-card height="200px" flat> <div> Active: {{ bottomNav }} // </div> <v-bottom-nav :active.sync="bottomNav" :value="true" absolute color="transparent"> <v-btn color="teal" flat value="recent"> <span>Recent</span> <v-icon>history</v-icon> </v-btn> <v-btn color="teal" flat value="favorites"> <span>Favorites</span> <v-icon>favorite</v-icon> </v-btn> <v-btn color="teal" flat value="nearby"> <span>Nearby</span> <v-icon>place</v-icon> </v-btn> </v-bottom-nav> </v-card> </v-app> </div>
این کامپوننت برای ایجاد یک دکمه چه به صورت متن و یا آیکن مورد استفاده قرار میگیرد. دکمهها به کاربران این امکان را میدهند تا اقداماتی را انجام دهند و انتخابهای خود را تنها با یک کلیک انجام دهند.
از دکمهها ممکن است در جاهای مختلف صفحه به خصوص در دیالوگ باکسها، فرمها و ابزارها مورد استفاده قرار گیرد.
کامپوننت v-btn نیز مانند سایر کامپوننتها تنظیمات خاص خود را دارد که از جمله آن میتوان به کوچکی و بزرگی دکمه، فعال یا غیر فعال بودن دکمه، نوع متن یا آیکن بودن دکمه اشاره نمود.
حالتهای مختلفی از دکمهها وجود دارند که میتوانند به بهتر شدن UI برنامه ما کمک کنند. برای مثال میتوان به موارد زیر اشاره کرد:
button drop-down variants : دکمههای کرکرهای که معمولا برای نظم و کم جا بودن در صفحه مورد استفاده قرار میگیرند.
icons : آیکنها میتوانند برای محتوای اصلی یک دکمه مورد استفاده قرار بگیرند تا ظاهر زیباتری را به دکمه ما بدهند.
floating : این دکمهها حالت آیکن را دارند؛ با این تفاوت که آیکن مورد نظر، درون یک محتوا قرار میگیرد.
loaders : به وسیله این دکمهها میتوان کاربر را متوجه انجام یک پردازش نمود. به صورت پیشفرض بعد از فشردن این نوع دکمهها محتوای دکمه فشرده شده تغییر ظاهر داده و به شکل یک دایره در حال چرخش در میآید. البته میتوان این پیشفرض را به حالتهای دیگری نیز تغییر داد.
round : این نوع دکمهها دقیقا کارآیی دکمههای معمولی را دارند؛ با این تفاوت که این دکمهها دارای لبههایی گرد هستند.
یک نمونه از ایجاد انواع دکمهها در زیر آمده است:
<div id="app"> <v-app id="inspire"> <div> <v-btn color="success">Success</v-btn> <v-btn color="error">Error</v-btn> <v-btn color="warning">Warning</v-btn> <v-btn color="info">Info</v-btn> </div> </v-app> </div>
یکی از کامپوننتهایی که به تازگی به vuetify اضافه شده است، کامپوننت تقویم یا v-calendar است. از این کامپوننت برای نمایش تاریخ، روز، هفته، ماه و سال استفاده میشود. یک تقویم دارای یک نوع و یک مقدار است که تعیین میکند چه نوع تقویمی، در طول چه مدت زمانی نمایش داده شود.
حالتهای مختلفی برای نمایش تقویم در صفحه وجود دارد که برای مثال میتوان به موارد زیر اشاره کرد:
events : به وسیله این گزینه میتوان برای هر روز یک رخداد خاص را مشخص نمود که به وسیله کلیک بر روی آن، اطلاعات آن رخداد نمایش داده شود.
weekly : میتوان یک تقویم هفتگی را ایجاد نمود و رخدادهای هفتگی را برای آن تنظیم کرد.
نمونه ایجاد یک تقویم در پایین آمده است:
<div id="app"> <v-app id="inspire"> <v-layout wrap> <v-flex xs12> <v-sheet height="500"> <v-calendar ref="calendar" v-model="start" :type="type" :end="end" color="primary"> </v-calendar> </v-sheet> </v-flex> <v-flex sm4 xs12> <v-btn @click="$refs.calendar.prev()"> <v-icon dark left> keyboard_arrow_left </v-icon> Prev </v-btn> </v-flex> <v-flex sm4 xs12> <v-select v-model="type" :items="typeOptions" label="Type"> </v-select> </v-flex> <v-flex sm4 xs12> <v-btn @click="$refs.calendar.next()"> Next <v-icon right dark> keyboard_arrow_right </v-icon> </v-btn> </v-flex> </v-layout> </v-app> </div>
js قطعه کد new Vue({ el: '#app', data: () => ({ type: 'month', //مشخص کننده نوع تقویم که در اینجا تقویم به صورت ماهانه است start: '2019-01-01', end: '2019-01-06', typeOptions: [ { text: 'Day', value: 'day' }, { text: '4 Day', value: '4day' }, { text: 'Week', value: 'week' }, { text: 'Month', value: 'month' }, { text: 'Custom Daily', value: 'custom-daily' }, { text: 'Custom Weekly', value: 'custom-weekly' } ] }) })
بررسی سیستم جدید گرید بوت استرپ 3
چهار اندازه متفاوت سیستم گرید بوت استرپ 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
قلب سیستم تزریق وابستگیهای NET Core. اینترفیس IServiceProvider است
IServiceProvider که اساس IoC Container برنامههای مبتنی بر NET Core. را تشکیل میدهد، در اسمبلی System.ComponentModel و در فضای نام System تعریف شدهاست:
namespace System { public interface IServiceProvider { object GetService(Type serviceType); } }
استفادهی مستقیم از اینترفیس IServiceProvider برای دسترسی به وهلههای سرویسها، اصطلاحا الگوی Service Locator نامیده میشود و باید تا حد ممکن از آن پرهیز کرد؛ چون وابستگی مستقیمی از IoC Container را درون کدهای ما قرار میدهد و به این ترتیب یک مرحله، نوشتن آزمونهای واحد برای آنرا مشکلتر میکند؛ چون زمان وهله سازی از یک سرویس، دقیقا مشخص نیست به چه وابستگیهایی نیاز دارد. به همین جهت همیشه باید با روش تزریق وابستگیها در سازندهی کلاس شروع کرد و اگر به هر دلیلی این روش مهیا نبود و توسط سیستم تزریق وابستگیهای جاری شناسایی و یا پشتیبانی نمیشد (مانند تزریق وابستگی در سازندههای Attributes)، آنگاه میتوان به الگوی Service Locator مراجعه کرد.
برای مثال در اکثر قسمتهای برنامههای ASP.NET Core امکان تزریق وابستگیها در سازندهی کنترلرها، میان افزارها و سایر اجزای آن وجود دارد و در این حالات نیازی به مراجعهی مستقیم به IServiceProvider برای دریافت وهلههای سرویسهای مورد نیاز نیست. به عبارتی نگرانی در مورد IServiceProvider بهتر است مشکل IoC Container باشد و نه ما.
در مثال زیر، روش استفادهی از IServiceProvider را جهت انجام تزریق وابستگیها (یا به عبارتی بهتر، روش دسترسی به وهلههای وابستگیها) را مشاهده میکنید:
using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace CoreIocServices { public interface IProductService { void Delete(int id); } public class ProductService : IProductService { private readonly ITestService _testService; private readonly ILogger<ProductService> _logger; public ProductService(IServiceProvider serviceProvider) { _testService = serviceProvider.GetRequiredService<ITestService>(); _logger = serviceProvider.GetService<ILogger<ProductService>>() ?? NullLogger<ProductService>.Instance; } public void Delete(int id) { _testService.Run(); _logger.LogInformation($"Deleted a product with id = {id}"); } } }
- با نگاه کردن به امضای سازندهی این سرویس مشخص نیست که دقیقا از چه وابستگیهایی استفاده میکند. اینکار نوشتن آزمونهای واحد آنرا مشکل میکند.
- این سرویس یک وابستگی اضافهتر را به نام IServiceProvider، نیز پیدا کردهاست که اگر از روش متداول تزریق وابستگیها در سازندهی کلاس استفاده میشد، نیازی به ذکر آن نبود.
- پیاده سازی Dispose Pattern در این حالت مشکلتر است و در قسمتی دیگر بررسی خواهد شد.
تفاوتهای بین متدهای ()<GetService<T و ()<GetRequiredService<T
از آنجائیکه دیگر از NET 1.0. استفاده نمیکنیم، استفادهی از متد GetService با امضایی که در اینترفیس IServiceProvider تعریف شده و strongly typed نیست، بیشتر برای کارهای پویا مناسب است. به همین جهت دو نگارش جنریک از آن در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions با امضای زیر تعریف شدهاند که نمونهای از آنرا در قسمت قبل نیز استفاده کردیم و برای استفادهی از آنها ذکر فضای نام Microsoft.Extensions.DependencyInjection ضروری است:
namespace Microsoft.Extensions.DependencyInjection { public static class ServiceProviderServiceExtensions { public static T GetRequiredService<T>(this IServiceProvider provider); public static T GetService<T>(this IServiceProvider provider); } }
- متد GetService یک شیء سرویس از نوع T را بازگشت میدهد و یا نال؛ اگر سرویسی از نوع T، پیشتر به سیستم معرفی نشده باشد.
- متد GetRequiredService یک شیء سرویس از نوع T را بازگشت میدهد و یا اگر سرویسی از نوع T پیشتر به سیستم معرفی نشده باشد، استثنای InvalidOperationException را صادر میکند.
بنابراین تنها تفاوت این دو متد، در نحوهی رفتار آنها با درخواست وهلهای از یک سرویس پیشتر ثبت نشدهاست؛ یکی نال را باز میگرداند و دیگری یک استثناء را صادر میکند.
با توجه به این تفاوتها کدامیک از متدهای GetService و یا GetRequiredService را باید استفاده کرد؟
همانطور که پیشتر نیز در توضیحات الگوی Service locator عنوان شد، هیچکدام! ابتدا با تزریق وابستگیهای در سازندهی کلاس شروع کنید و اگر تامین این وابستگی، توسط IoC Container جاری پشتیبانی نمیشد، آنگاه نیاز به استفادهی از یکی از نگارشهای متد GetService خواهد بود و متد توصیه شده نیز GetRequiredService است و نه GetService؛ به این دلایل:
- حذف کدهای تکراری: اگر از GetService استفاده کنید، نیاز خواهید داشت پس از تمام فراخوانیهای آن، بررسی نال بودن آنرا نیز انجام دهید. برای حذف این نوع کدهای تکراری، بهتر است از همان متد GetRequiredService استفاده کنید که به صورت توکار این بررسی را نیز انجام میدهد.
- پشتیبانی از روش Fail Fast و یا همان Defensive programming: اگر بررسی نال بودن GetService را فراموش کنید، در سطرهای بعدی، یافتن علت NullReferenceException صادر شده مشکلتر از رسیدگی به InvalidOperationException صادر شدهی توسط GetRequiredService خواهد بود که توضیحات دقیقی را در مورد سرویس ثبت نشده ارائه میدهد.
- اگر بر روی IoC Container پیشفرض NET Core. یک IoC Container دیگر را مانند AutoFac قرار دادهاید، استفادهی از GetRequiredService، سبب میشود تا اینگونه IoC Containerهای ثالث بتوانند اطلاعات مفیدتری را از سرویسهای ثبت نشده ارائه دهند.
تنها حالتی که استفادهی از روش GetService را نیاز دارد، شرطی کردن ثبت و معرفی کردن سرویسها به IoC Container است؛ اگر سرویسی ثبت شده بود، آنگاه قطعه کدی اجرا شود.
به عبارت دیگر در طراحی ساخت یافته، کلاسهای سطح بالا، به کلاسهای سطح پایین وابستهاند. این مسئله دو مشکل را ایجاد میکند:
- هر تغییری در کلاسهای سطح پایین ممکن است باعث ایجاد اشکالی در کلاسهای سطح بالا گردد.
- استفادهی مجدد از کلاسهای سطح بالا در جاهای دیگر مشکل است؛ زیرا وابستگی مستقیمی به کلاسهای سطح پایین دارند.
یکی از اصول پنجگانهی طراحی برنامههای شیء گرا که با نام اصول SOLID شناخته میشوند، اصل «وارونگی وابستگیها» است که روشی برای مشکل جفت شدگی و وابستگی کلاسها به یکدیگر را به صورت تئوری ارائه میدهد.
- ماژولهای (کلاسهای) سطح بالا نباید به ماژولهای (کلاسهای) سطح پایین وابسته باشند و هر دو باید به انتزاعات وابسته باشند (برای مثال interfaceها).
- انتزاعات نباید وابسته به جزئیات باشند؛ بلکه جزئیات (پیاده سازی) باید وابسته به انتزاعات باشند.
شیء گرایی در واقع در مورد نحوهی جریان کنترل است. در اینجا اینترفیسها به ما کنترل کاملی را بر جریان کنترل (Flow of control) میدهند که با استفاده از این امکان میتوانیم از نوشتن کدهای جفت شده، شکننده و کلاسهایی یکبار مصرف، بپرهیزیم.
الگوی Dependency Injection
- کلاس کلاینت / Client Class : کلاس کلاینت (کلاس وابسته) کلاسی است که به کلاس سرویس وابسته است .
- کلاس سرویس / Service Class : کلاس سرویس (وابستگی) کلاسی است که یک سرویس را به کلاس کلاینت ارائه میدهد.
- کلاس تزریق کننده / Injector Class : کلاس تزریق کننده، نمونهای از کلاس سرویس را ساخته و به کلاس کلاینت، تزریق میکند.
شکل زیر وابستگی بین کلاسها را شرح میدهد:
همانطور که میبینید، کلاس Injector، نمونهای از کلاس سرویس را میسازد و آن را به نمونهای از کلاس Client تزریق میکند. با این کار، DI، وظیفهی ساخت یک نمونه از کلاس Service را از درون کلاس Client جدا میکند.
انواع تزریقات وابستگیها:
به صورت کلی به سه روش و در سه مکان، امکان تزریق وابستگی کلاس سرویس، درون کلاس کلاینت وجود دارد:
- تزریق درون سازنده / Constructor Injection : در تزریق درون سازنده، در سازندهی کلاس کلاینت، لیستی از سرویسهای مورد نیاز کلاس، که کلاس، برای عملکرد خود به آنها «وابسته» است، ثبت میشوند و کلاس Injector، سرویس (وابستگی) مورد نظر را درون سازندهی کلاس Client ارائه میدهد.
- تزریق درون Property کلاس / Property Injection : در این حالت که همچنین با نام (Setter Injection) هم شناخته میشود، تزریق کننده، وابستگی را به وسیلهی یک Property عمومی کلاس کلاینت ارائه میدهد.
- تزریق درون متد / Method Injection : در این حالت، خود کلاس کلاینت، یک پیاده سازی از یک interface را ارائه میکند که درون آن متدهایی برای ارائهی وابستگیها به کلاینت تعریف شدهاند. در این وضعیت، تزریق کننده از این اینترفیس برای ارائهی وابستگیها به کلاینت درون متدها، استفاده میکند.
Inversion of Control Container
- Dependency Injection
- Service Locator
به صورت کلی IoC Container ها سه وظیفهی اساسی را برعهده دارند:
- ثبت سرویس درون خود
- ساخت نمونههای مورد نظر از سرویسها و ارائه دادن آنها به کلاسهایی که نیاز دارند.
- از بین بردن نمونه سرویسهای ساخته شده (Dispose) کردن آنها .
PS /> $env:PSModulePath -Split ":" /Users/sirwanafifi/.local/share/powershell/Modules /usr/local/share/powershell/Modules /usr/local/microsoft/powershell/7/Modules
PS /> Import-Module ./PingModule.psm1
PS /> Get-Module PingModule ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 0.0 PingModule Get-PingReply
PS /> Import-Module ./PingModule.ps1 PS /> Get-Module PingModule ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 0.0 PingModule
Function Get-PingReply { // as before } Function Get-PrivateFunction { Write-Debug 'This is a private function' } Export-ModuleMember -Function @( 'Get-PingReply' )
$moduleSettings = @{ Path = './PingModule.psd1' Description = 'A module to ping a remote system' RootModule = 'PingModule.psm1' ModuleVersion = '1.0.0' FunctionsToExport = @( 'Get-PingReply' ) PowerShellVersion = '5.1' CompatiblePSEditions = @( 'Core' 'Desktop' ) } New-ModuleManifest @moduleSettings
$moduleSettings = @{ Author = 'John Doe' Description = 'This is a sample module' } New-ModuleManifest $moduleSettings
New-ModuleManifest : A parameter cannot be found that matches parameter name 'Author'.
# # Module manifest for module 'PingModule' # # Generated by: sirwanafifi # # Generated on: 01/01/2023 # @{ # Script module or binary module file associated with this manifest. RootModule = './PingModule.psm1' # Version number of this module. ModuleVersion = '1.0.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' # ID used to uniquely identify this module GUID = '3f8561fc-c004-4c8e-b2fc-4a4191504131' # Author of this module Author = 'sirwanafifi' # Company or vendor of this module CompanyName = 'Unknown' # Copyright statement for this module Copyright = '(c) sirwanafifi. All rights reserved.' # Description of the functionality provided by this module Description = 'A module to ping a remote system' # Minimum version of the PowerShell engine required by this module PowerShellVersion = '5.1' # Name of the PowerShell host required by this module # PowerShellHostName = '' # Minimum version of the PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # DotNetFrameworkVersion = '' # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # ClrVersion = '' # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module # RequiredModules = @() # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module # FormatsToProcess = @() # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = 'Get-PingReply' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = '*' # DSC resources to export from this module # DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. # Tags = @() # A URL to the license for this module. # LicenseUri = '' # A URL to the main website for this project. # ProjectUri = '' # A URL to an icon representing this module. # IconUri = '' # ReleaseNotes of this module # ReleaseNotes = '' # Prerelease string of this module # Prerelease = '' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false # External dependent modules of this module # ExternalModuleDependencies = @() } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' }
PS /> Test-ModuleManifest ./PingModule.psd1 ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Script 1.0.0 PingModule Get-PingReply
PS /> Register-PSRepository -Name 'PSLocal' ` >> -SourceLocation "$(Resolve-Path $RepoPath)" ` >> -PublishLocation "$(Resolve-Path $RepoPath)" ` >> -InstallationPolicy 'Trusted'
ProjectRoot | -- PingModule | -- PingModule.psd1 | -- PingModule.psm1
PS /> Publish-Module -Path ./PingModule/ -Repository PSLocal
PS /> Find-Module -Name PingModule -Repository PSLocal Version Name Repository Description ------- ---- ---------- ----------- 0.0.1 PingModule PSLocal Get-PingReply is a.
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
ProjectRoot | -- PingModule | -- 1.0.0 | -- PingModule.psd1 | -- PingModule.psm1 | -- 1.0.1 | -- PingModule.psd1 | -- PingModule.psm1
PS /> Publish-Module -Path ./PingModule/1.0.0 -Repository PSLocal PS /> Publish-Module -Path ./PingModule/1.0.1 -Repository PSLocal
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser PS /> Get-InstalledModule -Name PingModule Version Name Repository Description ------- ---- ---------- ----------- 1.0.1 PingModule PSLocal Get-PingReply is a.
ProjectRoot | -- PingModule | -- 1.0.0 | -- Public/ | -- Private/ | -- PingModule.psd1 | -- PingModule.psm1
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName } $ScriptList = Get-ChildItem -Path $PSScriptRoot/Private/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName }
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
ProjectRoot | -- SignPdf | -- 1.0.0 | -- Public/ | -- dependencies/ | -- BouncyCastle.Crypto.dll | -- System.Drawing.Common.dll | -- Microsoft.Win32.SystemEvents.dll | -- iTextSharp.LGPLv2.Core.dll | -- Set-PDFSingature.ps1 | -- SignPdf.psd1 | -- SignPdf.psm1
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1 foreach ($Script in $ScriptList) { . $Script.FullName } Export-ModuleMember -Function Set-PDFSingature
using namespace iTextSharp.text using namespace iTextSharp.text.pdf using namespace System.IO Function Set-PDFSingature { [CmdletBinding()] Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ if (Test-Path ([Path]::Join($(Get-Location), $_))) { return $true } else { throw "Signature image not found" } if ($_.EndsWith('.pdf')) { return $true } else { throw "File extension must be .pdf" } })] [string]$PdfToSign, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateScript({ if (Test-Path ([Path]::Join($(Get-Location), $_))) { return $true } else { throw "Signature image not found" } if ($_.EndsWith('.png') -or $_.EndsWith('.jpg')) { return $true } else { throw "File extension must be .png or .jpg" } })] [string]$SignatureImage, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [int]$XPos = 130, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [int]$YPos = 50 ) Try { Add-Type -Path "$PSScriptRoot/dependencies/*.dll" $pdf = [PdfReader]::new("$(Get-Location)/$PdfToSign") $fs = [FileStream]::new("$(Get-Location)/$PdfToSign-signed.pdf", [FileMode]::Create) $stamper = [PdfStamper]::new($pdf, $fs) $stamper.AcroFields.AddSubstitutionFont([BaseFont]::CreateFont()) $content = $stamper.GetOverContent(1) $width = $pdf.GetPageSize(1).Width $image = [Image]::GetInstance("$(Get-Location)/$SignatureImage") $image.SetAbsolutePosition($width - $XPos, $YPos) $image.ScaleAbsolute(100, 30) $content.AddImage($image) $stamper.Close() $pdf.Close() $fs.Dispose() } Catch { Write-Host "Error: $($_.Exception.Message)" } }
PS /> Publish-Module -Path ./SignPdf/1.0.0 -Repository PSLocal
PS /> Install-Module -Name SignPdf -Repository PSLocal -Scope CurrentUser
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
کدهای ماژول را میتوانید از اینجا دانلود کنید.