مطالب
آپلود فایل توسط فرم‌های پویای jqGrid
پیشنیازها
Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
فرمت کردن اطلاعات نمایش داده شده به کمک jqGrid در ASP.NET MVC
استفاده ازExpressionها جهت ایجاد Strongly typed view در ASP.NET MVC


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


تغییرات فایل Layout برنامه

در اینجا دو فایل جدید ajaxfileupload.js و jquery.blockUI.js به مجموعه‌ی فایل‌های تعریف شده اضافه شده‌اند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>

    <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.7.2.min.js"></script>
    <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
    <script src="~/Scripts/i18n/grid.locale-fa.js"></script>
    <script src="~/Scripts/jquery.jqGrid.src.js"></script>
    <script src="~/Scripts/ajaxfileupload.js"></script>
    <script src="~/Scripts/jquery.blockUI.js"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>
از فایل jquery.blockUI.js برای نمایش صفحه‌ی منتظر بمانید تا فایل آپلود شود، استفاده خواهیم کرد.
 PM> Install-Package jQuery.BlockUI



نکته‌ای در مورد واکنشگرا کردن jqGrid

اگر می‌خواهید عرض jqGrid به تغییرات اندازه‌ی مرورگر پاسخ دهد، تنها کافی است تغییرات ذیل را اعمال کنید:
<div dir="rtl" id="grid1" style="width:100%;" align="center">
    <div id="rsperror"></div>
    <table id="list" cellpadding="0" cellspacing="0"></table>
    <div id="pager" style="text-align:center;"></div>
</div>


    <script type="text/javascript">
        $(document).ready(function () {

            // Responsive jqGrid
            $(window).bind('resize', function () {
                var targetContainer = "#grid1";
                var targetGrid = "#list";

                $(targetGrid).setGridWidth($(targetContainer).width() - 2, true);
            }).trigger('resize');


            $('#list').jqGrid({
                caption: "آزمایش هفتم",
                /// .....
            }).navGrid(
                /// .....
            ).jqGrid('gridResize', { minWidth: 400, minHeight: 150 });
        });
    </script>
در اینجا به تغییرات resize صفحه گوش فرا داده شده و سپس به کمک متد توکار setGridWidth، به صورت پویا اندازه‌ی عرض jqGrid تغییر خواهد کرد.
همچنین اگر می‌خواهید کاربر بتواند اندازه‌ی گرید را دستی تغییر دهد، به انتهای تعاریف گرید، تعریف متد gridResize را نیز اضافه کنید.




نحوه‌ی تعریف ستونی که قرار است فایل آپلود کند

                colModel: [
                    {
                        name: '@(StronglyTyped.PropertyName<Product>(x=>x.ImageName))',
                        index: '@(StronglyTyped.PropertyName<Product>(x => x.ImageName))',
                        align: 'center', width: 220,
                        editable: true,
                        edittype: 'file',
                        formatter: function (cellvalue, options, rowObject) {
                            return "<img src='/images/" + cellvalue + "?rnd=" + new Date().getTime() + "' />";
                        },
                        unformat: function (cellvalue, options, cell) {
                            return $('img', cell).attr('src').replace('/images/', '');
                        }
                    }
                ],
edittype ستونی که قرار است فایل آپلود کند، باید به file تنظیم شود. همچنین چون در اینجا این فایل آپلودی، تصویر یک محصول است، از formatter برای تبدیل مسیر فایل به تصویر و از unformat برای بازگشت این مسیر به مقدر اصلی آن استفاده خواهیم کرد. از unformat برای حالت ویرایش اطلاعات استفاده می‌شود. از formatter برای تغییر اطلاعات دریافتی از سرور به فرمت دلخواهی در سمت کلاینت می‌توان کمک گرفت.
Rnd اضافه شده به انتهای آدرس تصویر، جهت جلوگیری از کش شدن آن تعریف شده‌است.



کتابخانه‌ی JqGridHelper

در قسمت‌های قبل مطالب بررسی jqGrid یک سری کلاس مانند JqGridData برای بازگشت اطلاعات مخصوص jqGrid و یا JqGridRequest برای دریافت پارامترهای ارسالی توسط آن به سرور، تهیه کردیم؛ به همراه کلاس‌هایی مانند جستجو و مرتب سازی پویای اطلاعات.
اگر این کلاس‌ها را از پروژه‌ها و مثال‌های ارائه شده خارج کنیم، می‌توان به کتابخانه‌ی JqGridHelper رسید که فایل‌های آن در پروژه‌ی پیوست موجود هستند.
همچنین در این پروژه، کلاسی به نام StronglyTyped با متد PropertyName جهت دریافت نام رشته‌ای یک خاصیت تعریف شده‌است. گاهی از اوقات این تنها چیزی است که کدهای سمت کلاینت، جهت سازگار شدن با Refactoring و Strongly typed تعریف شدن نیاز دارند و نه ... محصور کننده‌هایی طویل و عریض که هیچگاه نمی‌توانند تمام قابلیت‌های یک کتابخانه‌ی غنی جاوا اسکریپتی را به همراه داشته باشند.
با کمی جستجو، برای jqGrid نیز می‌توانید از این دست محصور کننده‌هارا پیدا کنید اما ... هیچکدام کامل نیستند و دست آخر مجبور خواهید شد در بسیاری از موارد مستقیما JavaScript نویسی کنید.



یکپارچه سازی افزونه‌ی AjaxFileUpload با فرم‌های jqGrid

پس از این مقدمات، ستون ویژه‌ی actions که inline edit را فعال می‌کند، چنین تعریفی را پیدا خواهد کرد:
                colModel: [
                    {
                        name: 'myac', width: 80, fixed: true, sortable: false,
                        resize: false, formatter: 'actions',
                        formatoptions: {
                            keys: true,
                            afterSave: function (rowid, response) {
                                doInlineUpload(response, rowid);
                            },
                            delbutton: true,
                            delOptions: {
                                url: "@Url.Action("DeleteProduct","Home")"
                            }
                        }
                    }
                ],
در اینجا afterSave اضافه شده‌است تا کار ارسال فایل به سرور را در حالت ویرایش inline فعال کند.
و ویژگی‌های قسمت‌های edit، add و delete فرم‌های پویای jqGrid باید به نحو ذیل تغییر کنند:
            $('#list').jqGrid({
                caption: "آزمایش هفتم",
                // ....
            }).navGrid(
                '#pager',
                //enabling buttons
                { add: true, del: true, edit: true, search: false },
                //edit option
                {
                    width: 'auto',
                    reloadAfterSubmit: true, checkOnUpdate: true, checkOnSubmit: true,
                    beforeShowForm: function (form) {
                        centerDialog(form, $('#list'));
                    },
                    afterSubmit: doFormUpload,
                    closeAfterEdit: true
                },
                //add options
                {
                    width: 'auto', url: '@Url.Action("AddProduct","Home")',
                    reloadAfterSubmit: true, checkOnUpdate: true, checkOnSubmit: true,
                    beforeShowForm: function (form) {
                        centerDialog(form, $('#list'));
                    },
                    afterSubmit: doFormUpload,
                    closeAfterAdd: true
                },
                //delete options
                {
                    url: '@Url.Action("DeleteProduct","Home")',
                    reloadAfterSubmit: true
                }).jqGrid('gridResize', { minWidth: 400, minHeight: 150 });
با اکثر این تنظیمات در مطلب «فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC» آشنا شده‌اید. تنها قسمت جدید آن شامل رویدادگردان afterSubmit است. در اینجا است که افزونه‌ی AjaxFileUpload فعال شده و سپس اطلاعات المان فایل را به سرور ارسال می‌کند.
افزونه‌ی AjaxFileUpload پس از ارسال اطلاعات عناصر غیر فایلی فرم، باید فعال شود. به همین جهت است که از رویداد afterSubmit در حالت نمایش فرم‌های پویا و رویداد afterSave در حالت ویرایش inline استفاده کرده‌ایم.
در ادامه تعاریف متدهای doInlineUpload و doUpload بکار گرفته شده در رویداد afterSubmit را مشاهده می‌کنید:
        function doInlineUpload(response, rowId) {
            return doUpload(response, null, rowId);
        }

        function doFormUpload(response, postdata) {
            return doUpload(response, postdata, null);
        }

        function doUpload(response, postdata, rowId) {
            // دریافت خروجی متد ثبت اطلاعات از سرور
            // و استفاده از آی دی رکورد ثبت شده برای انتساب فایل آپلودی به آن رکورد
            var result = $.parseJSON(response.responseText);
            if (result.success === false)
                return [false, "عملیات ثبت موفقیت آمیز نبود", result.id];

            var fileElementId = '@(StronglyTyped.PropertyName<Product>(x=>x.ImageName))';
            if (rowId) {
                fileElementId = rowId + "_" + fileElementId;
            }

            var val = $("#" + fileElementId).val();
            if (val == '' || val === undefined) {
                // فایلی انتخاب نشده
                return [false, "لطفا فایلی را انتخاب کنید", result.id];
            }

            $('#grid1').block({ message: '<h4>در حال ارسال فایل به سرور</h4>' });
            $.ajaxFileUpload({
                url: "@Url.Action("UploadFiles", "Home")", // مسیری که باید فایل به آن ارسال شود
                secureuri: false,
                fileElementId: fileElementId, // آی دی المان ورودی فایل
                dataType: 'json',
                data: { id: result.id }, // اطلاعات اضافی در صورت نیاز
                complete: function () {
                    $('#grid1').unblock();
                },
                success: function (data, status) {
                    $("#list").trigger("reloadGrid");
                },
                error: function (data, status, e) {
                    alert(e);
                }
            });

            return [true, "با تشکر!", result.id];
        }
