مطالب
اجزاء معماری سیستم عامل اندروید :: بخش دوم
در مطلب قبلی در مورد سه ویژگی اصلی معماری اندروید توضیحاتی ارائه شد و در این مطلب ویژگی آخر از این معماری را توضیح خواهم داد:

Applications در معماری اندروید چه کاربردی دارد؟
اجزای یک اپلیکیشن در پلتفرم اندروید جزء اصلی ارائه به کاربر نهایی می‌باشد؛ بدین معنا که کاربر تنها با برنامه در ارتباط است و سیستم عامل، میزبان آن برنامه یا اپلیکیشن خواهد بود. اپلیکیشن جاییست که لیست تماس‌ها، شماره تلفن‌ها، پیام‌های کاربر و ... در آنجا قرار می‌گیرند و با دیگر اجزای نرم افزاری در ارتباط هستند!
بعنوان یک توسعه دهنده اندروید، محصول نهایی، در قالب یک اپلیکیشن با استفاده از API‌ها و کتابخانه‌ها و همچنین ماشین مجازی دال‌ویک اجرا می‌شوند. اگر شما بتوانید تغییراتی را در سیستم عامل اصلاح و ویرایش کنید، تنها در سطوح لایه‌های نرم افزاری اعمال میشود و شما بر روی امنیت برنامه یا اپلیکیشن درون هسته دسترسی لازم را ندارید و این یک معضل است و باید در لایه‌های اولیه برنامه، امنیت را بر روی برنامه اعمال کنید.
با این وجود اگر هسته یا دستگاه آسیب ببیند و مورد سوءاستفاده قرار بگیرد کاری از دست شما برنمی آید!


Security به معنای ایمنی یا امنیت در اندروید به چه معناست؟
ایمنی یا امنیت در اندروید، موضوع بسیار وسیعی است که در ابعاد مختلف این پلتفرم قابل بحث است. اول اجازه دهید هویت شما را تشخیص دهیم. آیا شما یک توسعه دهنده هستید؟ یا شاید شما یک کاربر عادی هستید که به حفاظت از خودتان از یک حمله اینترنتی علاقمندید! در هر صورت، شما در حال نوشتن یک برنامه هستید که به وسیله یک نفر دیگر و یا احتمالا هزاران نفر در هزاران مایل دورتر نصب خواهد شد.


از کاربر خود در یک برنامه محافظت کنید!
برنامه شما باید تلاش کند تا بهترین عملکرد ممکن را در زمان ارائه محصول نهایی داشته باشد. از داده‌های کاربران خود محافظت کنید، یعنی قبل از اینکه شروع به توسعه کنید، درباره امنیت محصول خود فکر کنید. ممکن است کاربری که در هزاران مایل دورتر از شما قرار دارد در خصوص امنیتی که شما بر روی برنامه خود اعمال کرده‌اید اطلاعاتی نداشته باشد و شما شاید امنیت داده‌های او را نقض کنید که این معادل عدم اطمینان همان شخص نسبت به شما خواهد بود! برنامه‌ریزی درباره امنیت، پیش از توسعه یک محصول می‌تواند باعث شود که شما از بررسی‌های بد و از دست دادن ضررهای بعد از آن جلوگیری کنید. پس یک برنامه برای حفاظت از داده‌های درون اپلیکیشن خود ایجاد کنید! برنامه‌ای که هم کاربردی باشد، هم از اطلاعات کاربران محافظت نماید.


خطرات امنیتی (Security Risks)  
کاربران دستگاه‌های تلفن همراه در مقایسه با کاربران دسکتاپ، با برخی مخاطرات منحصر به فردی مواجه هستند که نباید نادیده گرفته شود. صرف‌نظر از امکان از دست دادن تجهیزات سخت افزاری، اطلاعات، حریم خصوصی و داده‌های محرمانه شخصی کاربران دستگاه تلفن همراه را به خطر می‌اندازند. چرا این موضوع در پلتفرم جامع اندروید تا این حد پر اهمیت است؟ آیا شما بعنوان توسعه دهنده به این نکات دقت داشته‌اید؟
اول اینکه، کیفیت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه کاربر بیشتر شخصی می‌شود تا مواردی دیگر! به غیر از ایمیل، پیام‌های فوری، SMS / MMS ، لاگ تماس‌ها، عکس‌ها و پست صوتی وجود دارند که عموما توسعه دهندگان را دچار مشکل می‌کند. 

برخی از گزینه‌های فوق بر روی یک کامپیوتر رومیزی هم وجود دارند، ولی اهمیت این داده‌ها بر روی اندروید و اجزای آن اهمیت فوق العاده‌ای دارد. اطلاعات روی دستگاه موبایل شما به احتمال زیاد از ارزش بیشتری برخوردار خواهد بود، چرا که آن‌ها را در یک صفحه 4 - 5 اینچی به همراه خود حمل می‌کنید و با خود هر کجا می‌برید! این حالت، یک پلتفرم همگرا را بوجود می‌آورد؛ به این دلیل که سیستم رومیزی شما و تلفن همراه یک مجموعه غنی و کامل از اطلاعات حساس هستند که هردوی آنها شامل اطلاعات شخصی می‌باشند و برای شما اهمیت زیادی خواهند داشت. تصور کنید زمانیکه برای جلوگیری از نفوذ یا به سرقت رفتن شماره تلفن‌های خود، یک پشتیبان بر روی سیستم رو میزی خود تهیه می‌کنید و فایل پشتیبان شماره‌های تماس را بر روی سیستم شخصی نگه داری می‌کنید! آیا این همان پلتفرم همگرا نیست؟ آیا این دو سیستم مکمل هم نیستند؟حتی اگر همگام‌سازی را با یک مکان دوردست (Google Drive) انجام دهید، با این حال شما فقط در مقابل از دست دادن داده‌ها محافظت کرده‌اید و نه از دست دادن حریم خصوصی! 

همچنین در نظر بگیرید که فرمت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه، تعیین و مشخص شوند! این کار اطلاعات حساس شما را به مرز سرقت نزدیکتر می‌کند. هر تلفن همراه SMS / MMS ، تماس‌ها، و پست صوتی خواهد داشت. مکان‌های ذخیره شده از روی GPS و مواردی دیگر که قطعا اطلاع دارید، تمامی اینها جزء مواردی هستند که خطرات امنیتی را در سیستم عامل اندروید شامل می‌شود. حالا در نظر بگیرید که این اطلاعات تا چه حد مهم است؟ برای کاربرانی که هیچ گونه پشتیبانی از اطلاعاتی از خود ندارند، از دست دادن داده‌ها قابل تصور نیست!

خطرناکترین نوع حملات بر روی پلتفرم اندروید انجام می‌شوند، در سکوت کامل و چندین هزار مایل دروتر از شما و فرد مهاجم نیازی به دسترسی فیزیکی و لمس تلفن همراه شما نخواهد داشت! این نوع حملات در هر زمانی ممکن است رخ دهد و اغلب می‌تواند به دلیل امنیت ضعیف در جای دیگری بر روی دستگاه رخ دهد.

در مطلب بعدی پیرامون امنیت معماری اندروید صبحت خواهیم کرد...

مطالب
inject$ در AngularJs
همان طور که در پست‌های قبلی ذکر شده بود در angular تزریق وابستگی به صورت پیش فرض وجود دارد. کافیست نام سرویس مورد نظر با نام‌های پیش فرض تعبیه شده در  angular یا با نام سرویس‌های ساخته شده توسط خودتان مطابقت داشته باشد. به عنوان مثال برای تزریق سرویس scope$ در توابع سازنده کنترلر کافیست یک پارامتر به همین نام را به عنوان آرگومان در این توابع در نظر بگیرید. همچنین برای استفاده از سرویس http$ باید یک پارامتر دیگر به همین نام در این توابع در نظر داشته باشید و همچنین برای location$. در مورد سرویس‌های ساخته شده توسط خودتان نیز باید همین قانون را پیاده کنید. در این پست قصد دارم از injector تعبیه شده در angular برای تغییر رفتار فریم ورک در هنگام شناسایی پارامتر‌های توابع استفاده کنم.
ابتدا مثال زیر را به روش‌های قبلی پیاده سازی می‌کنیم:
var app = angular.module('myApp', []);

app.factory('bookService', function () {
    var books = [
        { name: 'A' },
        { name: 'B' },
        { name: 'C' }
    ];
    return books;
});
app.controller('bookCtrl', function ($scope, bookService) {
    $scope.books = bookService;
})
view مورد نظر نیز به صورت زیر خواهد بود:
<script type="text/javascript" src="~/scripts/Modules/module5.js"></script>

<div ng-app="myApp">
    <div ng-controller="bookCtrl">
        <table>
            <tr ng-repeat="book in books">
                <td>
                    {{book.name}}
                </td>
            </tr>
        </table>
    </div>
</div>
نیاز به توضیح نیست که در هنگام تعریف تابع سازنده کنترلر bookCtrl باید نام پارارمتر‌های وروردی تابع در هنگام تزریق وابستگی دقیقا مانند مثال بالا باشد. (بعنی scope$ برای دسترسی به سرویس scope و bookService برای دسترسی به سرویس ساخته شده توسط factory - ترتیب پارامترها در اینجا اهمیتی ندارد ). حال مثال بالا را با استفاده از injector موجود در angular برای تزریق وابستگی‌ها پیاده سازی می‌کنم. ابتدا تابع کنترلر bookCtrl را به صورت زیر ایجاد می‌کنیم:
var bookCtrl = function (sc,bs) {
    sc.books = bs;
};
از پارامتر sc به جای scope$ و از bs به عنوان bookService در این تابع استفاده شده است. سپس کنترلر موجود را به ماژول مورد نظر نسبت می‌دهیم:
app.controller('bookCtrl',bookCtrl);
اگر برنامه را به همین صورت اجرا کنید خروجی مورد نظر حاصل نخواهد شد. زیرا آرگومان‌های sc و bs برای angular تعریف نشده است. کافیست وابستگی‌های تابع کنترلر را به صورت زیر برای angular مشخص نماییم:
bookCtrl.$inject = ['$scope','bookService'];
در نتیجه تعریف کنترلر بالا به صورت کامل زیر خواهد بود:
var app = angular.module('myApp', []);

app.factory('bookService', function () {
    var books = [
        { name: 'A' },
        { name: 'B' },
        { name: 'C' }
    ];
    return books;
});

var bookCtrl = function (sc,bs) {
    sc.books = bs;
};

bookCtrl.$inject = ['$scope','bookService'];

app.controller('bookCtrl',bookCtrl);

از این پس در هنگام فراخوانی تابع کنترلر bookCtrl سرویس‌های scope$ و bookService به ترتیب به عنوان آرگومان‌های اول و دوم در اختیار کنترلر قرار می‌گیرند. می‌توان به جای فراخوانی مستقیم inject$، تزریق وابستگی‌ها را در هنگام تعریف توابع سازنده به صورت زیر نیز فراهم ساخت:
app.controller('bookCtrl', ['$scope', 'bookService', function (sc, bs) {
    sc.books = bs;
}])
 
مطالب
آموزش فایرباگ - #5 - HTML Panel
در این سری از مقالات آموزش FireBug ، به صورت ترتیبی پیش رفتیم و ابتدا توضیحات تقریبا مفصلی در مورد پنل Console دادیم.
اکنون به پرکاربردترین بخش آن ، یعنی پنل HTML می‌رسیم.

محتویاتی که در این پنل نمایش داده می‌شود ، کدهای صفحه‌ی جاری ، بصورت زنده است و با چیزی که از سمت سرور به مرورگر ارسال می‌شود متفاوت است ، تگ‌های HTML بصورت درختی مرتب شده اند و رنگی نمایش داده می‌شوند و امکان ویرایش محتوا ، ویرایش استایل ، مشاهده‌ی آرایش تگ‌ها بصورت بصری و ... وجود دارد.

نگاهی به کدهای ارسال شده به مرورگر در آدرس Google.com و نحوه‌ی نمایش آن در فایرباگ را ملاحظه بفرمایید.
( کدهای ارسال شده سمت چپ - نمایش در فایرباگ سمت راست )


این پنل به سه بخش اصلی تقسیم می‌شود :
  • بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش می‌دهد.
  •  Panel Toolbar که در بالای پنل قرار دارد.
  •  Side Panels که شامل پنل‌های Style , Computed , Layout , DOM می‌شود.
    که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.

در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقاله‌ی بعد با پنل‌های سمت راست یعنی Side Panels آشنا می‌شویم.

ویرایش HTML

هر تگ HTML از یک سری Attribute تشکیل می‌شود که در فایرباگ نام ویژگی بصورت آبی تیره و مقدار آن با رنگ قرمز مشخص شده است. برای ویرایش هریک از آن‌ها کافیست برویش کلیک کنید تا آن مقدار در یک باکس ویرایش به نمایش دربیاید.
با ویرایش مقدار ، این تغییر در لحظه بروی صفحه اعمال می‌شود.


برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک می‌کنید و سپس گزینه‌ی New Attribute را انتخاب می‌کنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد می‌کنید. با زدن کلیدهای Enter متوالی ، می‌توانید به وارد کردن ویژگی‌ها ادامه دهید.
برای ویرایش کردن یک تگ و تگ‌های فرزندش ، بروی تگ موردنظر کلیک کنید تا به حالت انتخاب دربیاید ، سپس بروی دکمه‌ی Edit در بالای پنل کلیک کنید. در نهایت بعد از انجام ویرایش ، مجددا بروی دکمه‌ی Edit کلیک کنید.


 Node View
NodeView نام بخش اصلی پنل HTML است که محتویات صفحه را بصورت مرتب شده و درختی نمایش می‌دهد.
تگ (Node) هایی که دارای فرزند می‌باشند ، یک علامت + یا - در کنارشان وجود دارد که با کلیک بروی آن می‌توانید آن تگ را باز/بسته کنید. همچنین این کار با کلید‌های + و - یا Right Arrow و Left Arrow از روی کیبورد هم قابل انجام است.
برای باز کردن یک لینک در این قسمت هم از ترکیب Ctrl و کلیک موس کمک بگیرید.
در نهایت زمانی که موس را بروی یک تگ قرار می‌دهید ، محیطی که توسط آن تگ در صفحه اشغال شده است بصورت رنگی مشخص می‌شود.
که رنگ زرد به معنی محیط margin ، رنگ آبی تیره به معنی محیط padding و رنگ آبی روشن هم به معنی محیط محتوا است.

 Panel Toolbar
این قسمت در بالای پنل HTML قرار دارد که گزینه‌های زیر را دارا می‌باشد:



  •  Break On Mutate
    این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، می‌دهد.
    زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل می‌کند.

  • Edit
    این دکمه برای ویرایش مستقیم محتوای یک تگ بکار می‌رود
    نکته‌ی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال می‌شوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال می‌شوند.

  •  Element Path
    زمانی که یک تگ را در FireBug انتخاب می‌کنید ، لیستی از تگ‌ها که از تگ جاری شروع و به تگ ریشه ختم می‌شود ، نمایش داده می‌شود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین می‌شود.
    نتیجه‌ی عملیاتی که بروی این تگ‌های انجام می‌دهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگ‌های نمایش داده شده در قسمت اصلی (NodeView) است.



Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب HTML هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Show Full Text
    در صورت فعال بودن ، متون بصورت کامل نمایش داده می‌شوند ، در غیراینصورت بصورت خلاصه شده نمایش داده می‌شوند.

  • Show White Space
    در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش می‌دهد.


  • Show Comments
    در صورت فعال بودن ، کامنت‌ها را هم نمایش می‌دهد


  • سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین می‌کنند.

  • Highlight Changes
    در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight می‌شود

  • Expand Changes
    در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) می‌شوند

  • Scroll Changes Into View
    در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت می‌کند.
    (فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمت‌های سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل می‌شود.)

  • Shade Box Model
    در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش می‌دهد ، در غیر اینصورت فقط یک خط دور تگ نشان می‌دهد.

  • Show Quick Info Box
    در صورت فعال بودن ، یک پاپ‌آپ به همراه اطلاعات مختصری از تگ در صفحه نمایش می‌دهد.




Context Menu
اگر بروی یک تگ راست کلیک کنید ، یک منو نمایش داده می‌شود ، در این قسمت با گزینه‌های این منو آشنا می‌شویم.

  • Copy HTML
    خود تگ و محتوایش را بصورت HTML در حافظه کپی می‌کند.

  • Copy innerHTML
    محتوای تگ را در حافظه کپی می‌کند.

  • Copy XPath
    آدرس XPath تگ را در حافظه کپی می‌کند.

  • Copy CSS Path
    CSS Selector تگ را در حافظه کپی می‌کند.

  • Log Events
    رویدادهای تگ را در پنل Console ثبت می‌کند. (کلیک موس ، فشردن کلید ، ...)
    برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.

  • Scroll Into View
    صفحه را به جایی که تگ قابل نمایش است منتقل می‌کند.

  • New Attribute...
    یک attribute جدید به تگ اضافه می‌کند.
    برای لغو عملیات ، دکمه‌ی Esc را بزنید.

  • Edit Attribute "<attribute name>"...
    اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینه‌ی بعدی را مشاهده خواهید کرد.
    معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.

  • Delete Attribute "<attribute name>"
    attribute را حذف می‌کند.

  • Edit HTML...
    تگ را به حالت ویرایش می‌برد.
    معادل انتخاب تگ ، زدن کلید Edit است.

  • Delete Element
    تگ را حذف می‌کند.
    راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.

  • Expand/Contract All
    تگ و Childهایش را باز/بسته می‌کند. (بجز تگ های <script> , <style> , <link>)
    می‌توان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگ‌های فوق هم شامل باز/بسته شدن می‌شوند.

  • Break On Attribute Change
    مانع اجرای کد JavaScript ای که attribute تگ را تغییر می‌دهد می‌شود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل می‌شود.
    به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute می‌شود قرار می‌دهد.

  • Break On Child Addition or Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار می‌دهد.

  • Break On Element Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار می‌دهد.

  • Inspect in DOM Tab
    تگ فعلی را در پنل DOM ، برای بررسی باز می‌کند.


در قسمت بعدی با پنل‌های سمت راست (Side Panels) آشنا می‌شویم.
مطالب
آشنایی با تست واحد و استفاده از کتابخانه Moq
تست واحد چیست؟

تست واحد ابزاری است برای مشاهده چگونگی عملکرد یک متد که توسط خود برنامه نویس نوشته میشود. به این صورت که پارامتر‌های ورودی، برای یک متد ساخته شده و آن متد فراخوانی و خروجی متد بسته به حالت مطلوب بررسی میشود. چنانچه خروجی مورد نظر مطلوب باشد تست واحد با موفقیت انجام میشود.


اهمیت انجام تست واحد چیست؟

درستی یک متد، مهمترین مسئله برای بررسی است و بارها مشاهده شده، استثناهایی رخ میدهند که توان تولید را به دلیل فرسایش تکراری رخداد میکاهند. نوشتن تست واحد منجر به این می‌شود چناچه بعدها تغییری در بیزنس متد ایجاد شود و ورودی و خروجی‌ها تغییر نکند، صحت این تغییر بیزنس، توسط تست بررسی مشود؛ حتی میتوان این تست‌ها را در build پروژه قرار داد و در ابتدای اجرای یک Solution تمامی تست‌ها اجرا و درستی بخش به بخش اعضا چک شوند.


شروع تست واحد:

یک پروژه‌ی ساده را داریم برای تعریف حساب‌های بانکی شامل نام مشتری، مبلغ سپرده، وضعیت و 3 متد واریز به حساب و برداشت از حساب و تغییر وضعیت حساب که به صورت زیر است:
    /// <summary>
    /// حساب بانکی
    /// </summary>
    public class Account
    {
        /// <summary>
        /// مشتری
        /// </summary>
        public string Customer { get; set; }
        /// <summary>
        /// موجودی حساب
        /// </summary>
        public float Balance { get; set; }
        /// <summary>
        /// وضعیت
        /// </summary>
        public bool Active { get; set; }

        public Account(string customer, float balance)
        {
            Customer = customer;
            Balance = balance;
            Active = true;
        }
        /// <summary>
        /// افزایش موجودی / واریز به حساب
        /// </summary>
        /// <param name="amount">مبلغ واریز</param>
        public void Credit(float amount)
        {
            if (!Active)
                throw new Exception("این حساب مسدود است.");
            if (amount < 0)
                throw new ArgumentOutOfRangeException("amount");
            Balance += amount;
        }
        /// <summary>
        /// کاهش موجودی / برداشت از حساب
        /// </summary>
        /// <param name="amount">مبلغ برداشت</param>
        public void Debit(float amount)
        {
            if (!Active)
                throw new Exception("این حساب مسدود است.");
            if (amount < 0)
                throw new ArgumentOutOfRangeException("amount");
            if (Balance < amount)
                throw new ArgumentOutOfRangeException("amount");
            Balance -= amount;
        }
        /// <summary>
        /// انسداد / رفع انسداد
        /// </summary>
        public void ChangeStateAccount()
        {
            Active = !Active;
        }
    }
تابع اصلی نیز به صورت زیر است:
    class Program
    {
        static void Main(string[] args)
        {
            var account = new Account("Ali",1000);

            account.Credit(4000);
            account.Debit(2000);
            Console.WriteLine("Current balance is ${0}", account.Balance);
            Console.ReadKey();
        }
    }