امضای رویدادگردان‌های afterSubmit و afterSave یکی نیست. به همین جهت دو متد اضافی به جای یک متد doUpload مورد استفاده قرار گرفته‌اند.
متد doUpload توسط پارامتر response، اطلاعات بازگشتی پس از ذخیره سازی متداول اطلاعات فرم را دریافت می‌کند. برای مثال ابتدا اطلاعات معمولی یک محصول در بانک اطلاعاتی ذخیره شده و سپس id آن به همراه یک خاصیت به نام success از طرف سرور بازگشت داده می‌شوند.
اگر success مساوی true بود، ادامه‌ی کار آپلود فایل انجام خواهد شد. در اینجا ابتدا بررسی می‌شود که آیا فایلی از طرف کاربر انتخاب شده‌است یا خیر؟ اگر خیر، یک پیام اعتبارسنجی سفارشی به او نمایش داده خواهد شد.
خروجی متد doUpload حتما باید به شکل یک آرایه سه عضوی باشد. عضو اول آن true و false است؛ به معنای موفقیت یا عدم موفقیت عملیات. عضو دوم پیام اعتبارسنجی سفارشی است و عضو سوم، Id ردیف.
در ادامه افزونه‌ی jQuery.BlockUI فعال می‌شود تا ارسال فایل به سرور را به کاربر گوشزد کند.
سپس فراخوانی متداول افزونه‌ی ajaxFileUpload را مشاهده می‌کنید. تنها نکته‌ی مهم آن فراخوانی متد reloadGrid در حالت success است. به این ترتیب گرید را وادار می‌کنیم تا اطلاعات ذخیره شده در سمت سرور را دریافت کرده و سپس تصویر را به نحو صحیحی نمایش دهد.



کدهای سمت سرور آپلود فایل

        [HttpPost]
        public ActionResult AddProduct(Product postData)
        {
            // ...
            return Json(new { id = postData.Id, success = true }, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult EditProduct(Product postData)
        {
            // ...
            return Json(new { id = postData.Id, success = true }, JsonRequestBehavior.AllowGet);
        }


        // todo: change `imageName` according to the form's file element name
        [HttpPost]
        public ActionResult UploadFiles(HttpPostedFileBase imageName, int id)
        {
            // ....
            return Json(new { FileName = product.ImageName }, "text/html", JsonRequestBehavior.AllowGet);
        }
در اینجا تنها نکته‌ی مهم، خروجی‌های JSON این متدها هستند.
در حالت‌های Add و Edit، نیاز است id رکورد ثبت شده بازگشت داده شود. این id در سمت کلاینت توسط پارامتر response دریافت می‌شود. از آن در افزونه‌ی ارسال فایل به سرور استفاده خواهیم کرد. اگر به متد UploadFiles دقت کنید، این id را دریافت می‌کند. بنابراین می‌توان یک ربط منطقی را بین فایل ارسالی و رکورد متناظر با آن برقرار کرد.
Content type مقدار بازگشتی از متد UploadFiles حتما باید text/html باشد (افزونه‌ی ارسال فایل‌ها، اینگونه کار می‌کند).


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
jqGrid07.zip
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 2 - ثبت اولین سرویس
یک پروژه‌ی ASP.NET Core را با قرار دادن نسخه‌ی NET Core. بر روی 3.1 و با استفاده از قالب Model View Controller ایجاد کنید. در اینجا نام پروژه را AspNetCoreDependencyInjection گذاشته‌ام. حالا در  پوشه‌ی Models، فایلی را با نام HomeViewModel.cs با محتویاتی به صورت زیر اضافه کنید:
public class HomeViewModel
{
     public string Id { get; set; }
     public string Message { get; set; }
     public DateTime DateTime { get; set; }
}

اکنون به پوشه‌ی Views بروید و فایل Index.cshtml را به این صورت تغییر دهید:

@model AspNetCoreDependencyInjection.Models.HomeViewModel
@{
ViewData["Title"] = "Home";
}

<div>
<div>
<div>
<p>
<b>Id : </b><span>@Model.Id</span> <br />
<b>Date And Time : </b><span> @Model.DateTime </span> <br/>
<b>Message : </b><span>@Model.Message</span>
</p>
</div>
</div>
</div>
و فایل MessageServiceA.cs را به پروژه اضافه کنید:
using AspNetCoreDependencyInjection.Services;

namespace AspNetCoreDependencyInjection.ServicesImplentaions
{
    public class MessageServiceAA 
    {
        public string Message()
        {
            return "A message from MessageServiceAA";
        }
    }
}
و همچنین فایل GuidHelper.cs را نیز اضافه می‌کنیم:
namespace AspNetCoreDependencyInjection.Helpers
{
    public class GuidProvider
    {
        private readonly Guid _serviceGuid;

        public GuidProvider()
        {
            _serviceGuid = Guid.NewGuid();
        }

        public Guid GetNewGuid() => Guid.NewGuid();

        public string GetGuidAsFormatedString(string prefix = "") => getFormatedGuid(_serviceGuid, prefix);

        private string getFormatedGuid(Guid guid, string prefix = "")
        {
            var guidString = guid.GetHashCode().ToString("x");
            if (string.IsNullOrEmpty(prefix) == false)
                guidString = new StringBuilder($"{prefix}-").Append(guidString).ToString();
            return guidString;
        }
    }
}

حالا درون کنترل HomeController، این تغییرات را انجام می‌دهیم:

private readonly ILogger<HomeController> _logger;
private readonly MessageServiceAA _messageService;
private readonly GuidProvider _ guidProvider;

public HomeController(ILogger<HomeController> logger)
{
            _logger = logger;
            _messageService = new MessageServiceAA();
            _guidProvider = new GuidProvider();
}

public IActionResult Index()
{
            var model = new HomeViewModel()
            {
                Id = _ guidProvider.GetGuidAsFormatedString(),
                Message = _messageService.Message(),
                DateTime = DateTime.Now,
            };
            return View(model);
}

همانطور که می‌بینید، در کد بالا، کنترلر HomeController، به دو شیء از کلاس‌ها و یا سرویس‌های GuidProvider و MessageServiceAA به صورت مستقیم وابسته شده‌است و با هر تغییری در هر کدام از این سرویسها، باید دوباره کامپایل شود. علاوه بر این اگر بخواهیم پیاده سازی‌های مختلفی را برای هر کدام از این موارد، ارائه دهیم، به مشکل بر می‌خوریم. خب بیاید تغییراتی را در کد بالا بدهیم تا مشکلات ذکر شده را حل کنیم.

برای این منظور پوشه‌ای را به نام Services می‌سازیم و اینترفیسی را به نام IMessageBrokerA ایجاد می‌کنیم و سپس کاری می‌کنیم که MessageServiceAA از این اینترفیس ارث بری کند:

namespace AspNetCoreDependencyInjection.Services
{
    public interface IMessageServiceA
    {
        string Message();
    }
}

و حالا می‌خواهیم با استفاده از تزریق وابستگی، وابستگی کنترلر HomeController را از کلاس MessageBrokerAA لغو کرده و آن را به اینترفیس IMessageBrokerA (انتزاع) وابسته کنیم. در اینجا ما از تکنیک تزریق درون سازنده یا Constructor Injection استفاده می‌کنیم.


تزریق درون سازنده

در این تکنیک، ما لیستی از وابستگی‌های مورد نیاز را به عنوان پارامترهای ورودی سازنده‌ی کلاس، تعریف می‌کنیم:
private readonly ILogger<HomeController> _logger;
private readonly IMessageServiceA _messageService;
private readonly GuidProvider _guidHelper;
public HomeController(ILogger<HomeController> logger , IMessageServiceA messageService)
{
        _logger = logger;
        _messageService = messageService;
        _messageService = new MessageServiceAA();
        _guidHelper = new GuidProvider();
}
و حالا اگر برنامه را اجرا کنیم، با خطایی روبه رو می‌شویم که در آن می‌گوید امکان واکشی (Resolve) سرویس‌های مورد نظر وجود ندارد. این خطا به دلیل ثبت نشدن اینترفیس IMessageServiceA و پیاده سازی آن، درون Microsoft Dependency Injection Container است   DI Container‌ها معمولا باید در زمان شروع برنامه، پیکربندی و مقدار دهی شودند، تا در ادامه‌ی چرخه‌ی حیات برنامه، بتوانند سرویس‌ها و اشیاء مورد نیاز را به کلاس‌هایی که نیاز دارند، واکشی و تزریق کنند. اولین مرحله از کار با DI Container‌ها، ثبت کردن سرویس‌ها درون آنهاست.
در ASP.NET Core از IServiceCollection برای ثبت کردن سرویس‌های برنامه‌ی خودمان استفاده می‌کنیم. تمامی سرویس‌هایی را که انتظار داریم توسط DI Container به کلاس‌هایی تزریق شوند، باید درون IServiceCollection ثبت گردند. تمام سرویس‌هایی که به وسیله‌ی IServiceCollection ثبت شده‌اند، پس از ساخته شدن، توسط اینترفیس IServiceProvider قابل واکشی هستند.

بنابراین دو اینترفیس حیاتی برای کار کرد صحیح Microsoft Dependency Injection Container درون ASP.NET Core وجود دارند:
  • IServiceCollection : برای ثبت سرویس‌ها
  • IServiceProvider : برای واکشی سرویس‌ها

در ASP.NET Core معمول‌ترین مکان برای ثبت کردن سرویس‌ها درون Container، به صورت پیش فرض درون کلاس Startup و درون متد ConfigureServices انجام می‌گیرد.
به صورت پیش فرض کلاس Startup دو متد دارد:
  • ConfigureServices : برای پیکربندی و ثبت سرویس‌های درونی DI Container استفاده می‌شود.
  • Configure : برای تنظیمات pipeline میان افزارها ( Middlewares ) بکار می‌رود.

در اینجا پیاده سازی پیش فرض کلاس Startup را می‌بینیم که البته کدهای درون متد Configure را برای درگیر نکردن ذهن شما، مخفی کرده‌ایم: 
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //  کدها جهت خوانایی بیشتر مخفی شده اند
        }
    }

همانطورکه می‌بینید، متد ConfigureService پارامتر IServiceCollection را می‌گیرد که به وسیله‌ی WebHost در زمان اجرای برنامه، مقدار دهی می‌شود.

تعداد زیادی Extension method برای IServiceCollection وجود دارند که برای پشتیبانی از ثبت کردن سرویس‌های مختلف در سناریوهای گوناگون به کار می‌روند. در اینجا ما از نسخه‌ی 3.1 چارچوب ASP.NET Core استفاده می‌کنیم. برای همین هم برای ثبت سرویس‌های پیش فرض فریمورک MVC از متد توسعه‌ی services.AddControllersWithViews()    استفاده می‌کنیم.  متد توسعه‌ی AddControllersWithViews() سرویس‌هایی را که معمولا در فریم ورک MVC استفاده می‌شوند، درون IServiceCollection ثبت می‌کند. در نسخه‌های قبلی چارچوب ASP.NET Core،  مانند نسخه‌های 2.1 و 2.2 برای این کار از متد توسعه‌ی AddMvc() استفاده می‌شد.

در Microsoft Dependency Injection Container ، معمولا  ترتیب ثبت سرویس‌ها مهم نیست.

خب، اولین سرویس اختصاصی برنامه‌ی خودمان را با چرخه‌ی حیات Transient و زیر سرویس پیشین، به شکل زیر ثبت می‌کنیم :

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddTransient<IMessageServiceA, MessageServiceAA>();
        }
همانطور که می‌بینید، در اینجا ما از متد AddTransient() استفاده کرده‌ایم. متد AddTransient() درون فضای نام Microsoft.Extensions.DependencyInjection قرار دارد. این متد Overload ‌های گوناگونی دارد و ما از نوعی از آن استفاده کرده‌ایم که دو نوع generic را می‌پذیرد و تعریف آن به صورت زیر است: 
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)
در اینجا TService ، اینترفیس سرویس ماست. این نوع، همان نوعی است که کلاس‌های ما می‌توانند به آن وابسته باشند. پارامتر دوم، از نوع TImplemention است که پیاده سازی مورد نظر برای TService را ثبت می‌کند. TImplmention   نوعی است که Container در زمان واکشی و تزریق TService از آن نمونه سازی کرده و به کلاس مورد نظر تزریق می‌کند.

در اینجا وقتی ما برای IMessageServiceA ، پیاده سازی MessageServiceA را ثبت می‌کنیم، از این به بعد DI Container، هر زمانیکه در لیست پارامترهای سازنده‌ی یک کلاس، IMessageServiceA را مشاهده کند، بررسی می‌کند که چه کلاسی به عنوانی پیاده سازی این اینترفیس ثبت شده‌است، سپس از آن نمونه سازی می‌کند و درون سازنده‌ی مورد نظر تزریق می‌کند. خب، حالا برنامه را دوباره اجرا کنید؛ می‌بینید که برنامه اجرا می‌شود.

 
در ادامه ابتدا در مورد روش‌های مختلف ثبت سرویس‌ها و بعد روش‌های واکشی سرویس‌ها را بررسی می‌کنیم.
مطالب
آموزش MEF#1
Managed Extensibility Framework یا MEF کامپوننتی از Framework 4 است که برای ایجاد برنامه‌های توسعه پذیر (Extensible) با حجم کم کد استفاده میشه.این تکنولوژی به برنامه نویسان این امکان رو میده که توسعه‌های (Extension) برنامه رو بدون پیکربندی استفاده کنند. همچنین به توسعه دهندگان این اجازه رو می‌ده که به آسانی کدها رو کپسوله کنند .
MEF به عنوان بخشی از 4 NET. و Silverlight 4 معرفی شد. MEF یک راه حل ساده برای مشکل توسعه در حال اجرای برنامه‌ها ارائه می‌کند.تا قبل از این تکنولوژی ، هر برنامه‌ای که می‌خواست یک مدل Plugin را پشتیبانی کنه لازم بود که خودش زیر ساخت‌ها را از ابتدا ایجاد کنه . این Plugin‌ها اغلب برای برنامه‌های خاصی بودند و نمی‌توانستند در پیاده سازی‌های چندگانه دوباره استفاده شوند. ولی MEF در راستای حل این مشکلات ، روش استانداردی رو برای میزبانی برنامه‌های کاربردی پیاده کرده است. 
برای فهم بهتر مفاهیم یک مثال ساده رو با MEF پیاده سازی می‌کنم.
ابتدا یک پروژه از نوع Console Application ایجاد کنید . بعد با استفاده از Add Reference یک ارجاع به System.ComponentModel.Composition بدید. سپس یک Interface به نام IViewModel را به صورت زیر ایجاد کنید:
public interface IViewModel
    {
        string Name { get; set; }
    }

یک خاصیت به نام Name برای دسترسی به نام ViewModel ایجاد می‌کنیم.
سپس 2 تا ViewModel دیگه ایجاد می‌کنیم که IViewModel را پیاده سازی کنند. به صورت زیر:
ViewModelFirst:
[Export( typeof( IViewModel ) )]
    public class ViewModelFirst : IViewModel
    {
        public ViewModelFirst()
        {
            this.Name = "ViewModelFirst";
        }

        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        private string _name;
    }


ViewModelSecond:
[Export( typeof( IViewModel ) )]
    public class ViewModelSecond : IViewModel
    {
        public ViewModelSecond()
        {
            this.Name = "ViewModelSecond";
        }

        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        private string _name;
    }


Export Attribute استفاده شده در بالای کلاس‌های ViewModel به این معنی است که این کلاس‌ها اینترفیس IViewModel رو Export کردند تا در جای مناسب بتونیم این ViewModel ‌ها Import کنیم.(Import , Export از مفاهیم اصلی در MEF هستند)
حالا نوبت به پیاده سازی کلاس Plugin می‌رسه.
public class PluginManager
    {
        public PluginManager()
        {

        }

        public IList<IViewModel> ViewModels
        {
            get
            {
                return _viewModels;
            }
            private set
            {
                _viewModels = value;
            }
        }

        [ImportMany( typeof( IViewModel ) )]
        private IList<IViewModel> _viewModels = new List<IViewModel>();

        public void SetupManager()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();

            CompositionContainer container = new CompositionContainer( aggregateCatalog );

            CompositionBatch batch = new CompositionBatch();

            batch.AddPart( this );

            aggregateCatalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) );           

            container.Compose( batch );
        }

کلاس PluginManager برای شناسایی و استفاده از کلاس هایی که صفت‌های Export رو دارند نوشته شده(دقیقا شبیه یک UnityContainer در Microsoft Unity Application Block یا IKernel در Ninject) عمل می‌کنه با این تفاوت که نیازی به Register با Bind کردن ندارند)
ابتدا بک لیست از کلاس هایی که IViewModel رو Export کردند داریم.
بعد در متد SetupManager ابتدا یک AggregateCatalog نیاز داریم تا بتونیم Composition Part‌ها رو بهش اضافه کنیم. به کد زیر توجه کنید:
 aggregateCatalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) );