به Solution، یک پروژه از نوع تست واحد اضافه میکنیم.
در این پروژه ابتدا Reference ایی از پروژه‌ای که مورد تست هست میگیریم. سپس در کلاس تست مربوطه شروع به نوشتن متدی برای انواع تست متدهای پروژه اصلی میکنیم.
توجه داشته باشید که Data Annotation‌های بالای کلاس تست و متدهای تست، در تعیین نوع نگاه کامپایلر به این بلوک‌ها موثر است و باید این مسئله به درستی رعایت شود. همچنین در صورت نیاز میتوان از کلاس StartUp برای شروع تست استفاده کرد که عمدتا برای تعریف آن از نام ClassInit استفاده میشود و در بالای آن از [ClassInitialize] استفاده میشود.
در Library تست واحد میتوان به دو صورت چگونگی صحت عملکرد یک تست را بررسی کرد: با استفاده از Assert و با استفاده از ExpectedException، که در زیر به هر دو صورت آن میپردازیم.
    [TestClass]
    public class UnitTest
    {
        /// <summary>
        /// تعریف حساب جدید و بررسی تمامی فرآیند‌های معمول روی حساب
        /// </summary>
        [TestMethod]
        public void Create_New_Account_And_Check_The_Process()
        {
            //Arrange
            var account = new Account("Hassan", 4000);
            var account2 = new Account("Ali", 10000);
            //Act
            account.Credit(5000);
            account2.Debit(3000);
            account.ChangeStateAccount();
            account2.Active = false;
            account2.ChangeStateAccount();
            //Assert
            Assert.AreEqual(account.Balance,9000);
            Assert.AreEqual(account2.Balance,7000);
            Assert.IsTrue(account2.Active);
            Assert.AreEqual(account.Active,false);
        }
همانطور که مشاهده میشود ابتدا در قسمت Arrange، خوراک تست آماده میشود. سپس در قسمت Act، فعالیت‌هایی که زیر ذره بین تست هستند صورت می‌پذیرند و سپس در قسمت Assert درستی مقادیر با مقادیر مورد انتظار ما مطابقت داده میشوند.
برای بررسی خطاهای تعیین شده هنگام نوشتن یک متد نیز میتوان به صورت زیر عمل کرد:
        /// <summary>
        /// زمانی که کاربر بخواهد به یک حساب مسدود واریز کند باید جلوی آن گرفته شود.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof (Exception))]
        public void When_Deactive_Account_Wants_To_add_Credit_Should_Throw_Exception()
        {
            //Arrange
            var account = new Account("Hassan", 4000) {Active = false};
            //Act
            account.Credit(4000);
            //Assert
            //Assert is handled with ExpectedException
        }

        [TestMethod]
        [ExpectedException(typeof (ArgumentOutOfRangeException))]
        public void When_Customer_Wants_To_Debit_More_Than_Balance_Should_Throw_ArgumentOutOfRangeException()
        {
            //Arrange
            var account = new Account("Hassan", 4000);
            //Act
            account.Debit(5000);
            //Assert
            //Assert is handled with ArgumentOutOfRangeException
        }
همانطور که مشخص است نام متد تست باید کامل و شفاف به صورتی انتخاب شود که بیانگر رخداد درون متد تست باشد. در این متدها Assert مورد انتظار با DataAnnotation که پیش از این توضیح داده شد کنترل گردیده است و بدین صورت کار میکند که وقتی Act انجام میشود، متد بررسی می‌کند تا آن Assert رخ بدهد.


استفاده از Library Moq در تست واحد

ابتدا باید به این توضیح بپردازیم که این کتابخانه چه کاری میکند و چه امکانی را برای انجام تست واحد فراهم میکند.
در پروژه‌های بزرگ و زمانی که ارتباطات بین لایه‌ای زیادی موجود است و اصول SOLID رعایت میشود، شما در یک لایه برای ارایه فعالیت‌ها و خدمات متدهایتان با Interface‌های لایه‌های دیگر در ارتباط هستید و برای نوشتن تست واحد متدهایتان، مشکلی بزرگ دارید که نمیتوانید به این لایه‌ها دسترسی داشته باشید و ماهیت تست واحد را زیر سوال میبرید. Library Moq این امکان را به شما میدهد که از این Interface‌ها یک تصویر مجازی بسازید و همانند Snap Shot با آن کار کنید؛ بدون اینکه در لایه‌های دیگر بروید و ماهیت تست واحد را زیر سوال ببرید.
برای استفاده از متدهایی که در این Interface‌ها موجود است شما باید یک شیء از نوع Mock<> از آنها بسازید و سپس با استفاده از متد Setup به صورت مجازی متد مورد نظر را فراخوانی کنید و مقدار بازگشتی مورد انتظار را با Return معرفی کنید، سپس از آن استفاده کنید.
همچنین برای دسترسی به خود شیء از Property ایی با نام Objet از موجودیت mock شده استفاده میکنیم.
برای شناسایی بهتر اینکه از چه اینترفیس هایی باید Mock<> بسازید، میتوانید به متد سازنده کلاسی که معرف لایه ایست که برای آن تست واحد مینویسید، مراجعه کنید.
نحوه اجرای یک تست واحد با استفاده از Moq با توجه به توضیحات بالا به صورت زیر است:
پروژه مورد بررسی لایه Service برای تعریف واحد‌های سازمانی است که با الگوریتم DDD و CQRS پیاده سازی شده است.
ابتدا به Constructor خود لایه سرویس نگاه میکنیم تا بتوانید شناسایی کنید از چه Interface هایی باید Mock<> کنیم.
  public class OrganizationalService : ICommandHandler<CreateUnitTypeCommand>,
                                         ICommandHandler<DeleteUnitTypeCommand>,                                    
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IUnitTypeRepository _unitTypeRepository;
        private readonly IOrganizationUnitRepository _organizationUnitRepository;
        private readonly IOrganizationUnitDomainService _organizationUnitDomainService;

        public OrganizationalService(IUnitOfWork unitOfWork, IUnitTypeRepository unitTypeRepository, IOrganizationUnitRepository organizationUnitRepository, IOrganizationUnitDomainService organizationUnitDomainService)
        {
            _unitOfWork = unitOfWork;
            _unitTypeRepository = unitTypeRepository;
            _organizationUnitRepository = organizationUnitRepository;
            _organizationUnitDomainService = organizationUnitDomainService;
        }
مشاهده میکنید که 4 Interface استفاده شده و در متد سازنده نیز مقدار دهی شده اند. پس 4 Mock نیاز داریم. در پروژه تست به صورت زیر و در ClassInitialize عمل میکنیم.
    [TestClass]
    public class OrganizationServiceTest
    {
        private static OrganizationalService _organizationalService;
        private static Mock<IUnitTypeRepository> _mockUnitTypeRepository;
        private static Mock<IUnitOfWork> _mockUnitOfWork;
        private static Mock<IOrganizationUnitRepository> _mockOrganizationUnitRepository;
        private static Mock<IOrganizationUnitDomainService> _mockOrganizationUnitDomainService;

        [ClassInitialize]
        public static void ClassInit(TestContext context)
        {
            TestBootstrapper.ConfigureDependencies();
            _mockUnitOfWork = new Mock<IUnitOfWork>();
            _mockUnitTypeRepository = new Mock<IUnitTypeRepository>();
            _mockOrganizationUnitRepository = new Mock<IOrganizationUnitRepository>();
            _mockOrganizationUnitDomainService=new Mock<IOrganizationUnitDomainService>();
            _organizationalService = new OrganizationalService(_mockUnitOfWork.Object, _mockUnitTypeRepository.Object,  _mockOrganizationUnitRepository.Object,_mockOrganizationUnitDomainService.Object);
        }
از خود لایه سرویس با نام OrganizationService یک آبجکت میگیریم و 4 واسط دیگر به صورت Mock شده تعریف میشوند. همچنین در کلاس بارگذار از همان نوع مقدار دهی میگردند تا در اجرای تمامی متدهای تست، در دست کامپایلر باشند. همچنین برای new کردن خود سرویس از mock.obect‌ها که حاوی مقدار اصلی است استفاده می‌کنیم.
خود متد اصلی به صورت زیر است:
        /// <summary>
        /// یک نوع واحد سازمانی را حذف مینماید
        /// </summary>
        /// <param name="command"></param>
        public void Handle(DeleteUnitTypeCommand command)
        {
            var unitType = _unitTypeRepository.FindBy(command.UnitTypeId);
            if (unitType == null)
                throw new DeleteEntityNotFoundException();

            ICanDeleteUnitTypeSpecification canDeleteUnitType = new CanDeleteUnitTypeSpecification(_organizationUnitRepository);
            if (canDeleteUnitType.IsSatisfiedBy(unitType))
                throw new UnitTypeIsUnderUsingException(unitType.Title);
            _unitTypeRepository.Remove(unitType);
        }
متد‌های تست این متد نیز به صورت زیر هستند:
        /// <summary>
        /// کامند حذف نوع واحد سازمانی باید به درستی حذف کند.
        /// </summary>
        [TestMethod]
        public void DeleteUnitTypeCommand_Should_Delete_UnitType()
        {
            //Arrange
            var unitTypeId=new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId };
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>();
            _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);
            try
            {
                //Act
                _organizationalService.Handle(deleteUnitTypeCommand);
            }
            catch (Exception ex)
            {
                //Assert
                Assert.Fail(ex.Message);
            }
        }
همانطور که مشاهده میشود ابتدا یک Guid به عنوان آی دی نوع واحد سازمانی گرفته میشود و همان آی دی برای تعریف کامند حذف به آن ارسال میشود. سپس یک نوع واحد سازمانی دلخواه تستی ساخته میشود و همچنین یک لیست خالی از واحد‌های سازمانی که برای چک شدن توسط خود متد Handle استفاده شده‌است ساخته میشود. در اینجا این متد خالی است تا شرط غلط شود و عمل حذف به درستی صورت پذیرد.
برای اعمالی که در Handle انجام میشود و متدهایی که از Interface‌ها صدا زده میشوند Setup میکنیم و آنهایی را که Return دارند به object هایی که مورد انتظار خودمان هست نسبت میدهیم.
در Setup اول میگوییم که آن Guid مربوط به "خوشه" است. در Setup بعدی برای عمل Remove کدی مینویسیم و چون عمل حذف Return ندارد میتواند، این خط به کل حذف شود! به طور کلی Setup هایی که Return ندارند میتوانند حذف شوند.
در Setup بعدی از Interface دیگر متد FindBy که قرار است چک کند این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است، در Return به آن یک لیست خالی اختصاص میدهیم تا نشان دهیم لیست خالی برگشته است.
عملیات Act را وارد Try میکنیم تا اگر به هر دلیل انجام نشد، Assert ما باشد.
دو حالت رخداد استثناء که در متد اصلی تست شده است در دو متد تست به طور جداگانه تست گردیده است:
        /// <summary>
        /// کامند حذف یک نوع واحد سازمانی باید پیش از حذف بررسی کند که این شناسه داده شده برای حذف موجود باشد.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DeleteEntityNotFoundException))]
        public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist()
        {
            //Arrange
            var unitTypeId = new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand();
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>();
            _mockUnitTypeRepository.Setup(d => d.FindBy(unitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);

            //Act
            _organizationalService.Handle(deleteUnitTypeCommand);
        }

        /// <summary>
        /// کامند حذف یک نوع واحد سازمانی نباید اجرا شود وقتی که نوع واحد برای تعریف واحد‌های سازمان استفاده شده است.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(UnitTypeIsUnderUsingException))]
        public void DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit()
        {
            //Arrange
            var unitTypeId = new Guid();
            var deleteUnitTypeCommand = new DeleteUnitTypeCommand { UnitTypeId = unitTypeId };
            var unitType = new UnitType("خوشه");
            var org = new List<OrganizationUnit>()
            {
                new OrganizationUnit("مدیریت یک", unitType, null),
                new OrganizationUnit("مدیریت دو", unitType, null)
            };
            _mockUnitTypeRepository.Setup(d => d.FindBy(deleteUnitTypeCommand.UnitTypeId)).Returns(unitType);
            _mockUnitTypeRepository.Setup(x => x.Remove(unitType));
            _mockOrganizationUnitRepository.Setup(z => z.FindBy(unitType)).Returns(org);

            //Act
            _organizationalService.Handle(deleteUnitTypeCommand);
        }
متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitTypeId_NotExist همانطور که از نامش معلوم است بررسی میکند که نوع واحد سازمانی که ID آن برای حذف ارسال میشود در Database وجود دارد و اگر نباشد Exception مطلوب ما باید داده شود.
در متد DeleteUnitTypeCommand_ShouldNot_Delete_When_UnitType_Exist_but_UsedForDefineOrganizationUnit بررسی میشود که از این نوع واحد سازمانی برای تعریف واحد سازمانی استفاده شده است یا نه و صحت این مورد با الگوی Specification صورت گرفته است. استثنای مطلوب ما Assert و شرط درستی این متد تست، میباشد.
مطالب
PowerShell 7.x - قسمت پنجم - اسکریپت بلاک و توابع
همانطور که در قسمت قبل اشاره شد، توابع نیز یکی از ویژگی‌های اصلی PowerShell هستند. قبل از بررسی بیشتر توابع بهتر است ابتدا با مفهوم script block آشنا شویم. script blocks به مجموعه‌ایی از دستورات گفته میشود که داخل یک بلاک قرار میگیرند. در واقع هر چیزی داخل {} یک script block محسوب میشود (البته به جز hash tables). به عنوان مثال در کد زیر از یک script block مخصوص، با نام فیلتر استفاده شده است که یک ورودی برای پارامتر FilterScript مربوط به دستور Where-Object میباشد. چیزی که این script block را متمایز میکند، خروجی آن است. به این معنا که خروجی آن باید یک مقدار بولین باشد: 
Get-Process | Where-Object { $_.Name -eq 'Dropbox' }
script blocks را به صورت مستقیم درون command line هم میتوانیم استفاده کنیم. به محض تایپ کردن } و زدن کلید enter، امکان نوشتن اسکریپت‌های چندخطی را درون ترمینال خواهیم داشت. در نهایت با بستن script block و زدن کلید enter، از بلاک خارج خواهیم شد: 
PS /Users/sirwanafifi/Desktop> $block = {
>> $newVar = 10
>> Write-Host $newVar
>> }
با اینکار یک بلاک از کد را داخل متغیری با اسم block ذخیره کرده‌ایم. برای فراخوانی این قطعه کد میتوانیم از یک عملگر مخصوص با نام invocation operator یا call operator استفاده کنیم: 
PS /Users/sirwanafifi/Desktop> & $block
یا حتی میتوانیم از Invoke-Command نیز برای اجرای بلاک استفاده کنیم. همچنین از عملگر & برای فراخوانی یک expression رشته‌ایی نیز میتوان استفاده کرد: 
PS /Users/sirwanafifi/Desktop> & "Get-Process"
البته این نکته را در نظر داشته باشید که & قادر به پارز کردن (parse) یک expression نیست. به عنوان مثال اجرای کد زیر با خطا مواجه خواهد شد (برای حل این مشکل میتوانید بجای آن از Invoke-Expression استفاده کنید که امکان پارز کردن پارامترها را نیز دارد):
PS /Users/sirwanafifi/Desktop> & "1 + 1"
or
PS /Users/sirwanafifi/Desktop> & "Get-Process -Name Slack"