تو این قطعه کد من یک Assembly Catalog رو که به Assembly جاری برنامه اشاره می‌کنه به AggregateCatalog اضافه کردم.
متد (batch.AddPart(this در واقع به این معنی است که به MEF گفته می‌شود این کلاس ممکن است شامل Export هایی باشد که به یک یا چند Import وابستگی دارند.
متد (AddExport(this در CompositionBatch به این معنی است که این کلاس ممکن است شامل Exportهایی باشد که به Import وابستگی ندارند.
حالا برای مشاهده نتایج کد زیر را در کلاس Program اضافه می‌کنیم:
static void Main( string[] args )
        {
            PluginManager plugin = new PluginManager();

            Console.WriteLine( string.Format( "Number Of ViewModels Before Plugin Setup Is [ {0} ]", plugin.ViewModels.Count ) );

            Console.WriteLine( Environment.NewLine );

            plugin.SetupManager();

            Console.WriteLine( string.Format( "Number Of ViewModels After Plugin Setup Is [ {0} ]", plugin.ViewModels.Count ) );

            Console.ReadLine();
        }

در کلاس بالا ابتدا تعداد کلاس‌های موجود در لیست ViewModels رو قبل از Setup کردن Plugin نمایش داده سپس بعد از Setup  کردن Plugin  دوباره تعداد کلاس‌های موجود در لیست ViewModel رو مشاهده می‌کنیم.که خروجی به شکل زیر تولید خواهد شد.


متد SetupManager در کلاس Plugin (با توجه به AggregateCatalog) که در این برنامه فقط Assembly  جاری رو بهش اضافه کردیم تمام کلاس هایی رو که نوع IViewModel رو Export کردند پیدا کرده و در لیست اضافه می‌کنه(این کار رو با توجه به ImportMany Attribute) انجام میده. در پست‌های بعدی روش استفاده از MEF رو در Prism یا WAF توضیح می‌دم.
مطالب
INPC استاندارد با بهره گیری از صفت CallerMemberName
یکی از Attribute‌های بسیار کاربردی که در سی شارپ 5 اضافه شد CallerMemberNameAttribute بود. این صفت به یک متد اجازه میدهد که از فراخواننده‌ی خود مطلع شود. این صفت را می‌توان بر روی یک پارامتر انتخابی که مقدار پیش‌فرضی دارد اعمال نمود.

استفاده از این صفت هم بسیار ساده است:

private void A ( [CallerMemberName] string callerName = "") 
{
  Console.WriteLine("Caller is " + callerName);
}

private static void B()
{
        // let's call A
        A();
}
در کد فوق، متد A به راحتی می‌تواند بفهمد چه کسی آن را فراخوانی کرده است. از جمله کاربردهای این صفت در ردیابی و خطایابی است.

ولی یک استفاده‌ی بسیار کاربردی از این صفت، در پیاده سازی رابط INotifyPropertyChanged می‌باشد.

معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را می‌نویسیم:

    public class PersonViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                this.name = value;
                OnPropertyChanged("Name");
            }
        }
    }

یعنی در Setter معمولا نام ویژگی ای را که تغییر کرده است، به متد OnPropertyChanged می‌فرستیم تا اطلاع رسانی‌های لازم انجام پذیرد. تا اینجای کار همه چیز خوب و آرام است. اما به محضی که کد شما کمی طولانی شود و شما به دلایلی نیاز به Refactor کردن کد و احیانا تغییر نام ویژگی‌ها را پیدا کنید، آن موقع مسائل جدیدی بروز پیدا می‌کند.

برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم می‌گیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که می‌خواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام می‌دهید. همانند کد زیر:

private string firstName;
public string FirstName
{
            get { return firstName; }
            set
            {
                this.firstName = value;
                OnPropertyChanged("Name");
            }
}

برنامه را کامپایل کرده و در کمال تعجب می‌بینید که بخشی از برنامه درست رفتار نمی‌کند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد می‌شود به درستی بروزرسانی نمی‌شوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینه‌ی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کرده‌ایم. پس جای تعجبی ندارد که در هر جای کد که رشته‌ای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش می‌کنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.

حال احتمال بروز این خطا را در ViewModel هایی با ده‌ها ویژگی و ترکیب‌های مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.

و اما راه حل چیست؟ به کارگیری صفت CallerMemberName

بهتر است که یک کلاس انتزاعی برای تمام ViewModel‌های خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VM‌های ما از آن مشتق شوند:

public abstract class ViewModelBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            OnPropertyChangedExplicit(propertyName);
        }

        protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
        {
            var memberExpression = (MemberExpression)projection.Body;
            OnPropertyChangedExplicit(memberExpression.Member.Name);
        }

        void OnPropertyChangedExplicit(string propertyName)
        {
            this.CheckPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;

            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #region Check property name

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void CheckPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new Exception(String.Format("Could not find property \"{0}\"", propertyName));
        }

        #endregion // Check property name
}

در این کلاس، ما پارامتر propertyName را از متد OnPropertyChanged، توسط صفت CallerMemberName حاشیه نویسی کرده‌ایم. این کار باعث می‌شود در Setter‌های ویژگی‌ها، به راحتی بدون نوشتن نام ویژگی، عملیات اطلاع رسانی تغییرات را انجام دهیم. بدین صورت که کافیست متد OnPropertyChanged بدون هیچ آرگومانی در Setter فراخوانی شود و صفت CallerMemberName به صورت اتوماتیک نام ویژگی ای که فراخوانی از درون آن انجام شده است را درون پارامتر propertyName قرار می‌دهد.

پس کلاس PersonViewModel را به صورت زیر می‌توانیم اصلاح و تکمیل کنیم:

public class PersonViewModel : ViewModelBase
{
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                this.firstName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                this.lastName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        public string FullName
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
}
همانطور که می‌بینید متد OnPropertyChanged بدون آرگومان فراخوانی میشود. اکنون اگر شما اقدام به Refactor کردن کد خود بکنید دیگر نگرانی از بابت تغییر نکردن رشته‌ها و کامنت‌ها نخواهید داشت و مطمئن هستید، نام ویژگی هر چیزی که باشد، به صورت خودکار به متد ارسال خواهد شد.

کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه می‌دهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق می‌بینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیله‌ی یک Func به راحتی بیان می‌کنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.

برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.

ارجاع:
Raise INPC witout string name
مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


به همین جهت اگر می‌خواهید رزومه‌ی غنی‌تری را ارائه دهید، فراگیری React می‌تواند موقعیت‌های شغلی بیشتری را نصیب شما کند.


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

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


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

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

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-01.zip

در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی می‌کنیم.
مطالب دوره‌ها
الگوی معکوس سازی کنترل چیست؟
معکوس سازی کنترل (Inversion of Control) الگویی است که نحوه پیاده سازی اصل معکوس سازی وابستگی‌ها (Dependency inversion principle) را بیان می‌کند. با معکوس سازی کنترل، کنترل چیزی را با تغییر کنترل کننده، معکوس می‌کنیم. برای نمونه کلاسی را داریم که ایجاد اشیاء را کنترل می‌کند؛ با معکوس سازی آن به کلاسی یا قسمتی دیگر از سیستم، این مسئولیت را واگذار خواهیم کرد.
IoC یک الگوی سطح بالا است و به روش‌های مختلفی به مسایل متفاوتی جهت معکوس سازی کنترل، قابل اعمال می‌باشد؛ مانند:
- کنترل اینترفیس‌های بین دو سیستم
- کنترل جریان کاری برنامه
- کنترل بر روی ایجاد وابستگی‌ها (جایی که تزریق وابستگی‌ها و DI ظاهر می‌شوند)


سؤال: بین IoC و DIP چه تفاوتی وجود دارد؟

در DIP (قسمت قبل) به این نتیجه رسیدیم که یک ماژول سطح بالاتر نباید به جزئیات پیاده سازی‌های ماژولی سطح پایین‌تر وابسته باشد. هر دوی این‌ها باید بر اساس Abstraction با یکدیگر ارتباط برقرار کنند. IoC روشی است که این Abstraction را فراهم می‌کند. در DIP فقط نگران این هستیم که ماژول‌های موجود در لایه‌های مختلف برنامه به یکدیگر وابسته نباشند اما بیان نکردیم که چگونه.


معکوس سازی اینترفیس‌ها

هدف از معکوس سازی اینترفیس‌ها، استفاده صحیح و معنا دار از اینترفیس‌ها می‌باشد. به این معنا که صرفا تعریف اینترفیس‌ها به این معنا نیست که طراحی صحیحی در برنامه بکار گرفته شده است و در حالت کلی هیچ معنای خاصی ندارد و ارزشی را به برنامه و سیستم شما اضافه نخواهد کرد.
برای مثال یک مسابقه بوکس را درنظر بگیرید. در اینجا Ali یک بوکسور است. مطابق عادت معمول، یک اینترفیس را مخصوص این کلاس ایجاد کرده، به نام IAli و مسابقه بوکس از آن استفاده خواهد کرد. در اینجا تعریف یک اینترفیس برای Ali، هیچ ارزش افزوده‌ای را به همراه ندارد و متاسفانه عادتی است که در بین بسیاری از برنامه نویس‌ها متداول شده است؛ بدون اینکه علت واقعی آن‌را بدانند و تسلطی به الگوهای طراحی برنامه نویسی شیءگرا داشته باشند. صرف اینکه به آن‌ها گفته شده است تعریف اینترفیس خوب است، سعی می‌کنند برای همه چیز اینترفیس تعریف کنند!
تعریف یک اینترفیس تنها زمانی ارزش خواهد داشت که چندین پیاده سازی از آن ارائه شود. در مثال ما پیاده سازی‌های مختلفی از اینترفیس IAli بی‌مفهوم است. همچنین در دنیای واقعی، در یک مسابقه بوکس، چندین و چند شرکت کننده وجود خواهند داشت. آیا باید به ازای هر کدام یک اینترفیس جداگانه تعریف کرد؟ ضمنا ممکن است اینترفیس IAli متدی داشته باشد به نام ضربه، اینترفیس IVahid متد دیگری داشته باشد به نام دفاع.
کاری که در اینجا جهت طراحی صحیح باید صورت گیرد، معکوس سازی اینترفیس‌ها است. به این ترتیب که مسابقه بوکس است که باید اینترفیس مورد نیاز خود را تعریف کند و آن هم تنها یک اینترفیس است به نام IBoxer. اکنون Ali، Vahid و سایرین باید این اینترفیس را جهت شرکت در مسابقه بوکس پیاده سازی کنند. بنابراین دیگر صرف وجود یک کلاس، اینترفیس مجزایی برای آن تعریف نشده و بر اساس معکوس سازی کنترل است که تعریف اینترفیس IBoxer معنا پیدا کرده است. اکنون IBoxer دارای چندین و چند پیاده سازی خواهد بود. به این ترتیب، تعریف اینترفیس، ارزشی را به سیستم افزوده است.
به این نوع معکوس سازی اینترفیس‌ها، الگوی provider model نیز گفته می‌شود. برای مثال کلاسی که از چندین سرویس استفاده می‌کند، بهتر است یک IService را ایجاد کرده و تامین کننده‌هایی، این IService را پیاده سازی کنند. نمونه‌ای از آن در دنیای دات نت، Membership Provider موجود در ASP.NET است که پیاده سازی‌های بسیاری از آن تاکنون تهیه و ارائه شده‌اند.




معکوس سازی جریان کاری برنامه

جریان کاری معمول یک برنامه یا Noraml flow، عموما رویه‌ای یا Procedural است؛ به این معنا که از یک مرحله به مرحله‌ای بعد هدایت خواهد شد. برای مثال یک برنامه خط فرمان را درنظر بگیرید که ابتدا می‌پرسد نام شما چیست؟ در مرحله بعد مثلا رنگ مورد علاقه شما را خواهد پرسید.
برای معکوس سازی این جریان کاری، از یک رابط کاربری گرافیکی یا GUI استفاده می‌شود. مثلا یک فرم را درنظر بگیرید که در آن دو جعبه متنی، کار دریافت نام و رنگ را به عهده دارند؛ به همراه یک دکمه ثبت اطلاعات. به این ترتیب بجای اینکه برنامه، مرحله به مرحله کاربر را جهت ثبت اطلاعات هدایت کند، کنترل به کاربر منتقل و معکوس شده است.


معکوس سازی تولید اشیاء

معکوس سازی تولید اشیاء، اصل بحث دوره و سری جاری را تشکیل می‌دهد و در ادامه مباحث، بیشتر و عمیق‌تر بررسی خواهد گردید.
روش متداول تعریف و استفاده از اشیاء دیگر درون یک کلاس، وهله سازی آن‌ها توسط کلمه کلیدی new است. به این ترتیب از یک وابستگی به صورت مستقیم درون کدهای کلاس استفاده خواهد شد. بنابراین در این حالت کلاس‌های سطح بالاتر به ماژول‌های سطح پایین، به صورت مستقیم وابسته می‌گردند.
برای اینکه این کنترل را معکوس کنیم، نیاز است ایجاد و وهله سازی این اشیاء وابستگی را در خارج از کلاس جاری انجام دهیم. شاید در اینجا بپرسید که چرا؟
اگر با الگوی طراحی شیءگرای Factory آشنا باشید، همان ایده در اینجا مدنظر است:
Button button;
switch (UserSettings.UserSkinType)
{
   case UserSkinTypes.Normal:
      button = new Button();
      break;
   case UserSkinTypes.Fancy:
      button = new FancyButton();
      break;
}
در این مثال بر اساس تنظیمات کاربر، قرار است شکل دکمه‌های نمایش داده شده در صفحه تغییر کنند.
حال در این برنامه اگر قرار باشد کار و کنترل محل وهله سازی این دکمه‌ها معکوس نشود، در هر قسمتی از برنامه نیاز است این سوئیچ تکرار گردد (برای مثال در چند ده فرم مختلف برنامه). بنابراین بهتر است محل ایجاد این دکمه‌ها به کلاس دیگری منتقل شود مانند ButtonFactory و سپس از این کلاس در مکان‌های مختلف برنامه استفاده گردد:
 Button button = ButtonFactory.CreateButton();
در این حالت علاوه بر کاهش کدهای تکراری، اگر حالت دکمه جدیدی نیز طراحی گردید، نیاز نخواهد بود تا بسیاری از نقاط برنامه بازنویسی شوند.
بنابراین در مثال فوق، کنترل ایجاد دکمه‌ها به یک کلاس پایه قرار گرفته در خارج از کلاس جاری، معکوس شده است.


انواع معکوس سازی تولید اشیاء

بسیاری شاید تصور کنند که تنها راه معکوس سازی تولید اشیاء، تزریق وابستگی‌ها است؛ اما روش‌های چندی برای انجام اینکار وجود دارد:
الف) استفاده از الگوی طراحی Factory (که نمونه‌ای از آن‌را در قسمت قبل مشاهده کردید)
ب) استفاده از الگوی Service Locator
 Button button = ServiceLocator.Create(IButton.Class)
در این الگو بر اساس یک سری تنظیمات اولیه، نوع خاصی از یک اینترفیس درخواست شده و نهایتا وهله‌ای که آن‌را پیاده سازی می‌کند، به برنامه بازگشت داده می‌شود.
ج) تزریق وابستگی‌ها
Button button = GetTheButton();
Form1 frm = new Form1(button);
در اینجا نوع وابستگی مورد نیاز کلاس Form1 در سازنده آن تعریف شده و کار تهیه وهله‌ای از آن وابستگی در خارج از کلاس صورت می‌گیرد.

به صورت خلاصه هر زمانیکه تولید و وهله سازی وابستگی‌های یک کلاس را به خارج از آن منتقل کردید، کار معکوس سازی تولید وابستگی‌ها انجام شده است.
مطالب
امکان معرفی نوع‌های محدود به یک فایل در C# 11
در C# 11 ارائه‌ی شده‌ی به همراه NET 7.0.، واژه‌ی کلیدی جدید file، جهت تعریف نوع‌های محدود به یک فایل «File Scoped Types» ارائه شده‌است. این واژه‌ی کلیدی را می‌توان به تعریف هر نوع دلخواهی مانند class, interface, record, struct, enum, delegate اضافه کرد (منهای خواص، فیلدها و رخدادها؛ البته تا C# 11)، تا آن نوع، دیگر در سایر کلاس‌های فایل‌های برنامه، قابل دسترسی نباشد و سطح دید استفاده‌ی از آن، تنها محدود به فایل جاری محل قرار گیری آن شود. به این ترتیب می‌توان در یک فضای نام مشخص، چندین کلاس هم‌نام را تعریف کرد؛ کاری که در نگارش‌های پیشین #C، میسر نبود. بدیهی دیگر نمی‌توان یک چنین نوع‌هایی را با سطوح دسترسی متداول internal و یا  public، تعریف و ترکیب کرد.


یک مثال: نمونه‌ای از نحوه‌ی تعریف و استفاده‌ی از File Scoped Types

فرض کنید دو فایل جدید را به نام‌های File1.cs و File2.cs به پروژه‌ی جاری اضافه کرده‌ایم.
محتوای فایل File1.cs به صورت زیر است:
namespace CS11Tests;

file static class Post
{
    public static string GetTitle() => "Title from File1.cs";
}

internal static class InternalClassFromFile1
{
    public static string GetTitle() => Post.GetTitle();
}
و محتوای فایل File2.cs به نحو زیر تعریف شده‌است:
namespace CS11Tests;

file static class Post
{
    public static string GetTitle() => "Title from File2.cs";
}

internal static class InternalClassFromFile2
{
    public static string GetTitle() => Post.GetTitle();
}
اگر دقت کنید، ذیل فضای نام مشخص و ثابت CS11Tests، دو کلاس هم نام Post را داریم که اینبار با واژه‌ی کلیدی file، شروع شده‌اند و میدان دید دسترسی به آن‌ها، محدود به همان فایل دربرگیرنده‌ی آن‌ها است و در سایر قسمت‌های برنامه قابل دسترسی نیستند. اگر خواستیم به‌نحوی از آن‌ها در سایر قسمت‌های برنامه نیز استفاده کنیم، مانند فایل Program.cs، می‌توان یک تعریف متداول internal/public را مانند کلاس‌های internal تعریف شده، ایجاد کرد و سپس به صورت «غیرمستقیمی» به آن‌ها دسترسی یافت:
using System.Security.AccessControl;
using CS11Tests;
using static System.Console;

WriteLine(InternalClassFromFile1.GetTitle());
WriteLine(InternalClassFromFile2.GetTitle());

امکان partial تعریف کردن نوع‌های محدود به یک فایل در C# 11

در اینجا می‌توان نوع‌های محدود به یک فایل را partial نیز تعریف کرد؛ به شرطی که تمام تعاریف آن‌ها داخل همان فایل قرار گیرند:
namespace CS11Tests;

file static partial class Post
{
    internal static string GetFileScopeTitle() => "Title from File3.cs";
}

file static partial class Post
{
    internal static string AnotherGetFileScopeTitle() => "Another Title from File3.cs";
}

یک سؤال: اگر در یک فایل، file class Post و در فایلی دیگر، کلاس هم نام داخلی internal class Post را تعریف کردیم، آیا می‌توان از نمونه‌ی هم‌نام internal، در کلاس file دار استفاده کرد؟
پاسخ:
خیر!
فرض کنید در File4.cs چنین تعریفی را داریم:
namespace CS11Tests;