توابع
در قسمت قبل با نحوه ایجاد توابع آشنا شدیم. به این نوع توابع، basic functions گفته میشود و ساده‌ترین نوع توابع در PowerShell هستند. همچنین خیلی محدود نیز میباشند؛ یکسری ورودی/خروجی دارند. برای کنترل بیشتر روی نحوه فراخوانی توابع (به عنوان مثال دریافت ورودی از pipeline و…) باید از advanced functions یا توابع پیشرفته استفاده کنیم. در واقع به محض استفاده از اتریبیوتی با نام [()CmdletBinding] تابع ما تبدیل به یک advanced function خواهد شد. منظور از دریافت ورودی از pipeline این است که بتوانیم خروجی دستورات را به تابع‌مان pipe کنیم اینکار در basic function امکانپذیر نیست: 
Function Add-Something {
    Write-Host "$_ World"
}

"Hello" | Add-Something
اما با کمک advanced functions میتوانیم چنین قابلیتی را داشته باشیم: 
Function Add-Something {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Name
    )

    Write-Host "$Name World"
}

"Hello" | Add-Something
یکی دیگر از ویژگی‌های advanced functions امکان استفاده فلگ Verbose حین فراخوانی دستورات میباشد. به عنوان مثال قطعه کد زیر را در نظر بگیرید: 
$API_KEY = "...."

Function Read-WeatherData {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$CityName
    )

    $Url = "https://api.openweathermap.org/data/2.5/forecast?q=$CityName&cnt=40&appid=$API_KEY&units=metric"
    Try {
        Write-Verbose "Reading weather data for $CityName"
        $Response = Invoke-RestMethod -Uri $Url
        $Response.list | ForEach-Object {
            Write-Verbose "Processing $($_.dt_txt)"
            [PSCustomObject]@{
                City               = $Response.city.name
                DateTime           = [DateTime]::Parse($_.dt_txt)
                Temperature        = $_.main.temp
                Humidity           = $_.main.humidity
                Pressure           = $_.main.pressure
                WindSpeed          = $_.wind.speed
                WindDirection      = $_.wind.deg
                Cloudiness         = $_.clouds.all
                Weather            = $_.weather.main
                WeatherDescription = $_.weather.description
            }
        } | Where-Object { $_.DateTime.Date -eq (Get-Date).Date }
        Write-Verbose "Done processing $CityName"
    }
    Catch {
        Write-Error $_.Exception.Message
    }
}
کاری که تابع فوق انجام میدهد، دریافت دیتای پیش‌بینی وضعیت آب‌وهوای یک شهر است. در حالت عادی فراخوانی تابع فوق پیام‌های Verbose را نمایش نمیدهد. از آنجائیکه تابع فوق یک advanced function است، میتوانیم فلگ Verbose را نیز وارد کنیم. با اینکار به صورت صریح گفته‌ایم که پیام‌های از نوع Verbose را نیز نمایش دهد: 
Read-WeatherData -CityName "London" -Verbose
هر چند این مقدار را همانطور که در قسمت‌های قبلی عنوان شد میتوانیم تغییر دهیم که دیگر مجبور نباشیم با فراخوانی هر تابع، این فلگ را نیز ارسال کنیم. بیشتر دستورات native نیز قابلیت نمایش پیام‌های Verbose را با ارسال همین فلگ در اختیارمان قرار میدهند. بنابراین بهتر است برای امکان مشاهده جزئیات بیشتر حین فراخوانی توابع‌مان از Write-Verbose استفاده کنیم. در ادامه اجزای دیگر توابع را بررسی خواهیم کرد (بیشتر این اجزا درون یک script block نیز قابل استفاده هستند)

کنترل کامل بر روی ورودی‌های توابع
بر روی ورودی‌های یک تابع میتوانیم کنترل نسبتاً کاملی داشتیم باشیم. PowerShell یک مجموعه وسیع از قابلیت‌ها را برای هندل کردن پارامترها و همچنین اعتبارسنجی ورودی‌ها ارائه میدهد. به عنوان مثال میتوانیم یک پارامتر را mandatory کنیم یا اینکه امکان positional binding و غیره را تعیین کنیم. اتریبیوت Parameter در واقع یک وهله از System.Management.Automation.ParameterAttribute میباشد. میتوانید با نوشتن دستور زیر لیستی از خواصی را که میتوانید همراه با این اتریبیوت تعیین کنید، مشاهده کنید: 
PS /> [Parameter]::new()

ExperimentName                  :
ExperimentAction                : None
Position                        : -2147483648
ParameterSetName                : __AllParameterSets
Mandatory                       : False
ValueFromPipeline               : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments     : False
HelpMessage                     :
HelpMessageBaseName             :
HelpMessageResourceId           :
DontShow                        : False
TypeId                          : System.Management.Automation.ParameterAttribute
در ادامه یک مثال از نحوه هندل کردن ورودی‌های یک تابع را بررسی خواهیم کرد. تابع زیر یک لیست از URLها را از کاربر دریافت کرده و یک health check توسط دستور Test-Connection انجام میدهد. در کد زیر پارامتر Websites را با تعدادی اتریبیوت مزین کرده‌ایم. توسط اتریبیوت Parameter تعیین کرده‌ایم که ورودی الزامی است و همچنین مقدار آن میتواند از pipeline نیز دریافت شود. در ادامه توسط ValidatePattern یک عبارت باقاعده را برای بررسی صحیح بودن URL دریافتی نوشته‌ایم. از آنجائیکه ورودی از نوع آرایه‌ایی از string تعریف شده است، این تست برای هر آیتم از آرایه بررسی خواهد شد. برای پارامتر دوم یعنی Count نیز رنج مقداری را که کاربر وارد میکند، حداقل ۳ و حداکثر ۳ انتخاب کرده‌ایم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidatePattern('^www\..*')]
        [string[]]$Websites,
        [ValidateRange(1, 3)]
        [int]$Count = 3
    )
    $Results = @()
    $Websites | ForEach-Object {
        $Website = $_
        $Result = Test-Connection -ComputerName $Website -Count $Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Results += @{
            Website = $Website
            Result  = $ResultText
        }
        Write-Verbose "The result of pinging $Website is $ResultText"
    }
    $Results | ForEach-Object { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $Count }; } 
    }
}
یکی دیگر از اعتبارسنجی‌هایی که میتوانیم برای پارامترهای یک تابع انتخاب کنیم، ValidateScript است. توسط این اتریبیوت میتوانیم یک منطق سفارشی برای اعتبارسنجی مقادیر پارامترها بنویسیم. به عنوان مثال تابع فوق را به گونه‌ایی تغییر خواهیم داد که لیست وب‌سایت‌ها را از طریق یک فایل JSON دریافت کند. میخواهیم قبل از دریافت فایل مطمئن شویم که فایل، به صورت فیزیکی روی دیسک وجود دارد، در غیراینصورت باید یک خطا را به کاربر نمایش دهیم: 
Function Ping-Website {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                If (-Not ($_ | Test-Path) ) {
                    Throw "File or folder does not exist" 
                }
                If (-Not ($_ | Test-Path -PathType Leaf) ) {
                    Throw "The Path argument must be a file. Folder paths are not allowed."
                }
                If ($_ -NotMatch "(\.json)$") {
                    throw "The file specified in the path argument must be either of type json"
                }
                Return $true
            })]
        [Alias("src", "source", "file")]
        [System.IO.FileInfo]$Path,
        [int]$Count = 1
    )
    $Results = [System.Collections.ArrayList]@()
    $Urls = Get-Content -Path $Path | ConvertFrom-Json
    $Urls | ForEach-Object -Parallel {
        $Website = $_.url
        $Result = Test-Connection -ComputerName $Website -Count $using:Count -Quiet
        $ResultText = $Result ? 'Success' : 'Failed'
        $Item = @{
            Website = $Website
            Result  = $ResultText
        }
        $null = ($using:Results).Add($Item)
    }
    
    $Results | ForEach-Object -Parallel { 
        $_ | Select-Object @{ Name = "Website"; Expression = { $_.Website }; }, @{ Name = "Result"; Expression = { $_.Result }; }, @{ Name = "Number Of Attempts"; Expression = { $using:Count }; } 
    }
}
تابع Ping-Website را جهت بررسی فیچر جدیدی که همراه با دستور ForEach-Object استفاده میشود، تغییر داده‌ایم تا به صورت Parallel عمل کند؛ این قابلیت از نسخه ۷ به بعد به PowerShell اضافه شده است. از آنجائیکه این قابلیت باعث میشود script block مربوط به ForEach-Object درون یک context دیگر با نام runspace اجرا شود. در نتیجه برای دسترسی به متغیرهای بیرون از script block نیاز خواهیم داشت از یک متغیر خودکار تحت‌عنوان using قبل از نام متغیر و بعد از علامت $ استفاده کنیم. همچنین آرایه مثال قبل را نیز به ArrayList تغییر داده‌ایم. زیرا در حالت قبلی امکان تغییر سایز یک آرایه با سایز ثابت را نخواهیم داشت. نکته دیگری که در مورد کد فوق میتوان به آن توجه کرد، نال کردن خروجی متد Add مربوط به آرایه‌ی Results است. همانطور که در قسمت قبل توضیح دادیم، از این تکنیک برای suppress کردن خروجی استفاده میکنیم و چون در اینجا خروجی متد Add یک عدد میباشد، با تکنیک فوق، خروجی را دیگر درون کنسول مشاهده نخواهیم کرد. توسط اتریبیوت Alias نیز نام‌های دیگری را که میتوان برای پارامتر Path حین فراخوانی تابع استفاده کرد، تعیین کرده‌ایم. لیست کامل اتریبیوت‌هایی را که میتوان برای پارامترهای یک تابع تعیین کرد، میتوانید در مستندات PowerShell ببینید. 
نکته: اگر تابع فوق را همراه با فلگ Verbose فراخوانی کنیم، لاگ‌های موردنظر را درون کنسول مشاهده نخواهیم کرد؛ زیرا همانطور که اشاره شد script block درون یک context جدا اجرا میشود و باید متغیرهای خودکار مربوط به Output را مجدداً مقداردهی کنیم:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    # As before
    $Urls | ForEach-Object -Parallel {
        $DebugPreference = $using:DebugPreference 
        $VerbosePreference = $using:VerbosePreference 
        $InformationPreference = $using:InformationPreference 
        
        # As before
    }
    
    # As before
}

قابلیت تعریف بلاک‌ها/توابع، به صورت تودرتو  
درون توابع و script block امکان نوشتن بلاک‌های تودرتو را نیز داریم:
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }
    $someVariable = 20

    & $doSomeWork
}
خروجی بلاک فوق  Some variable value: 20 خواهد بود؛ زیرا قبل از فراخوانی doSomeWork مقدار متغیر عددی someVariable را به ۲۰ تغییر داده‌ایم. برای script blocks این امکان را داریم که دقیقاً در همان جایی که بلاک را تعریف میکنیم، یک snapshot تهیه کنیم. در اینحالت خروجی، مقدار Some variable value: 10 خواهد شد: 
$scriptBlock = {
    $logOutput = {
        param($message)
        Write-Host $message
    }

    [int]$someVariable = 10
    $doSomeWork = {
        & $logOutput -message "Some variable value: $someVariable"
    }.GetNewClosure()
    $someVariable = 20

    & $doSomeWork
}
یکسری بلاک‌های ویژه نیز درون توابع و script blockها میتوانیم بنویسیم که اصطلاحاً به name blocks معروف هستند:
begin
process
end
dynamicparam
درون یک تابع اگر هیچکدام از بلاک‌های فوق استفاده نشود، به صورت پیش‌فرض بدنه تابع، درون بلاک end قرار خواهد گرفت. بلاک begin قبل از شروع pipeline اجرا میشود. process به ازای هر آیتم pipe شده اجرا خواهد شد. end نیز در پایان اجرا میشود. به عنوان مثال تابع زیر را در نظر بگیرید:
function Show-Pipeline {
    begin { 
        Write-Host "Pipeline start" 
    }
    process { 
        Write-Host  "Pipeline process $_" 
    }
    end { 
        Write-Host  "Pipeline end $_" 
    }
}
در ادامه یکسری آیتم را به ورودی این تابع pipe خواهیم کرد:
PS /> 1..2 | Show-Pipeline                                   
Pipeline start 
Pipeline process 1
Pipeline process 2
Pipeline end 2
همانطور که مشاهده میکنید، به ازای هر آیتم pipe شده، یکبار بلاک process اجرا شده است. همچنین برای دسترسی به مقدار آیتم pipe شده نیز از متغیر خودکار _$ استفاده کرده‌ایم (PSItem$ نیز به همین متغیر اشاره دارد).