internal static class Post
{
    public static string GetTitle() => "Title from File4.cs";
}
در اینجا در فضای نام مشخصی، کلاس Post، به صورت internal تعریف شده‌است. اکنون در File3.cs، مجدد تعریف کلاس هم‌نام Post را اینبار به صورت file داریم:
namespace CS11Tests;

file static class Post
{
    internal static string GetFileScopeTitle() => CS11Tests.Post.GetTitle() + "Title from File3.cs";
}
این قطعه کد کامپایل نمی‌شود. چون Post ای که در اینجا قابل استفاده‌است، دقیقا همان کلاس Post جاری این فایل است و نه نمونه‌ی هم‌نام internal در فایلی دیگر.


خروجی کامپایلر C# 11 در مورد سطح دسترسی file

کامپایلر C# 11 جهت جلوگیری از تداخل نام‌های حاصل از تعریف کلاس‌های با سطح دسترسی file، از قالب زیر:
<SourceFileNameWithoutExtension>F$index$_TypeName
برای نامگذاری نهایی اینگونه نوع‌ها استفاده می‌کند؛ مانند مثال زیر که مرتبط با کلاس Post تعریف شده‌ی در فایل File1.cs است:
internal static class <File1>F3A5590C89B71B2DB20A548228781187A11D076C0CC91E851A4EE796FFE808F8F__Post
{
    public static string GetTitle()
    {
        return "Title from File1.cs";
    }
}
Index منحصربفرد استفاده شده، مشکل تداخل نام‌ها را برطرف می‌کند و به علت وجود <> در تعریف این نام‌های ویژه، امکان استفاده‌ی از آن‌ها در سایر قسمت‌ها و فایل‌های برنامه وجود ندارد.
تاکنون از این روش نامگذاری ویژه، در موارد دیگری مانند async/await , lambda, anonymous method, anonymous types نیز استفاده شده‌است.

چرا قابلیت «File Scoped Types» به زبان C# 11 اضافه شده‌است؟

- جهت کدهای تولیدی توسط ابزارها: گاهی از اوقات، تولید کننده‌های کد، از یک نام مشخص مانند DataSet، بارها و بارها استفاده می‌کنند. برای جلوگیری از تداخل این‌ها، عموما از تعریف تو در توی کلاس‌ها استفاده می‌شود و یا نام آن‌ها را با ایندکس‌هایی مانند DateSet1، DateSet2 و امثال آن‌ها مشخص می‌کنند. وجود واژه‌ی کلیدی file، کار ابزارهای تولید کننده‌ی کد را ساده‌تر می‌کند.
- برای ساده سازی تعریف متدهای الحاقی: با استفاده از سطح دسترسی فایل می‌توان از تداخل متدهای الحاقی هم نام و همچنین شلوغ شدن intellisense جلوگیری کرد. به این ترتیب می‌توان کلاس‌های حاوی Extension method مختص به یک فایل را ایجاد کرد که در سایر قسمت‌های برنامه قابل دسترسی نباشند.
- کاهش تعریف کلاس‌های تو در تو: همانطور که عنوان شد، یکی از روش‌های مقابله‌ی با مشکل تعریف کلاس‌های هم نام در یک فضای نام مشخص، تعریف nested classes است. با ارائه‌ی واژه‌ی کلیدی file، می‌توان یک سطح فرو رفتگی تعریف کلاس‌ها را کاهش داد و به کدهای تمیزتری رسید.
- امکان کپسوله سازی‌های بهتر: عموما کامپوننت‌ها و ماژول‌ها، از چند کلاس تشکیل می‌شوند. با وجود واژه‌ی کلیدی file، می‌توان به سطح بالاتری از خصوصی سازی نوع‌ها، بدون نیاز به تعریف نوع‌های private و یا nested private رسید.
- سهولت نوشتن کلاس‌های آزمون‌های واحد: عموما هر کلاس آزمون، از نوع‌ها و داده‌های خاص خودش استفاده می‌کنند و در اینجا می‌توان سطح دسترسی این تعاریف را بسیار محدود و مختص به همان فایل Test کرد.
مطالب
فیلترها در MVC
هنگامی که درخواستی به سرور ارسال می‌گردد، به کنترلر و اکشن مربوطه جهت پاسخگویی هدایت می‌شود. خب شاید مواقعی شما نیاز داشته باشید قبل یا بعد از اجرای اکشن متدی، کدی اجرا گردد. به‌همین جهت در MVC قابلیتی بنام Filter ارائه گردید.
فیلتر، یک کلاس سفارشی است که شما می‌توانید منطق برنامه را جهت اجرا، قبل یا بعد از اجرای یک اکشن متد، در آن پیاده سازی نمایید. فیلترها می‌توانند به یک اکشن متد و یا کنترلری منتسب شوند که در ادامه با این روشها آشنا خواهید شد.

در لیست زیر انواع فیلترها و اینترفیس‌هایی که باید توسط کلاس سفارشی شما پیاده سازی شوند، معرفی شده است.

 نوع توضیح
 فیلتر توکار
 اینترفیس
 Authorization
انجام عملیات احراز هویت و سطح دسترسی، قبل از اجرای کد اکشن متد  
 [Authorize] و [RequireHttps]  
 IAuthorizationFilter 
 Action
اجرای کدهایی قبل از اجرای کدهای اکشن متد 
   IActionFilter 
 Result
اجرای کدهایی قبل یا بعد از تولید ویو (View result) 
 [OutputCache]   IResultFilter 
Exception
اجرای کدهایی در صورت وجود استثنای مدیریت نشده 
[HandleError] 
IExceptionFilter
مثال: هنگامی که خطایی در حین اجرای اکشن متدی رخ می‌دهد، فیلتر توکار MVC بنام HandleError اجرا می‌شود. این فیلتر توکار فایل Error.cshtml را که در فولدر Shared قرار دارد، رندر می‌کند و نمایش می‌دهد.
در تکه کد زیر نحوه‌ی استفاده از این فیلتر را مشاهده می‌کنید:
[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        //throw exception for demo
        throw new Exception("This is unhandled exception");
            
        return View();
    }

    public ActionResult About()
    {
        return View();
    }

    public ActionResult Contact()
    {
        return View();
    }        
}

نکته: فیلترهای اعمال شده‌ی به یک کنترلر، به تمام اکشن متدهای آن نیز اعمال می‌گردند. 

در کد بالا خصیصه‌ی HandleError به HomeController اعمال شده است. بنابراین در صورت بروز خطایی در هر کدام از اکشن‌ها، صفحه‌ی Error.cshtml نمایش داده خواهد شد و در تظر داشته باشید که خطاها توسط try-catch هندل نشده‌اند.
باید جهت عملکرد صحیح فیلتر توکار HandleErrorAttribute، مقدار customErrors در قسمت System.web فایل web.config مساوی on باشد.
<customErrors mode="On" />


مهیا کنندگان فیلترها

بصورت پیش فرض MVC از سه طریق زیر فیلترها را جهت استفاده‌ی در برنامه فراهم می‌کند:

  1. خصیصه‌ی GlobalFilters.Filters برای فیلترهای سراسری
  2. کلاس FilterAttributeFilterProvider برای فیلترهای خصیصه‌ای
  3. کلاس ControllerInstanceFilterProvider جهت افزودن کنترلر به یک وهله از FilterProviderCollection

در ادامه با نحوه‌ی ایجاد یک فیلتر، بوسیله‌ی هر یک از روش‌های بالا، با ذکر مثالی بیشتر آشنا خواهید شد.

ترتیب اجرای فیلترها

همانطور که در ابتدا اشاره شد، در MVC چهار نوع فیلتر معرفی شده است که امکان استفاده‌ی از آنها به‌صورت همزمان در سطح کنترلر و یا اکشن متد وجود دارد. اما ترتیب  اجرای آنها متفاوت و به ترتیب زیر است:

  1. فیلترهای Authorization
  2. فیلترهای Action
  3. فیلترهای Result یا Response
  4. فیلترهای Exception

فیلترها براساس ترتیب اشاره شده‌ی در بالا اجرا خواهند شد. در صورتیکه چند فیلتر از یک نوع استفاده شود، جهت تقدم و تاخر در اجرا، از خاصیت Order استفاده خواهد شد. بعنوان مثال در کد زیر بدلیل خاصیت Order=1 ابتدا AuthorizationFilterB  و سپس AuthorizationFilterA اجرا می‌شود.

[AuthorizationFilterA(Order=2)]
[AuthorizationFilterB(Order=1)]
public ActionResult Index()
{          
    return View();
}
علاوه بر خاصیت Order، مقدار Scope نیز سطح سومی از ترتیب اجرای فیلترها می‌باشد. مقادیر Scope بشرح زیر است:
public enum FilterScope
{
    First = 0,
    Global = 10,
    Controller = 20,
    Action = 30,
    Last = 100,
}
این خصیصه‌ی فیلترها در محل بکار گیری آنها مقدار دهی می‌شود. در صورتیکه فیلتری بصورت سراسری رجیستر شود، Scope آن برابر 10 و در سطح کنترلر، برابر 20 خواهد بود و الی آخر.

نکته: مقدار Scope فیلترهای Authorization برابر 0 و فیلترهای Exception برابر 100 می‌باشد.

ایجاد فیلتر سفارشی

روش اول: پیاده سازی اینترفیس یکی از انواع فیلترها و ارث بری از کلاس FilterAttribute

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

  • IAuthorizationFilter
// Called when authorization is required
void OnAuthorization(AuthorizationContext filterContext)
  • IActionFilter
// Called after the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called before an action method executes
void OnActionExecuting(ActionExecutingContext filterContext)
  • IResultFilter
// Called after an action result executes
void OnResultExecuted(ResultExecutedContext filterContext)

// Called before an action result executes
void OnResultExecuting(ResultExecutedContext filterContext)
  • IException
// Called when an exception occurs
void OnException(ExceptionContext filterContext)

یادآوری: همانطور که در ابتدای مقاله اشاره شد، فیلترها قبل یا بعد از اجرای اکشن متدها فراخوانی خواهند شد. بنابراین به کامنت بالای متد فیلترها دقت داشته باشید.

مثال: پیاده سازی اینترفیس IExceptionFilter و ارث بری از کلاس FilterAttribute جهت تهیه‌ی فیلتری سفارشی از نوع Exception

class CustomErrorHandler : FilterAttribute, IExceptionFilter
{
    public override void IExceptionFilter.OnException(ExceptionContext filterContext)
    {
        Log(filterContext.Exception);

        base.OnException(filterContext);
    }

    private void Log(Exception exception)
    {
        //log exception here..
    }
}

روش دوم:
ارث بری از ActionFilterAttribute
کلاس abstract فوق دارای چهار متد زیر جهت تحریف است. همانطور که مشاهده می‌کنید این کلاس علاوه بر دو متد OnActionExecuted و OnActionExecuting دارای دو متد دیگر OnResultExecuting و OnResultExecuted که به‌ترتیب قبل و بعد خروجی (Result) اکشن متد اجرا می‌شوند، نیز می‌باشد. این نوع فیلترها عموما جنبه‌ی استفاده عمومی داشته و می‌توان از آنها جهت logging ،caching و یا authorization استفاده کرد.
// Called by MVC after the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called by MVC before the action method executes
void OnActionExecuted(ActionExecutedContext filterContext)

// Called by MVC after the action result executes
void OnResultExecuted(ResultExecutedContext filterContext)

// Called by MVC before the action result executes
void OnResultExecuting(ResultExecutingContext filterContext)

مثال: کلاس LogAttribute که از کلاس ActionFilterAttribute ارث بری کرده است، عملیات قبل و بعد از اجرای اکشن متد را لاگ می‌کند.
public class LogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Log("OnActionExecuted", filterContext.RouteData); 
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Log("OnActionExecuting", filterContext.RouteData);      
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Log("OnResultExecuted", filterContext.RouteData);      
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Log("OnResultExecuting ", filterContext.RouteData);      
    }

    private void Log(string methodName, RouteData routeData)
    {
        var controllerName = routeData.Values["controller"];
        var actionName = routeData.Values["action"];
        var message = String.Format("{0}- controller:{1} action:{2}", methodName, 
                                                                    controllerName, 
                                                                    actionName);
        Debug.WriteLine(message);
    }
}

روش سوم:
پیاده سازی داخل کنترلر
کلاس Controller  می‌تواند هر یک از اینترفیس‌های فیلترها را پیاده سازی نماید. به عبارت دیگر در هر کلاس کنترلر می‌توانید متدهای زیر را تحریف نمایید.
  • OnAuthorization ^
  • OnException ^
  • OnActionExecuting ^
  • OnActionExecuted ^
  • OnResultExecuting ^
  • OnResultExecuted ^


روش چهارم: ارث بری از کلاس فیلترهای توکار و مهیای در MVC و تحریف متدهای آن 
در کد زیر با تحریف و سفارشی سازی متد OnException مخصوص فیلتر توکار HandleError، قابلیت‌های آن افزایش یافته است:

class CustomErrorHandler : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        Log(filterContext.Exception);

        base.OnException(filterContext);
    }

    private void Log(Exception exception)
    {
        //log exception here..
    }
}


رجیستر فیلترها

  • سراسری:

درصورتی که قصد داشته باشید فیلتری بصورت سراسری و در کل برنامه فعال گردد باید آن را در رویداد Application_Start فایل Global.asax.cs بوسیله‌ی متد RegisterGlobalFilters کلاس FiterConfig رجیستر نمایید. بعد از آن فیلتر به کلیه‌ی کنترلرها و اکشن متدها اعمال می‌گردد.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
          FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    }
}

// FilterConfig.cs located in App_Start folder 
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());

        // add your new custom filters
        filters.Add(new LogAttribute()); 
        filters.Add(new CustomErrorHandler());
     }
}

در کد بالا فیلتر توکار HandleError و البته فیلترهای سفارشی دیگری نیز به صورت سراسری به تمام اکشن متدهای کنترلرها اعمال گردیده است.

  • کنترلر: در صورتی که فقط بخواهید یک فیلتر به کل اکشن‌های یک کنترلر اعمال گردد. همانند آنچه که در مثال ابتدایی بدان اشاره شد.
[HandleError]
public class HomeController : Controller
  • اکشن متد: اعمال یک فیلتر به یک اکشن متد خاص کنترلر. در کد زیر فیلتر HandleError فقط به اکشن متد Index کنترلر Home اعمال خواهد شد.
public class HomeController : Controller
{
    [HandleError]
    public ActionResult Index()
    {
        return View();
    }
}
مطالب
اندازه گیری کارآیی کدها توسط NBench
این روزها جهت اندازه‌گیری کارآیی قطعات کدهای دات نتی، استفاده از فریم ورک‌های مخصوصی که بسیاری از نکات ریز مرتبط با اینگونه اندازه‌گیری‌ها را مانند warmup یا گرم کردن JIT (جهت عدم اندازه گیری زمان کامپایل پویای کدها، بجای زمان واقعی اجرای آن‌ها)، اندازه‌گیری فشار بر روی Garbage collector و غیره را انجام می‌دهند، بجای استفاده‌ی از Stop Watch، متداول است. یکی از معروفترین‌های این گروه، که تقریبا حالت استانداردی را در جهت اندازه گیری کارآیی کدهای دات نتی پیدا کرده‌است، فریم ورک سورس باز NBench است.


شروع به کار با NBench

برای شروع به کار با NBench، ابتدا نیاز است دو بسته‌ی نیوگت ذیل را نصب کرد:
PM> Install-Package NBench
PM> Install-Package NBench.Runner
عملکرد این فریم ورک، شبیه به عملکرد فریم ورک‌های آزمون‌های واحد است. برای مثال فرض کنید که می‌خواهید فشار حافظه و فشار بر روی GC قطعه کدی را اندازه گیری کنید:
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)]
[MemoryMeasurement(MemoryMetric.TotalBytesAllocated)]
public void AddMemoryMeasurement()
{
    const int numberOfAdds = 1000000;
    var dictionary = new Dictionary<int, int>();
    for (var i = 0; i < numberOfAdds; i++)
    {
        dictionary.Add(i, i);
    }
}
 
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)]
[GcMeasurement(GcMetric.TotalCollections, GcGeneration.AllGc)]
public void MeasureGarbageCollections()
{
    var dataCache = new List<int[]>();
    for (var i = 0; i < 500; i++)
    {
        for (var j = 0; j < 10000; j++)
        {
            var data = new int[100];
            dataCache.Add(data.ToArray());
        }
 
        dataCache.Clear();
    }
}
همانند نوشتن متدهای آزمون‌های واحد، ابتدا یک یا چند متد public void را در اینجا اضافه می‌کنیم.
سپس هر متد تست به ویژگی PerfBenchmark مزین می‌شود. در اینجا RunMode.Iterations به این معنا است که خودمان قصد داریم در طی یک حلقه، تعداد بار انجام را مشخص کنیم.
ویژگی MemoryMeasurement برای اندازه گیری حافظه‌ی مصرفی یک قطعه کد و GcMeasurement برای اندازه گیری فشار بر روی Garbage collector بکار می‌رود.


اجرای آزمون‌های NBench

پس از تهیه‌ی دو متد فوق، به پوشه‌ی packages\NBench.Runner.0.3.4\lib\net45 مراجعه کنید. یک فایل exe در آن موجود است که کار یافتن و اجرای آزمون‌های NBench را انجام می‌دهد. به عنوان پارامتر آن تنها کافی است مسیر اسمبلی برنامه (فایل exe و یا dll) را به آن ارسال کنیم:
 D:\Prog\NBenchSample\packages\NBench.Runner.0.3.4\lib\net45\NBench.Runner.exe "D:\Prog\NBenchSample\NBenchSample\bin\Release\NBenchSample.exe"
پس از آن، کار اجرای آزمون‌های NBench شروع شده و پس از مدتی ابتدا BEGIN WARMUP و END WARMUP‌ها را می‌توان مشاهده کرد و در آخر یک چنین خروجی ارائه می‌شود:
 --------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement ---------------
TotalBytesAllocated: Max: 47,842,944.00 bytes, Average: 42,002,757.60 bytes, Min: 41,353,848.00 bytes, StdDev: 2,052,032.33 bytes
TotalBytesAllocated: Max / s: 359,074,078.19 bytes, Average / s: 311,474,786.96 bytes, Min / s: 300,926,928.79 bytes, StdDev / s: 16,869,581.62 bytes