با توجه به توضیحات named blockهای فوق، اکنون اگر بخواهیم نسخه اول تابع Ping-Website را با pipe کردن یک آرایه فراخوانی کنیم، خروجی که در کنسول نمایش داده خواهد شد، تنها آیتم آخر از آرایه خواهد بود:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website                 

Website       Result  Number Of Attempts
-------       ------  ------------------
www.yahoo.com Success                  3
دلیل آن نیز این است که به صورت صریح کدها را درون بلاک process ننوشته بودیم. همانطور که عنوان شد، در حالت پیش‌فرض، بدنه توابع درون بلاک end قرار خواهند گرفت و تنها یکبار اجرا خواهند شد. بنابراین:
Function Ping-Website {
    [CmdletBinding()]
    Param(
        # As before
    )
    process {
        # As before
    }
}
اینبار اگر تابع را مجدداً فراخوانی کنیم، خروجی مطلوب را نمایش خواهد داد:
PS /> "www.google.com", "www.yahoo.com" | Ping-Website

Website        Result  Number Of Attempts
-------        ------  ------------------
www.google.com Success                  3
www.yahoo.com  Success                  3

بلاک dynamicparam
از این بلاک برای تعریف پارامترهای داینامیک که به صورت on the fly نیاز هست ایجاد شوند، استفاده میشود. برای درک بهتر آن فرض کنید میخواهیم تابعی را بنویسیم که امکان خواندن یک فایل CSV را به ما میدهد. تا اینجای کار توسط Import-CSV به یک خط دستور قابل انجام است. اما فرض کنید میخواهیم به کاربر این امکان را بدهیم که یک ستون موردنظر از فایل را مشاهده کند. همچنین میخواهیم یک اعتبارسنجی هم روی نام ستونی که کاربر قرار است وارد کند نیز داشته باشیم. به عنوان مثال یک فایل CSV با ستون‌های name, lname, age داریم و کاربر میخواهد تنها ستون اول یک name را واکشی کند:
PS /> Read-Csv ./users.csv -Columns name
برای اینکار میتوانیم با کمک dynamic param یک پارامتر را در زمان اجرا ایجاده کرده و مقادیری را که کاربر برای ستون‌ها مجاز است وارد کند، براساس هدر فایل CSV تنظیم کنیم:
using namespace System.Management.Automation
Function Read-Csv {
    Param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path
    )
    DynamicParam {
        $firstLine = Get-Content $Path | Select-Object -First 1
        [String[]]$headers = $firstLine -split ', '
        $parameters = [RuntimeDefinedParameterDictionary]::new()
        $parameter = [RuntimeDefinedParameter]::new(
            'Columns', [String[]], [Attribute[]]@(
                [Parameter]@{ Mandatory = $false; Position = 1 }
                [ValidateSet]::new($headers)
            )
        )
        $parameters.Add($parameter.Name, $parameter) 
        Return $parameters
    }
    Begin {
        $csvContent = Import-Csv $Path
        If ($PSBoundParameters.ContainsKey('Columns')) {
            $columns = $PSBoundParameters['Columns']
            $csvContent | Select-Object -Property $columns
        }
        Else {
            $csvContent
        }
    }
}
درون کنسول PowerShell هم یک IntelliSense برای مقادیر مجاز نمایش داده خواهد شد:

نظرات مطالب
متدهای کمکی مفید در پروژه های asp.net mvc
ببخشید این کتابخانه Html Agility Pack   که معرفی کردید آیا امکان انتخاب تمامی radio هایی که تیک خورده رو میده. در حالت عادی با دستور زیر همه رو برمیگردونه

var radio = doc.DocumentNode.SelectNodes("//input[@class='radio']");
اما من میخام فقط radio هایی رو برگردونه که تیک خورده. ممنون میشم راهنمایی کنید؟
مطالب
Angular Material 6x - قسمت سوم - طرحبندی برنامه
پس از نصب بسته‌ی Angular Material و آشنایی با سیستم Angular Flex Layout برای پوشش کمبود سیستم طرحبندی آن، در این قسمت طرح ابتدایی دفترچه تلفن این سری را پیگیری می‌کنیم تا به طراحی زیر برای حالت‌های دسکتاپ و موبایل برسیم:



در اینجا توسط کامپوننت sidenav، کار نمایش لیست تماس‌ها صورت می‌گیرد و نمایش این کامپوننت واکنشگرا است. به این معنا که در اندازه‌های صفحات نمایشی بزرگ، نمایان است و در صفحات نمایشی کوچک، مخفی خواهد شد. در بالای صفحه یک Toolbar قرار دارد که همیشه نمایان است و از آن برای نمایش گزینه‌های منوی برنامه استفاده می‌کنیم. همچنین ناحیه‌ی main content را هم مشاهده می‌کنید که با انتخاب هر شخص از لیست تماس‌ها، جزئیات او در این قسمت نمایش داده خواهد شد.


ایجاد ماژول مدیریت تماس‌ها

در قسمت اول، برنامه را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد کردیم که نتیجه‌ی آن تولید فایل src\app\app-routing.module.ts می‌باشد:
 ng new MaterialAngularClient --routing
در ادامه ماژول مخصوص مدیریت تماس‌ها را ایجاد می‌کنیم که به آن feature module هم گفته می‌شود. برای این منظور دستور زیر را اجرا کنید:
 ng g m ContactManager -m app.module --routing


این دستور ماژول جدید contact-manager را به همراه تنظیمات ابتدایی مسیریابی و همچنین به روز رسانی app.module، برای درج آن، ایجاد می‌کند. البته در این حالت نیاز است به app.module.ts مراجعه کرد و محل درج آن‌را تغییر داد:
import { ContactManagerModule } from "./contact-manager/contact-manager.module";

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    CoreModule,
    SharedModule.forRoot(),
    ContactManagerModule,
    AppRoutingModule
  ],
})
export class AppModule { }
به صورت پیش‌فرض ContactManagerModule، پس از AppRoutingModule ذکر می‌شود و چون مسیر catch all را در ادامه در AppRoutingModule قرار می‌دهیم، دیگر هیچگاه مسیریابی‌های ContactManagerModule پردازش نخواهند شد. به همین جهت باید آن‌را پیش از AppRoutingModule قرار داد.
سپس دستور زیر را اجرا می‌کنیم تا کامپوننت contact-manager-app در ماژول contact-manager ایجاد شود:
 ng g c contact-manager/ContactManagerApp --no-spec
با این خروجی:
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.html (38 bytes)
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.ts (319 bytes)
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.css (0 bytes)
UPDATE src/app/contact-manager/contact-manager.module.ts (436 bytes)
همانطور که ملاحظه می‌کنید این دستور کار به روز رسانی contact-manager.module را نیز جهت معرفی این کامپوننت جدید انجام داده‌است.
این کامپوننت به عنوان میزبان سایر کامپوننت‌هایی که در مقدمه‌ی بحث عنوان شدند، عمل می‌کند. این کامپوننت‌ها را به صورت زیر در پوشه‌ی components ایجاد می‌کنیم:
ng g c contact-manager/components/toolbar --no-spec
ng g c contact-manager/components/main-content --no-spec
ng g c contact-manager/components/sidenav --no-spec
با این خروجی:



تنظیمات مسیریابی برنامه

در ادامه به src\app\app-routing.module.ts مراجعه کرده و این ماژول جدید را به صورت lazy load معرفی می‌کنیم:
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

const routes: Routes = [
  { path: "contactmanager", loadChildren: "./contact-manager/contact-manager.module#ContactManagerModule" },
  { path: "", redirectTo: "contactmanager", pathMatch: "full" },
  { path: "**", redirectTo: "contactmanager" }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا تنظیمات صفحه‌ی پیش‌فرض برنامه و همچنین not found و یا catch all را نیز مشاهده می‌کنید که هر دو به contactmanager تنظیم شده‌اند.

سپس تنظیمات مسیریابی ماژول مدیریت تماس‌ها را در فایل src\app\contact-manager\contact-manager-routing.module.ts به صورت زیر تغییر می‌دهیم:
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

import { MainContentComponent } from "./components/main-content/main-content.component";
import { ContactManagerAppComponent } from "./contact-manager-app/contact-manager-app.component";

const routes: Routes = [
  {
    path: "", component: ContactManagerAppComponent,
    children: [
      { path: "", component: MainContentComponent }
    ]
  },
  { path: "**", redirectTo: "" }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ContactManagerRoutingModule { }
در اولین بار بارگذاری این ماژول، کامپوننت ContactManagerApp بارگذاری می‌شود. همچنین مسیر not found نیز به همان مسیر ریشه‌ی این کامپوننت تنظیم شده‌است.
کامپوننت ContactManagerApp که کار هاست سایر کامپوننت‌های این ماژول را بر عهده دارد، دارای router-outlet خاص خود خواهد بود. به همین جهت برای آن children تعریف شده‌است که مسیر پیش‌فرض آن، بارگذاری کامپوننت MainContent است.
بنابراین نیاز است به فایل contact-manager-app\contact-manager-app.component.html مراجعه و ابتدا منوی کنار صفحه را به آن افزود:
 <app-sidenav></app-sidenav>
app-sidenav همان selector کامپوننت sidenav است که در فایل sidenav\sidenav.component.ts قابل مشاهده‌است.
سپس در قالب sidenav\sidenav.component.html، کار تعریف toolbar و همچنین router-outlet را انجام می‌دهیم:
<app-toolbar></app-toolbar>
<router-outlet></router-outlet>
بر اساس مسیریابی که تعریف کردیم، router-outlet کار نمایش Main Content را انجام می‌دهد.


معرفی Angular Material به ماژول جدید مدیریت تماس‌ها

در قسمت اول، یک فایل material.module.ts را ایجاد کردیم که به همراه تعریف تمامی کامپوننت‌های Angular Material بود. سپس آن‌را به shared.module.ts افزودیم که حاوی تعریف ماژول فرم‌ها و همچنین Flex Layout نیز هست. به همین جهت برای معرفی این‌ها به این ماژول جدید تنها کافی است در فایل src\app\contact-manager\contact-manager.module.ts در قسمت imports، کار معرفی SharedModule صورت گیرد:
import { SharedModule } from "../shared/shared.module";

@NgModule({
  imports: [
    CommonModule,
    SharedModule,
    ContactManagerRoutingModule
  ]
})
export class ContactManagerModule { }
تا اینجا پیش از ادامه‌ی کار، فرمان ng serve -o را صادر کنید تا مطمئن شویم همه چیز به درستی قابل دریافت و کامپایل است.


پس از اجرای برنامه مشاهده می‌کنید که ابتدا ماژول مدیریت تماس‌ها بارگذاری شده‌است و سپس contact-manager-app عمل و sidenav را بارگذاری کرده و آن نیز سبب نمایش کامپوننت toolbar و سپس main-content شده‌است.


تنظیم طرحبندی برنامه توسط کامپوننت‌های Angular Material و همچنین Flex Layout


پس از این تنظیمات اکنون نوبت به تنظیم طرحبندی برنامه‌است و آن‌را با قراردادن کامپوننت Sidenav بسته‌ی Angular Material شروع می‌کنیم:
<mat-sidenav-container *ngIf="shouldRun">
   <mat-sidenav mode="side" opened>
      Sidenav content
   </mat-sidenav>
   Primary content
</mat-sidenav-container>
از کامپوننت Sidenav عموما برای طراحی منوی راهبری سایت استفاده می‌شود. این کامپوننت در سه حالت قابل تنظیم است که بر روی نحوه‌ی نمایش Sidenav content و Primary content تاثیرگذار است:
- Over: قسمت Sidenav content بر روی Primary content قرار می‌گیرد.
- Push: قسمت Sidenav content قسمت Primary content را از سر راه خود بر می‌دارد.
- Side:  قسمت Sidenav content در کنار Primary content قرار می‌گیرد.

در اینجا از حالت Side، در صفحات نمایشی بزرگ (اولین تصویر این قسمت) و از حالت Over، در صفحات نمایشی موبایل (مانند تصویر زیر) استفاده خواهیم کرد.



در ابتدا کدهای کامل هر سه کامپوننت و سپس توضیحات آن‌ها را مشاهده خواهید کرد:


تنظیم margin در CSS اصلی برنامه

زمانیکه sidenav و toolbar را بر روی صفحه قرار می‌دهیم، فاصله‌ای بین آن‌ها و لبه‌های صفحه مشاهده می‌شود. برای اینکه این فاصله را به صفر برسانیم، به فایل src\styles.css مراجعه کرده و margin بدنه‌ی صفحه را به صفر تنظیم می‌کنیم:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";

body {
  margin: 0;
}


طراحی قالب main content

<mat-card>
  <h1>Main content</h1>
</mat-card>
برای نمایش main-content از کامپوننت ساده‌ی card استفاده شده‌است که به فایل main-content\main-content.component.html اضافه خواهد شد. این قسمت در نهایت توسط router-outlet نمایش داده می‌شود.


طرای منوی راهبری واکنشگرا

sidenav\sidenav.component.css 
 sidenav\sidenav.component.html 
.app-sidenav-container {
  position: fixed;
}

.app-sidenav {
  width: 240px;
}

.wrapper {
  margin: 50px;
}
<mat-sidenav-container fxLayout="row"
         fxFill>
  <mat-sidenav #sidenav 
    fxFlex="1 1 100%" [opened]="!isScreenSmall"
    [mode]="isScreenSmall ? 'over' : 'side'">
    <mat-toolbar color="primary">
      Contacts
    </mat-toolbar>
    <mat-list>
      <mat-list-item>Item 1</mat-list-item>
      <mat-list-item>Item 2</mat-list-item>
      <mat-list-item>Item 3</mat-list-item>
    </mat-list>
  </mat-sidenav>

  <mat-sidenav-content fxLayout="column" fxFlex="1 1 100%" fxFill>
    <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
    <div>
      <router-outlet></router-outlet>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>

- نمای کلی صفحه در این قسمت طراحی شده‌است. sidenav-container که در برگیرنده‌ی اصلی است، به fxLayout از نوع row تنظیم شده‌است. یعنی mat-sidenav و mat-sidenav-content دو ستون آن‌را از چپ به راست تشکیل می‌دهند و درون یک ردیف، سیلان خواهند یافت. همچنین می‌خواهیم این container کل صفحه را پر کند، به همین جهت از fxFill استفاده شده‌است. این fxFill اعمال شده‌ی به container، زمانی عمل خواهد کرد که position آن در css، به fixed تنظیم شود که اینکار در css این قالب و در کلاس app-sidenav-container آن انجام شده‌است.
- سپس toolbar و همچنین router-outlet که main content را نمایش می‌دهند، داخل sidenav-content قرار گرفته‌اند و هر دو با هم، ستون دوم این طرحبندی را تشکیل می‌دهند. به همین جهت fxLayout آن به column تنظیم شده‌است (ستون اول آن، لیست تماس‌ها است و ستون دوم آن، از دو ردیف toolbar و main-content تشکیل می‌شود).
- اگر دقت کنید یک template reference variable به نام sidenav# به container اعمال شده‌است. از آن، جهت باز و بسته کردن sidenav استفاده می‌شود:
<app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
زمانیکه در toolbar بر روی دکمه‌ی منوی همبرگری کلیک شود، متد sidenav.toggle فراخوانی شده و این مورد سبب نمایان شدن مجدد sidenav خواهد شد. در این مورد در ادامه بیشتر بحث می‌کنیم.
- mat-sidenav از دو قسمت تشکیل شده‌است. بالای آن توسط mat-toolbar صرفا کلمه‌ی Contacts نمایش داده می‌شود و سپس ذیل آن، لیست فرضی تماس‌ها توسط کامپوننت mat-list قرار گرفته‌اند (تا فعلا خالی نباشد. در قسمت‌های بعدی آن‌را پویا خواهیم کرد). رنگ تولبار آن‌را ("color="primary) نیز به primary تنظیم کرده‌ایم تا خاکستری پیش‌فرض آن نباشد.
- کار کلاس mat-elevation-z10 این است که بین sidenav و main-content یک سایه‌ی سه بعدی را ایجاد کند که آن‌را در تصاویر مشاهده می‌کنید. عددی که پس از z قرار می‌گیرد، میزان عمق سایه را مشخص می‌کند.
- این قسمت از sidenav به همراه دو خاصیت opened و همچنین mode است که به مقدار isScreenSmall عکس العمل نشان می‌دهند:
<mat-sidenav  [opened]="!isScreenSmall" [mode]="isScreenSmall ? 'over' : 'side'">
در اینجا می‌خواهیم اگر اندازه‌ی صفحه xs شد، حالت over بجای حالت پیش‌فرض side تنظیم شود. یعنی در حالت موبایل و اندازه‌ی صفحه‌ی کوچک، sidenav در صورت فراخوانی متد sidenav.toggle در toolbar، بر روی قسمتی از صفحه ظاهر شود و نه در کنار آن که مخصوص حالت تمام صفحه است. همچنین می‌خواهیم اگر اندازه‌ی صفحه کوچک بود، sidenav بسته شود و نمایان نباشد. به همین جهت خاصیت opened آن به isScreenSmall تنظیم شده‌است. مدیریت خاصیت isScreenSmall در کدهای این کامپوننت به صورت زیر انجام می‌شود:

محتویات فایل sidenav\sidenav.component.ts:
import { Component, OnDestroy, OnInit } from "@angular/core";
import { MediaChange, ObservableMedia } from "@angular/flex-layout";
import { Subscription } from "rxjs";

@Component({
  selector: "app-sidenav",
  templateUrl: "./sidenav.component.html",
  styleUrls: ["./sidenav.component.css"]
})
export class SidenavComponent implements OnInit, OnDestroy {

  isScreenSmall = false;
  watcher: Subscription;

  constructor(private media: ObservableMedia) {
    this.watcher = media.subscribe((change: MediaChange) => {
      this.isScreenSmall = change.mqAlias === "xs";
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    this.watcher.unsubscribe();
  }
}
ObservableMedia را در انتهای قسمت دوم این سری بررسی کردیم. کار آن گوش فرادادن به تغییرات اندازه‌ی صفحه‌است. زمانیکه mqAlias آن برای مثال مساوی xs شد، یعنی در حالت موبایل قرار داریم. در این حالت مقدار خاصیت isScreenSmall به true تنظیم می‌شود و برعکس. با توجه به اینکه این media یک Observable است، نیاز است کار unsubscribe از آن نیز همواره در کدها وجود داشته باشد که نمونه‌ای از آن در متد ngOnDestroy صورت گرفته‌است.
تاثیر خاصیت isScreenSmall بر روی دو خاصیت opened و mode کامپوننت sidenav را در دو تصویر زیر مشاهده می‌کنید. اگر اندازه‌ی صفحه کوچک شود، ابتدا sidenav مخفی می‌شود. اگر کاربر بر روی دکمه‌ی منوی همبرگری کلیک کند، سبب نمایش مجدد sidenav، اینبار با حالت over و بر روی محتوای زیرین آن خواهد شد:




طراحی نوار ابزار واکنشگرا

کدهای قالب و css تولبار (ستون دوم طرحبندی کلی صفحه) را در ادامه مشاهده می‌کنید:
  toolbar\toolbar.component.css    toolbar\toolbar.component.html 
 
.sidenav-toggle {
  padding: 0;
  margin: 8px;
  min-width:56px;
}
 
<mat-toolbar color="primary">
  <button mat-button fxHide fxHide.xs="false" 
              class="sidenav-toggle" (click)="toggleSidenav.emit()">
    <mat-icon>menu</mat-icon>
  </button>

  <span>Contact Manager</span>
</mat-toolbar>

با توجه به استفاده‌ی از fxHide، یعنی دکمه‌ی نمایش منوی همبرگری در تمام حالات مخفی خواهد بود. برای لغو آن و نمایش آن در حالت موبایل، از حالت واکنشگرای آن یعنی fxHide.xs استفاده می‌کنیم (قسمت «کار با API واکنشگرای Angular Flex layout» در مطلب قبلی این سری). به این ترتیب زمانیکه کاربر اندازه‌ی صفحه را کوچک می‌کند و یا اندازه‌ی واقعی صفحه‌ی نمایش او کوچک است، این دکمه نمایان خواهد شد.
همچنین در sidenav یک چنین تعریفی را داریم:
 <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
بروز رخ‌داد toggleSidenav سبب خواهد شد که متد sidenav.toggle فراخوانی شود و سبب نمایش sidenav در اندازه‌های کوچک صفحه‌ی نمایشی گردد. این رخ‌داد سفارشی را نیز به رخ‌داد click دکمه‌ی همبرگری تولبار متصل کرده‌ایم که با کلیک بر روی آن، کار emit آن صورت می‌گیرد. این emit نیز سبب خواهد شد تا sidenav.toggle متصل به سمتی دیگر، فعال شود. نحوه‌ی تعریف این رخ‌داد سفارشی را در کدهای کامپوننت تولبار، در ادامه مشاهده می‌کنید:

محتویات فایل toolbar\toolbar.component.ts:
import { Component, EventEmitter, OnInit, Output } from "@angular/core";

@Component({
  selector: "app-toolbar",
  templateUrl: "./toolbar.component.html",
  styleUrls: ["./toolbar.component.css"]
})
export class ToolbarComponent implements OnInit {

  @Output() toggleSidenav = new EventEmitter<void>();

  constructor() { }

  ngOnInit() {
  }
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-02.zip
برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید. پس از اجرای برنامه، یکبار آن‌را در حالت تمام صفحه و بار دیگر با کوچک‌تر کردن اندازه‌ی مرورگر آزمایش کنید. در حالتیکه به اندازه‌ی موبایل رسیدید، بر روی دکمه‌ی همبرگری نمایان شده کلیک کنید تا عکس العمل آن و نمایش مجدد sidenav را در حالت over، مشاهده نمائید.
مطالب
ASP.NET MVC #14

آشنایی با نحوه معرفی تعاریف طرحبندی سایت به کمک Razor

ممکن است یک سری از اصطلاحات را در قسمت‌های قبل مانند master page در لابلای توضیحات ارائه شده، مشاهده کرده باشید. این نوع مفاهیم برای برنامه نویس‌های ASP.NET Web forms آشنا است (و اگر با Web forms view engine‌ در ASP.NET MVC کار کنید، دقیقا یکی است؛ البته با این تفاوت که فایل code behind آن‌ها حذف شده است). به همین جهت در این قسمت برای تکمیل بحث، مروری خواهیم داشت بر نحوه‌ی معرفی جدید آن‌ها توسط Razor.
در یک پروژه جدید ASP.NET MVC و در پوشه Views\Shared\_Layout.cshtml آن، فایل Layout آن،‌ مفهوم master page را دارد. در این نوع فایل‌ها، زیر ساخت مشترک تمام صفحات سایت قرار می‌گیرند:

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>

<body>
@RenderBody()
</body>
</html>

اگر دقت کرده باشید، در هیچکدام از فایل‌های Viewایی که تا این قسمت به پروژه‌های مختلف اضافه کردیم، تگ‌هایی مانند body، title و امثال آن وجود نداشتند. در ASP.NET مرسوم است کلیه اطلاعات تکراری صفحات مختلف سایت را مانند تگ‌های یاد شده به همراه منویی که باید در تمام صفحات قرار گیرد یا footer‌ مشترک بین تمام صفحات سایت، به یک فایل اصلی به نام master page که در اینجا layout نام گرفته، Refactor کنند. به این ترتیب حجم کدها و markup تکراری که باید در تمام Viewهای سایت قرار گیرند به حداقل خواهد رسید.
برای مثال محل قرار گیری تعاریف Content-Type تمام صفحات و همچنین favicon سایت، بهتر است در فایل layout باشد و نه در تک تک Viewهای تعریف شده:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="shortcut icon" href="@Url.Content("~/favicon.ico")" type="image/x-icon" />


در کدهای فوق یک نمونه پیش فرض فایل layout را مشاهده می‌کنید. در اینجا توسط متد RenderBody، محتوای رندر شده یک View درخواستی، به داخل تگ body تزریق خواهد شد.
تا اینجا در تمام مثال‌های قبلی این سری، فایل layout در Viewهای اضافه شده معرفی نشد. اما اگر برنامه را اجرا کنیم باز هم به نظر می‌رسد که فایل layout اعمال شده است. علت این است که در صورت عدم تعریف صریح layout در یک View، این تعریف از فایل Views\_ViewStart.cshtml دریافت می‌گردد:

@{
Layout = "~/Views/Shared/_Layout.cshtml";
}

فایل ViewStart، محل تعریف کدهای تکراری است که باید پیش از اجرای هر View مقدار دهی یا اجرا شوند. برای مثال در اینجا می‌شود بر اساس نوع مرورگر،‌ layout خاصی را به تمام Viewها اعمال کرد. مثلا یک layout‌ ویژه برای مرورگرهای موبایل‌ها و layout ایی دیگر برای مرورگرهای معمولی. امکان دسترسی به متغیرهای تعریف شده در یک View در فایل ViewStart از طریق ViewContext.ViewData میسر است.
ضمن اینکه باید درنظر داشت که می‌توان فایل ViewStart را در زیر پوشه‌های پوشه اصلی View نیز قرار داد. مثلا اگر فایل ViewStart ایی در پوشه Views/Home قرار گرفت، این فایل محتوای ViewStart اصلی قرار گرفته در ریشه پوشه Views را بازنویسی خواهد کرد.
برای معرفی صریح فایل layout، تنها کافی است مسیر کامل فایل layout را در یک View مشخص کنیم:

@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>

اهمیت این مساله هم در اینجا است که یک سایت می‌تواند چندین layout یا master page داشته باشد. برای نمونه یک layout برای صفحات ورود اطلاعات؛ یک layout خاص هم مثلا برای صفحات گزارش گیری نهایی سایت.
همانطور که پیشتر نیز ذکر شد، در ASP.NET حرف ~ به معنای ریشه سایت است که در اینجا ابتدای محل جستجوی فایل layout را مشخص می‌کند.
به این ترتیب زمانیکه یک کنترلر، View خاصی را فراخوانی می‌کند، کار از فایل Views\Shared\_Layout.cshtml شروع خواهد شد. سپس View درخواستی پردازش شده و محتوای نهایی آن، جایی که متد RenderBody قرار دارد، تزریق خواهد شد.
همچنین مقدار ViewBag.Title ایی که در فایل View تعریف شده، در فایل layout جهت رندر مقدار تگ title استفاده می‌شود (انتقال یک متغیر از View به یک فایل master page؛ کلاس layout، مدل View ایی را که قرار است رندر کند به ارث می‌برد).

یک نکته:
در نگارش سوم ASP.NET MVC امکان بکارگیری حرف ~ به صورت مستقیم در حین تعریف یک فایل js یا css وجود ندارد و حتما باید از متد سمت سرور Url.Content کمک گرفت. در نگارش چهارم ASP.NET MVC، این محدودیت برطرف شده و دقیقا همانند متغیر Layout ایی که در بالا مشاهده می‌کنید، می‌توان بدون نیاز به متد Url.Content، مستقیما از حرف ~ کمک گرفت و به صورت خودکار پردازش خواهد شد.


تزریق نواحی ویژه یک View در فایل layout

توسط متد RenderBody، کل محتوای View درخواستی در موقعیت تعریف شده آن در فایل Layout، رندر می‌شود. این ویژگی به نحو یکسانی به تمام Viewها اعمال می‌شود. اما اگر نیاز باشد تا view بتواند محتوای markup قسمت ویژه‌ای از master page را مقدار دهی کند، می‌توان از مفهومی به نام Sections استفاده کرد:
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>
<body>
<div id="menu">
@if (IsSectionDefined("Menu"))
{
RenderSection("Menu", required: false);
}
else
{
<span>This is the default ...!</span>
}
</div>
<div id="body">
@RenderBody()
</div>
</body>
</html>

در اینجا ابتدا بررسی می‌شود که آیا قسمتی به نام Menu در View جاری که باید رندر شود وجود دارد یا خیر. اگر بله، توسط متد RenderSection، آن قسمت نمایش داده خواهد شد. در غیراینصورت، محتوای پیش فرضی را در صفحه قرار می‌دهد. البته اگر از متد RenderSection با آرگومان required: false استفاده شود، درصورتیکه View جاری حاوی قسمتی به نام مثلا menu نباشد، تنها چیزی نمایش داده نخواهد شد. اگر این آرگومان را حذف کنیم، یک استثنای عدم یافت شدن ناحیه یا قسمت مورد نظر صادر می‌گردد.
نحوه‌ی تعریف یک Section در Viewهای برنامه به شکل زیر است:
@{
ViewBag.Title = "Index";
//Layout = null;
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Index</h2>
@section Menu{
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
}

برای مثال فرض کنید که یکی از Viewهای شما نیاز به دو فایل اضافی جاوا اسکریپت برای اجرای صحیح خود دارد. می‌توان تعاریف الحاق این دو فایل را در قسمت header فایل layout قرار داد تا در تمام Viewها به صورت خودکار لحاظ شوند. یا اینکه یک section سفارشی را به نحو زیر در آن View خاص تعریف می‌کنیم:

@section JavaScript
{
<script type="text/javascript" src="@Url.Content("~/Scripts/SomeScript.js")" />;
<script type="text/javascript" src="@Url.Content("~/Scripts/AnotherScript.js")" />;
}

سپس کافی است جهت تزریق این کدها به header تعریف شده در master page مورد نظر، یک سطر زیر را اضافه کرد:

@RenderSection("JavaScript", required: false)

به این ترتیب، اگر view ایی حاوی تعریف قسمت JavaScript نبود، به صورت خودکار شامل تعاریف الحاق اسکریپت‌های یاد شده نیز نخواهد گردید. در نتیجه دارای حجمی کمتر و سرعت بارگذاری بالاتری نیز خواهد بود.



مدیریت بهتر فایل‌ها و پوشه‌های یک برنامه ASP.NET MVC به کمک Areas

به کمک قابلیتی به نام Areas می‌توان یک برنامه بزرگ را به چندین قسمت کوچکتر تقسیم کرد. هر کدام از این نواحی، دارای تعاریف مسیریابی، کنترلرها و Viewهای خاص خودشان هستند. به این ترتیب دیگر به یک برنامه‌ی از کنترل خارج شده ASP.NET MVC که دارای یک پوشه Views به همراه صدها زیر پوشه است، نخواهیم رسید و کنترل این نوع برنامه‌های بزرگ ساده‌تر خواهد شد.
برای مثال یک برنامه بزرگ را درنظر بگیرید که به کمک قابلیت Areas، به نواحی ویژه‌ای مانند گزارشگیری، قسمت ویژه مدیریتی، قسمت کاربران، ناحیه بلاگ سایت، ناحیه انجمن سایت و غیره، تقسیم شده است. به علاوه هر کدام از این نواحی نیز هنوز می‌توانند از اطلاعات ناحیه اصلی برنامه مانند master page آن استفاده کنند. البته باید درنظر داشت که فایل viewStart به پوشه جاری و زیر پوشه‌های آن اعمال می‌شود. اگر نیاز باشد تا اطلاعات این فایل به کل برنامه اعمال شود، فقط کافی است آن‌را به یک سطح بالاتر، یعنی ریشه سایت منتقل کرد.


نحوه افزودن نواحی جدید
افزودن یک Area جدید هم بسیار ساده است. بر روی نام پروژه در VS.NET کلیک راست کرده و سپس گزینه Add|Area را انتخاب کنید. سپس در صفحه باز شده، نام دلخواهی را وارد نمائید. مثلا نام Reporting را وارد نمائید تا ناحیه گزارشگیری برنامه از قسمت‌های دیگر آن مستقل شود. پس از افزودن یک Area جدید، به صورت خودکار پوشه جدیدی به نام Areas به ریشه سایت اضافه می‌شود. سپس داخل آن، پوشه‌ی دیگری به نام Reporting اضافه خواهد شد. پوشه reporting اضافه شده هم دارای پوشه‌های Model، Views و Controllers خاص خود می‌باشد.
اکنون که پوشه Areas به ریشه سایت اضافه شده است، با کلیک راست بر روی این پوشه نیز گزینه‌ی Add|Area در دسترس می‌باشد. برای نمونه یک ناحیه جدید دیگر را به نام Admin به سایت اضافه کنید تا بتوان امکانات مدیریتی سایت را از سایر قسمت‌های آن مستقل کرد.


نحوه معرفی تعاریف مسیریابی نواحی تعریف شده
پس از اینکه کار با Areas را آغاز کردیم، نیاز است تا با نحوه‌ی مسیریابی آن‌ها نیز آشنا شویم. برای این منظور فایل Global.asax.cs قرار گرفته در ریشه اصلی برنامه را باز کنید. در متد Application_Start، متدی به نام AreaRegistration.RegisterAllAreas، کار ثبت و معرفی تمام نواحی ثبت شده را به فریم ورک، به عهده دارد. کاری که در پشت صحنه انجام خواهد شد این است که به کمک Reflection تمام کلاس‌های مشتق شده از کلاس پایه AreaRegistration به صورت خودکار یافت شده و پردازش خواهند شد. این کلاس‌ها هم به صورت پیش فرض به نام SomeNameAreaRegistration.cs در ریشه اصلی هر Area توسط VS.NET تولید می‌شوند. برای نمونه فایل ReportingAreaRegistration.cs تولید شده، حاوی اطلاعات زیر است:

using System.Web.Mvc;

namespace MvcApplication11.Areas.Reporting
{
public class ReportingAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Reporting";
}
}

public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Reporting_default",
"Reporting/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}

توسط AreaName، یک نام منحصربفرد در اختیار فریم ورک قرار خواهد گرفت. همچنین از این نام برای ایجاد پیوند بین نواحی مختلف نیز استفاده می‌شود.
سپس در قسمت RegisterArea، یک مسیریابی ویژه خاص ناحیه جاری مشخص گردیده است. برای مثال تمام آدرس‌های ناحیه گزارشگیری سایت باید با http://localhost/reporting آغاز شوند تا مورد پردازش قرارگیرند. سایر مباحث آن هم مانند قبل است. برای مثال در اینجا نام اکشن متد پیش فرض، index تعریف شده و همچنین ذکر قسمت id نیز اختیاری است.
همانطور که ملاحظه می‌کنید، تعاریف مسیریابی و اطلاعات پیش فرض آن منطقی هستند و آنچنان نیازی به دستکاری و تغییر ندارند. البته اگر دقت کرده باشید مقدار نام controller پیش فرض، مشخص نشده است. بنابراین بد نیست که مثلا نام Home یا هر نام مورد نظر دیگری را به عنوان نام کنترلر پیش فرض در اینجا اضافه کرد.


تعاریف کنترلر‌های هم نام در نواحی مختلف
در ادامه مثال جاری که دو ناحیه Admin و Reporting به آن اضافه شده، به پوشه‌های Controllers هر کدام، یک کنترلر جدید را به نام HomeController اضافه کنید. همچنین این HomeController را در ناحیه اصلی و ریشه سایت نیز اضافه نمائید. سپس برای متد پیش فرض Index هر کدام هم یک View جدید را با کلیک راست بر روی نام متد و انتخاب گزینه Add view، اضافه کنید. اکنون برنامه را به همین نحو اجرا نمائید. اجرای برنامه با خطای زیر متوقف خواهد شد:

Multiple types were found that match the controller named 'Home'. This can happen if the route that services this
request ('{controller}/{action}/{id}') does not specify namespaces to search for a controller that matches the request.
If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.

The request for 'Home' has found the following matching controllers:
MvcApplication11.Areas.Admin.Controllers.HomeController
MvcApplication11.Controllers.HomeController

فوق العاده خطای کاملی است و راه حل را هم ارائه داده است! برای اینکه مشکل ابهام یافتن HomeController برطرف شود، باید این جستجو را به فضاهای نام هر قسمت از نواحی برنامه محدود کرد (چون به صورت پیش فرض فضای نامی برای آن مشخص نشده، کل ناحیه ریشه سایت و زیر مجموعه‌های آن‌را جستجو خواهد کرد). به همین جهت فایل Global.asax.cs را گشوده و متد RegisterRoutes آن‌را مثلا به نحو زیر اصلاح نمائید:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
, namespaces: new[] { "MvcApplication11.Controllers" }
);
}

آرگومان چهارم معرفی شده، آرایه‌ای از نام‌های فضاهای نام مورد نظر را جهت یافتن کنترلرهایی که باید توسط این مسیریابی یافت شوند، تعریف می‌کند.
اکنون اگر مجددا برنامه را اجرا کنیم، بدون مشکل View متناظر با متد Index کنترلر Home نمایش داده خواهد شد.
البته این مشکل با نواحی ویژه و غیر اصلی سایت وجود ندارد؛ چون جستجوی پیش فرض کنترلرها بر اساس ناحیه است.
در ادامه مسیر http://localhost/Admin/Home را نیز در مرورگر وارد کنید. سپس بر روی صفحه در مروگر کلیک راست کرده و سورس صفحه را بررسی کنید. مشاهده خواهید کرد که master page یا فایل layout ایی به آن اعمال نشده است. علت را هم در ابتدای بحث Areas مطالعه کردید. فایل Views\_ViewStart.cshtml در سطحی که قرار دارد به ناحیه Admin اعمال نمی‌شود. آن‌را به ریشه سایت منتقل کنید تا layout اصلی سایت نیز به این قسمت اعمال گردد. البته بدیهی است که هر ناحیه می‌تواند layout خاص خودش را داشته باشد یا حتی می‌توان با مقدار دهی خاصیت Layout نیز در هر view، فایل master page ویژه‌ای را انتخاب و معرفی کرد.


نحوه ایجاد پیوند بین نواحی مختلف سایت
زمانیکه پیوندی را به شکل زیر تعریف می‌کنیم:
@Html.ActionLink(linkText: "Home", actionName: "Index", controllerName: "Home")

یعنی ایجاد لینکی در ناحیه جاری. برای اینکه پیوند تعریف شده به ناحیه‌ای خارج از ناحیه جاری اشاره کند باید نام Area را صریحا ذکر کرد:

@Html.ActionLink(linkText: "Home", actionName: "Index", controllerName: "Home",
routeValues: new { Area = "Admin" } , htmlAttributes: null)


همین نکته را باید حین کار با متد RedirectToAction نیز درنظر داشت:
public ActionResult Index()
{
return RedirectToAction("Index", "Home", new { Area = "Admin" });
}


مطالب دوره‌ها
تزریق وابستگی‌ها
خوب! بالاخره بعد از دو قسمت قبل، به بحث اصلی تزریق وابستگی‌ها رسیدیم. بحثی که بسیاری از برنامه نویس‌های تصور می‌کنند همان IoC است که پیشتر در مورد آن بحث شد.

تزریق وابستگی‌ها یا DI چیست؟

تزریق وابستگی‌ها یکی از انواع IoC بوده که در آن ایجاد و انقیاد (binding) یک وابستگی، در خارج از کلاسی که به آن نیاز دارد صورت می‌گیرد. روش‌های متفاوتی برای ارائه این وابستگی وهله سازی شده در خارج از کلاس به آن وجود دارد که در ادامه مورد بررسی قرار خواهند گرفت.
یک مثال: بسته غذایی را که با خود به سر کار می‌برید درنظر بگیرید. تمام اجزای مورد نیاز تشکیل دهنده یک بسته غذا عموما داخل آن قرار گرفته و حمل می‌شوند. حالت عکس آن زمانی است که در محل کار به شما غذا می‌دهند. این دقیقا همان حالتی است که تزریق وابستگی‌ها کار می‌کند؛ یک سری سرویس‌های خارجی، نیازهای کلاس جاری را برآورده می‌سازند.


در تصویر فوق یک طراحی مبتنی بر معکوس سازی کنترل‌ها را مشاهده می‌کنید. وابستگی‌های یک کلاس توسط اینترفیسی مشخص شده و سپس کلاسی وجود دارد که این وابستگی را پیاده سازی کرده است.
همچنین در این تصویر یک خط منقطع از کلاس MyClass به کلاس Dependency رسم شده است. این خط، مربوط به حالتی است که خود کلاس، مستقیما کار وهله سازی وابستگی مورد نیاز خود را انجام دهد؛ هر چند اینترفیسی نیز در این بین تعریف شده باشد. بنابراین اگر در بین کدهای این کلاس، چنین کدی مشاهده شد:
 IDependency dependency= new Dependency();
تعریف dependency از نوع IDependency به معنای معکوس سازی کنترل نبوده و عملا همان معماری سابق و متداول بکارگرفته شده است. برای بهبود این وضعیت، از تزریق وابستگی‌ها کمک خواهیم گرفت:


در اینجا یک کلاس دیگر به نام Injector اضافه شده است که قابلیت تزریق وابستگی‌ها را به کلاس نیازمند آن به روش‌های مختلفی دارا است. کار کلاس Injector، وهله سازی MyClass و همچنین وابستگی‌های آن می‌باشد؛ سپس وابستگی را دراختیار MyClass قرار می‌دهد.


تزریق وابستگی‌ها در سازنده کلاس

یکی از انواع روش‌های تزریق وابستگی‌ها، Constructor Injection و یا تزریق وابستگی‌ها در سازنده کلاس می‌باشد که متداول‌ترین نوع آن‌ها نیز به‌شمار می‌رود. در این حالت، وابستگی پس از وهله سازی، از طریق پارامترهای سازنده یک کلاس، در اختیار آن قرار می‌گیرند.
یک مثال:
public class Shopper
{
   private readonly ICreditCard _creditCard;
   public Shopper(ICreditCard creditCard)
   {
      _creditCard = creditCard;
   }
}
در اینجا شما یک کلاس خریدار را مشاهده می‌کنید که وابستگی مورد نیاز خود را به شکل یک اینترفیس از طریق سازنده کلاس دریافت می‌کند.
 ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper(creditCard);
برای نمونه، وهله‌ای از مستر کارتی که  ICreditCard را پیاده سازی کرده باشد، می‌توان به سازنده این کلاس ارسال کرد. کار وهله سازی وابستگی و واژه کلیدی new به خارج از کلاس استفاده کننده از وابستگی منتقل شده است. بنابراین همانطور که ملاحظه می‌کنید، این مفاهیم آنچنان پیچیده نبوده و به همین سادگی قابل تعریف و اعمال هستند.


تزریق در خواص عمومی کلاس

Setter Injection و یا تزریق در خواص عمومی یک کلاس، یکی دیگر از روش‌های تزریق وابستگی‌ها است (setter در اینجا منظور همان get و set یک خاصیت می‌باشد).
در حالت تزریق وابستگی‌ها در سازنده کلاس، امکان وهله سازی آن کلاس بدون ارسال وابستگی‌ها به سازنده آن ممکن نیست؛ اما در اینجا خیر و امکان وهله سازی و استفاده از یک کلاس، پیش از اعمال وابستگی‌ها نیز وجود دارد. بنابراین امکان تغییر و تعویض وابستگی‌ها پس از وهله سازی کلاس نیز میسر است.
public class Shopper
{
   public ICreditCard CreditCard { get; set; }
}

ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper();
shopper.CreditCard = creditCard;
نمونه‌ای از این روش را در مثال فوق مشاهده می‌کنید. در این کلاس، وابستگی مورد نیاز از طریق یک خاصیت عمومی دریافت شده است.


تزریق اینترفیس‌ها

تزریق اینترفیس‌ها نیز یکی دیگر از روش‌های تزریق وابستگی‌ها است؛ اما آنچنان استفاده گسترده‌ای برخلاف دو روش قبل نیافته است.
در این روش نه از سازنده کلاس استفاده می‌شود و نه از یک خاصیت عمومی. ابتدا یک اینترفیس که بیانگر ساختار کلاسی که قرار است تزریق شود ایجاد می‌گردد. سپس این اینترفیس را در کلاس وابستگی مورد نظر پیاده سازی خواهیم کرد. در این اینترفیس نیاز است متد خاصی تعریف شود تا کار تزریق وابستگی را انجام دهد.
یک مثال:
public interface IDependOnCreditCard
{
   void Inject(ICreditCard creditCard);
}

public class Shopper : IDependOnCreditCard
{
  private ICreditCard creditCard;
  public void Inject(ICreditCard creditCard)
  {
     this.creditCard = creditCard;
  }
}  

ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper();
((IDependOnCreditCard)shopper).Inject(creditCard);
در اینجا یک اینترفیس به نام IDependOnCreditCard  تعریف گردیده و متد خاصی را به نام Inject تدارک دیده است که نوعی از کردیت کارد را دریافت می‌کند.
در ادامه کلاس خریدار، اینترفیس IDependOnCreditCard را پیاده سازی کرده است. به این ترتیب کلاس Injector تنها کافی است بداند تا این متد خاص را باید جهت تنظیم و تزریق وابستگی‌ها فراخوانی نماید. این روش نسبتا شبیه به روش Setter injection است.
این روش بیشتر می‌تواند جهت فریم ورک‌هایی که قابلیت یافتن کلیه کلاس‌های مشتق شده از IDependOnCreditCard  را داشته و سپس می‌دانند که باید متد Inject آن‌ها را فراخوانی کنند، مناسب است.


نکاتی که باید حین کار با تزریق وابستگی‌ها درنظر داشت

الف) حین استفاده از تزریق وابستگی‌ها، وهله سازی به تاخیر افتاده وابستگی‌ها میسر نیست. برای مثال زمانیکه یک وابستگی قرار است در سازنده کلاسی تزریق شود، وهله سازی آن باید پیش از نیاز واقعی به آن صورت گیرد. البته امکان استفاده از اشیاء Lazy دات نت 4 برای مدیریت این مساله وجود دارد اما در حالت کلی، دیگر همانند قبل و روش‌های متداول، وهله سازی تنها زمانیکه نیاز به آن وابستگی خاص باشد، میسر نیست. به همین جهت باید به تعداد وابستگی‌هایی که قرار است در یک کلاس استفاده شوند نیز جهت کاهش مصرف حافظه دقت داشت.
ب) یکی از مزایای دیگر تزریق وابستگی‌ها، ساده‌تر شدن نوشتن آزمون‌های واحد است. زیرا تهیه Mocks در این حالت کار با اینترفیس‌ها بسیار ساده‌تر است. اما باید دقت داشت، کلاسی که در سازنده آن حداقل 10 اینترفیس را به عنوان وابستگی دریافت می‌کند، احتمالا دچار مشکلاتی در طراحی و همچنین مصرف حافظه می‌باشد.
 
نظرات مطالب
نمایش تاریخ شمسی توسط JavaScript در AngularJS
با تشکر از مقاله خوبتون. خیلی کار را ساده میکنه فیلتری که نوشتید.
فقط من در هنگام استفاده از moment-jalali مشکلی برام به وجود اومد که برطرفش کردم. حس کردم شاید مشکل دوستان دیگر هم باشه. مشکل به این صورت بود که با اینکه من تمام کارهایی که در مقاله ذکر شده بود را انجام دادم، برای نمایش تاریخ فارسی میشد اما باز هم مینوشت x days ago به جای نمایش فارسی چند روز قبل. در مورد ماه و سال هم به همین صورت بود. من در خود وب سایت momentJs مطالعه کردم و متوجه شدم این کتابخانه درون خودش یک سری فایل برای زبان‌های گوناگون داره. بنده فایل اسکریپت زبان فارسی به همراه یک تکه کد که زبان فارسی را به عنوان زبان پیش فرض قرار میداد را اضافه کردم و مشکل حل شد.
    <!-- BEGIN MOMENT -->
    <script src="/bower_components/moment/moment.js"></script>
    <script src="/bower_components/moment/locale/fa.js"></script>
    <script src="/bower_components/moment-jalaali/build/moment-jalaali.js"></script>
    <script>
        moment.locale('fa');  // Set the default/global locale
        // ...
    </script>
    <!-- END MOMENT -->
و یک کار دیگه اینکه میشه این فیلتر را به هر صورتی خودتون ترجیح میدید Customize کنید. من از این فیلتر چندین نسخه تهییه کردم. به عنوان مثال یک فیلتر برای نمایش مدت زمان گذشته (مثلا چند ماه قبل)، یک فیلتر برای نمایش تاریخ به تنهایی و ...
یک نمونه از این پیاده سازی را آوردم امیدوارم مفید باشه.
app.filter('jalaliDateFromNow', function () {
    return function (inputDate) {
        var date = moment(inputDate);
        return date.fromNow();
    }
});