--------------- RESULTS: NBenchSample.Program+MeasureGarbageCollections ---------------
TotalCollections [Gen0]: Max: 708.00 collections, Average: 702.80 collections, Min: 697.00 collections, StdDev: 3.65 collections
TotalCollections [Gen0]: Max / s: 111.55 collections, Average / s: 109.87 collections, Min / s: 107.88 collections, StdDev / s: 1.28 collections

TotalCollections [Gen1]: Max: 338.00 collections, Average: 334.60 collections, Min: 330.00 collections, StdDev: 2.41 collections
TotalCollections [Gen1]: Max / s: 53.61 collections, Average / s: 52.31 collections, Min / s: 51.10 collections, StdDev / s: 0.70 collections

TotalCollections [Gen2]: Max: 32.00 collections, Average: 24.80 collections, Min: 18.00 collections, StdDev: 4.73 collections
TotalCollections [Gen2]: Max / s: 4.91 collections, Average / s: 3.87 collections, Min / s: 2.86 collections, StdDev / s: 0.72 collections


نکته‌ای در مورد اندازه گیری فشار حافظه

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

شبیه به همین استراتژی، در پیاده سازی Dictionary نیز بکارگرفته شده‌است:
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)]
[MemoryMeasurement(MemoryMetric.TotalBytesAllocated)]
public void AddMemoryMeasurement_With_initial_Size()
{
    const int numberOfAdds = 1000000;
    var dictionary = new Dictionary<int, int>(numberOfAdds);
    for (var i = 0; i < numberOfAdds; i++)
    {
        dictionary.Add(i, i);
    }
}
اگر اینبار این آزمون را انجام دهیم، به نتیجه‌ی ذیل خواهیم رسید:
 --------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement_With_initial_Size ---------------
TotalBytesAllocated: Max: 23,245,912.00 bytes, Average: 23,245,912.00 bytes, Min: 23,245,912.00 bytes, StdDev: 0.00 bytes
TotalBytesAllocated: Max / s: 394,032,435.34 bytes, Average / s: 389,108,363.43 bytes, Min / s: 378,502,981.34 bytes, StdDev / s: 5,575,519.09 bytes
در اینجا زمانیکه شیء دیکشنری ایجاد شده‌است، اندازه‌ی اولیه‌ی آن نیز مشخص گردیده‌است. همین مساله سبب شده‌است تا مصرف حافظه‌ی آن از نزدیک به 41 مگابایت (متد AddMemoryMeasurement ابتدای بحث) به نزدیک 24 مگابایت (متد AddMemoryMeasurement_With_initial_Size فوق) کاهش یابد.
علت اینجا است که دیکشنری در پشت صحنه، از یک متد ReSize استفاده می‌کند که شبیه به سیستم عامل، بیشتر از مقدار مورد نیاز جهت ذخیره‌ی اشیاء، برای کاهش تعداد بار تخصیص‌های حافظه، حافظه به خود اختصاص می‌دهد. به همین جهت زمانیکه اندازه‌ی اولیه را مشخص کرد‌ه‌ایم، کار تخصیص حافظه‌ی بیش از اندازه‌ی این شیء، به شدت کاهش یافته‌است.


بررسی متد MeasureGarbageCollections

در متد MeasureGarbageCollections، مقدار زیادی شیء بر روی heap ایجاد شده و GC را وادار به عکس العمل شدید می‌کند.
حلقه‌ی داخلی ایجاد شده نیز تعداد زیادی شیء را در جهت پاکسازی GC تخصیص می‌دهد. این پاکسازی در مرحله‌ا‌ی به نام generation 0 صورت می‌گیرد.
اشیاء اضافه شده‌ی به لیست، طول عمر بیشتری دارند (تا پایان حلقه). بنابراین از garbage collection at generation 0 جان سالم به در خواهند برد و در garbage collection at generation 1  به عمر آن‌ها پایان داده خواهد شد. هرچند ممکن است تعدادی از آن‌ها پاکسازی نشده و تا پایان full garbage collection (generation 2) باقی بمانند.
در آزمایش انجام شده، با ذکر GcGeneration.AllGc، هر سه مورد Gen0 تا Gen2 اندازه گیری خواهند شد. عموما اندازه گیری Gen0 و Gen1 مهم نیستند و این‌ها خیلی زود به پایان خواهند رسید. اگر تعداد بار رخ‌دادن Gen2 زیاد بود (یا اصلا وجود داشت)، می‌تواند سبب بروز مشکلات کارآیی شدیدی گردد.
بنابراین می‌توان بجای تنظیم GcGeneration.AllGc، صرفا از GcGeneration.Gen2 استفاده کرد.


اندازه‌گیری Throughput یا تعداد بار اجرای یک متد در ثانیه

روش دیگر کار با فریم ورک NBench، ایجاد یک کلاس مخصوص و سپس افزودن متدهای Setup مزین به PerfSetup، متد Cleanup مزین به PerfCleanup و سپس تعدادی متد اندازه گیری کارآیی توسط ویژگی PerfBenchmark است. در اینجا برای اندازه‌گیری سرعت اجرای متدها، از ویژگی CounterThroughputAssertion استفاده خواهد شد که پارامتر اول آن نام یک شمارشگر است. این شمارشگر در متد Setup ایجاد می‌شود (با یک نام دلخواه).
public class DictionaryThroughputTests
{
    private readonly Dictionary<int, int> _dictionary = new Dictionary<int, int>();
 
    private const string AddCounterName = "AddCounter";
    private Counter _addCounter;
    private int _key;
 
    private const int AverageOperationsPerSecond = 20000000;
 
    [PerfSetup]
    public void Setup(BenchmarkContext context)
    {
        _addCounter = context.GetCounter(AddCounterName);
        _key = 0;
    }
 
    [PerfBenchmark(RunMode = RunMode.Throughput, TestMode = TestMode.Test)]
    [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)]
    public void AddThroughput_ThroughputMode(BenchmarkContext context)
    {
        _dictionary.Add(_key++, _key);
        _addCounter.Increment();
    }
 
    [PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Test)]
    [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)]
    public void AddThroughput_IterationsMode(BenchmarkContext context)
    {
        for (var i = 0; i < AverageOperationsPerSecond; i++)
        {
            _dictionary.Add(i, i);
            _addCounter.Increment();
        }
    }
 
    [PerfCleanup]
    public void Cleanup(BenchmarkContext context)
    {
        _dictionary.Clear();
    }
}
در این آزمایش‌ها، RunMode.Throughput به معنای اجرای متد آزمایش به تعداد AverageOperationsPerSecond توسط فریم ورک NBench است. در حالت قید RunMode.Iterations، تعداد بار اجرا، توسط حلقه‌ای که ما مشخص کرده‌ایم، تعیین می‌گردد.
 --------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_ThroughputMode ---------------
[Counter] AddCounter: Max: 575,654.00 operations, Average: 575,654.00 operations, Min: 575,654.00 operations, StdDev: 0.00 operations
[Counter] AddCounter: Max / s: 7,205,997.59 operations, Average / s: 7,163,894.30 operations, Min / s: 7,075,316.79 operations, StdDev / s: 42,518.20 operations

--------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_IterationsMode ---------------
[Counter] AddCounter: Max: 20,000,000.00 operations, Average: 20,000,000.00 operations, Min: 20,000,000.00 operations, StdDev: 0.00 operations
[Counter] AddCounter: Max / s: 7,409,380.61 operations, Average / s: 7,250,991.24 operations, Min / s: 6,880,938.73 operations, StdDev / s: 148,085.19 operations
اگر دقت کنید، کارآیی اندازه گیری شده‌ی در حالت RunMode.Iterations بیشتر است از حالت RunMode.Throughput. چون در حالت RunMode.Throughput، فریم ورک کار اجرای متد را از طریق Reflection انجام می‌دهد. بنابراین بهتر است از حالت RunMode.Iterations، جهت رسیدن به نتایج دقیق‌تری استفاده کرد.
در اینجا برای گزارش دادن، عددهای Average و  Average / s باید مورد استفاده قرار گیرند.
بازخوردهای دوره
آشنایی با AOP Interceptors
- سؤال شما این بود که در کلاس اصلی من، در متدی داخل آن، با چند صدهزار رکورد کار انجام می‌شود. پاسخ این است که اصلا این پروکسی ایجاد شده ربطی به داخل متد شما ندارد. کاری به وهله سازی‌های انجام شده داخل آن نیز ندارد. invocation.Proceed یعنی این متد رو اجرا کن؛ نه اینکه هر وهله‌ای که داخل آن متد قرار می‌گیرد را نیز با پروکسی مزین کن.
- تمام ORMها برای پیاده سازی مباحث Lazy loading یک شیء پروکسی را از شیء اصلی شما ایجاد می‌کنند. نمونه‌اش را شاید با EF Code first با نام‌های خودکاری مانند ClassName_00394CF1F92740F13E3 دیده باشید؛ NHibernate هم یک زمانی از همین Castle.Core برای تدارک پروکسی‌های اشیاء استفاده می‌کرد. سربار آن در حین ایجاد چندین هزار وهله از یک شیء، در حد همان کار با ORMهایی است که هر روزه از آن‌ها استفاده می‌کنید (اگر می‌خواهید یک حسی از این قضیه داشته باشید).