نظرات مطالب
نمایش گرادیان در iTextSharp
مطالب
غیرفعال سازی کش مرورگر در مورد تصاویر ایستا

امروز هنگام نمایش تصاویر در یک گرید با مشکل زیر مواجه شده بودم. (گرفتار شده بودم!!)
فرض کنید یک فرم درست کرده‌اید که یک تصویر به همراه توضیحاتی توسط آن ثبت می‌شود. جهت اطمینان خاطر ثبت کننده، تصاویر ثبت شده در یک گرید پائین فرم نمایش داده خواهند شد. نام این تصاویر همواره ثابت است (برای مثال نام تصویر تشکیل شده است از یک شماره پروژه بعلاوه پسوند فایل).
مشکلی در ثبت تصاویر یا توضیحات آنها وجود نداشت. مشکل در این بود که پس از ویرایش رکورد و انتخاب تصویری جدید و ثبت آن، همان تصویر قبلی در مرورگر نمایش داده می‌شد. این مورد را می‌شد با فشردن دکمه‌های ctrl+f5 حل کرد (به روز کردن کش مرورگر)، اما این راه حل اصولی حل این مشکل نیست. (تصورش را بکنید که به کاربر گفته شود پس از هر بار فشردن دکمه ثبت، یکبار هم دکمه‌های ctrl+f5 را فشار دهید!)
راه ‌حل‌های استاندارد غیرفعال کردن کش در ASP.Net هم هیچکدام افاقه نکردند. ترکیبی از موارد زیر در page_load صفحه تست شدند:
            //do not cache...
Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
Response.Expires = -1;
Response.Cache.SetNoStore();
Response.Cache.SetCacheability(HttpCacheability.NoCache);

چون نام تصاویر تغییری نمی‌کرد، برای مثال 1234 همیشه همان 1234 باقی می‌ماند (صرفنظر از محتوای جدید آن)، مرورگر این تصویر غیرپویا را کش می‌کرد و فقط با ctrl+f5 این کش به روز می‌شد. (روش فوق در مورد غیرفعال کردن کش کردن یک صفحه پویای ASP.Net مؤثر است (برای مثال توصیه می‌شود که کش کردن صفحات لاگین را حتما به این صورت غیرفعال کنید)، اما در مورد اشیاء غیرپویای صفحه مطابق آزمایش من اثری نداشت)

این مشکل به صورت زیر حل شد: (یک ستون GridView است)
<asp:TemplateField HeaderText="لوگو">
<ItemTemplate>
<asp:Image ID="Image2" runat="server"
ImageUrl='<%# "pics/"+Eval("filename")+"?uid="+Guid.NewGuid().ToString("N") %>' />
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>

یک عبارت منحصر بفرد به صورت کوئری استرینگ به انتهای نام تصویر اضافه شد. مشکلی هم در نمایش تصویر ایجاد نمی‌کند، مرورگر آن‌را یک تصویر جدید به حساب آورده و دوباره همان تصویر قبلی موجود در کش را نمایش نخواهد داد. (با IE و فایرفاکس تست شد و اینجا دیگر مهم نیست که وضعیت تنظیمات به روز رسانی کش مرورگرهای تک تک کاربران به چه صورتی است و آیا باید نگران همه‌ی آنها بود یا خیر یا این‌که اصلا ارزش آن‌را دارد که برای یک صفحه کلا کش مرورگر را غیرفعال کرد؟)

زمانیکه GridView رندر شود، تصویر ما به صورت زیر خواهد بود:
<img id="GridView1__ctl2_Image2" src="pics/123456.PNG?uid=2a2c4247ae264ef49be46a2436ae03c9" border="0" />

نظرات مطالب
اعتبار سنجی سمت کاربر wysiwyg-editor ها در ASP.NET MVC
- این تنظیم پیش فرض در فایل jquery.validate.js موجود است (عبارت ":hidden" را در آن جستجو کنید).
- فقط متد jQuery.validator.setDefaults یاد شده را به همراه پارامترهایی که ذکر شد، یکبار در صفحه فراخوانی کنید (پس از الحاق فایل jquery.validate.js).
مطالب
نمایش اشیاء موجود در View بر اساس دسترسی‌ها در ASP.NET MVC
سیستم دسترسی در یک سیستم، همیشه برای من چالش برانگیز بوده است. با دیدن کدهای مختلف از افراد مختلف، شیوه‌های گوناگونی از کدنویسی را دیده‌ام؛ ولی یکی از نکاتی که در بین آن‌ها بررسی نشده بود و یا از آن غافل مانده بودند، بررسی بعضی از عناصر موجود در ویو بود که باید با توجه به نقش کاربر سیستم، وضعیت آن بررسی میشد. 
برای مثال تصور کنید که شما دو کاربر دارید که هر دو سطح دسترسی به پروفایل کاربران دیگر را دارند. ولی یکی از کاربرها این توانایی را دارد تا با کلیک بر روی دکمه‌ای، لاگین کاربر مورد نظر را مسدود نماید، ولی دیگری نمی‌تواند این دکمه را ببیند، یا برای او به صورت غیرفعال نمایش داده شود. در اکثر این مواقع کاری که برنامه نویسان انجام میدهند، نوشتن توابع به شیوه‌های گوناگون در لابلای انبوهی از کدهای View هست تا بتوانند این مسئله را پیاده سازی کنند. ولی با اضافه شدن هر چه بیشتر این عناصر به صفحه و با توجه به انبوهی از ویووها و ویووکامپوننت‌ها کار بسیار سخت‌تر میشود.
 به همین جهت من از فیلترها در MVC کمک گرفتم و این موضوع را با توجه به خروجی نهایی صفحه بررسی میکنم. به این معنا که وقتی صفحه بدون هیچ گونه اعتبارسنجی در سطح ویو آماده شد، با استفاده از فیلتر مورد نظر که به صورت سراسری اضافه شده است، بررسی میکنم که آیا این کاربر حق دارد بعضی از المان‌ها را ببیند یا خیر؟ در صورتیکه برای هر المان موجود در صفحه اعتباری نداشته باشد، آن المان از صفحه حذف و یا غیرفعال میشود.
ابتدا یک صفحه را با المان‌های زیر میسازیم. از آنجا که بیشتر تگ‌های عملیاتی از نوع لینک و دکمه هستند، از هر کدام، سه عنصر به صفحه اضافه میکنیم:
<button data-perm="true" data-controller="c" data-action="r" data-method="post" data-type="disable">Test 1</button>
<button data-perm="true" data-controller="c" data-action="t" data-method="post">Test 2</button>
<button data-perm="false" data-controller="c" data-action="r" data-method="post">Test 3</button>
<a data-perm="true" data-controller="c" data-action="m" data-method="post">Test 4</a>
<a data-perm="false" data-controller="c" data-action="m" data-method="post">Test 5</a>
<a data-perm="true" data-controller="c" data-action="t" data-method="post">Test 6</a>
در اینجا ما از همان ساختار آشنای *-data استفاده میکنیم و معنی هر کدام از ویژگی‌ها برابر زیر است:
ویژگی
توضیحات
 perm   آیا نیاز به اعتبارسنجی دارد یا خیر؟ در صورتی که المانی مقدار Perm آن با مقدار true پر گردد، اعتبارسنجی روی آن اعمال خواهد شد.   
 controller   نام کنترلری که به آن دسترسی دارد.  
 action  نام اکشنی که به آن در کنترلر ذکر شده دسترسی دارد. 
 method   در صورتیکه دسترسی get و post و ... هر یک متفاوت باشد.  
 type  نحوه برخورد با المان غیرمجاز. در صورتیکه با disable مقداردهی شود، المان غیرفعال و در غیر اینصورت، از روی صفحه حذف میشود.

سپس یک کلاس جدید ساخته و با ارث بری از ActionFilterAttribute، کار ساخت فیلتر را آغاز میکنیم:

    public class AuthorizePage: ActionFilterAttribute
    {
        private HtmlTextWriter _htmlTextWriter;
        private StringWriter _stringWriter;
        private StringBuilder _stringBuilder;
        private HttpWriter _output;
        IAuthorization _auth;
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {        
            _stringBuilder = new StringBuilder();
            _stringWriter = new StringWriter(_stringBuilder);
            _htmlTextWriter = new HtmlTextWriter(_stringWriter);
            _output = (HttpWriter) filterContext.RequestContext.HttpContext.Response.Output;
            filterContext.RequestContext.HttpContext.Response.Output = _htmlTextWriter;
            _auth = new Auth();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            var response = _stringBuilder.ToString();

            response = AuthorizeTags(response);
            _output.Write(response);
        }

        public string AuthorizeTags(string response)
        {

            var doc = GetHtmlDocument(response);
            var nodes=doc.DocumentNode.SelectNodes("//*[@data-perm]");

            if (nodes == null)
                return response;

            foreach(var node in nodes)
            {
                var dataPermission = node.Attributes["data-perm"];
                if(!dataPermission.Value.TryBooleanParse())
                {
                    continue;
                }

                var controller = node.Attributes["data-controller"].Value;
                var action = node.Attributes["data-action"].Value;
                var method = node.Attributes["data-method"].Value;

                var access=_auth.Authorize(HttpContext.Current.User.Identity.Name , controller, action, method);

                if (access)
                    continue;

                var removeElm = true;
                var type = node.Attributes["data-type"]?.Value;
                if (type!=null && type.ToLower()== "disable")
                {
                    removeElm = false;
                }

                if(removeElm)
                {
                    node.Remove();
                    continue;
                }


                node.Attributes.Add("disabled", "true");
            }

            return doc.DocumentNode.OuterHtml;
        }

        private HtmlDocument GetHtmlDocument(string htmlContent)
        {
            var doc = new HtmlDocument
            {
                OptionOutputAsXml = true,
                OptionDefaultStreamEncoding = Encoding.UTF8
            };
            doc.LoadHtml(htmlContent);

            return doc;
        }

    }
در خطوط اولیه، از متد OnActionExecuting به عنوان یک سازنده برای پر کردن در لحظه هر درخواست استفاده میکنیم. htmlTextWriter که یک پیاده سازی از TextWriter هست، برای نوشتن بهینه کدهای HTML سودمند میباشد و از آن جهت که نیاز زیادی برای کار با رشته‌ها دارد، از StringBuilder در پشت صحنه استفاده می‌کند. جهت آشنایی بیشتر با این کلاس‌ها، مطلب StringBuilder و قسمت نظراتش را مطالعه بفرمایید. همچنین یک httpWriter هم برای ایجاد خروجی در اکشن مورد نظر نیازمندیم و مورد آخر یک اینترفیس میباشد که جهت اعتبارسنجی مورد استفاده قرار میگیرد و این اینترفیس میتواند از طریق تزریق وابستگی‌ها پر شود:
  public interface IAuthorization
    {
        bool Authorize(string userId, string controller, string action, string method);
    }

ادامه کد در متد OnResultExecuted قرار دارد و متد اصلی کار ما میباشد. این متد بعد از صدور خروجی از اکشن، صدا زده شده اجرا میشود و شامل خروجی اکشن میباشد. خروجی اکشن را به متدی به نام AuthorizeResponse داده و با استفاده از بسته htmlagilitypack که یک HTML Parser میباشد، کدهای HTML را تحلیل میکنیم. قاعده فیلترسازی المان‌ها در این کتابخانه بر اساس قواعد تعریف شده در XPath میباشد. بر اساس این قاعده ما گفتیم هر نوع تگی که دارای ویژگی data-perm میباشد، باید به عنوان گره‌های فیلتر شده برگشت داده شود. سپس مقادیر نام کنترلر و اکشن و ... از المان دریافت شده و با استفاده از اینترفیسی که ما اینجا تعریف کرده‌ایم، بررسی میکنیم که آیا این کاربر به این موارد دسترسی دارد یا خیر. در صورتیکه پاسخ برگشتی، از عدم اعتبار کاربر بگوید، گره مورد نظر حذف و یا در صورتیکه ویژگی data-type وجود داشته و مقدارش برابر disable باشد، آن المان غیرفعال خواهد شد. در نهایت کد تولیدی سند را به رشته تبدیل کرده و جایگزین خروجی فعلی میکنیم.
جهت تعریف سراسری آن در Global.asax داریم:
protected void Application_Start()
{
  GlobalFilters.Filters.Add(new AuthorizePage());
}
اشتراک‌ها
ساخت یک Timer با استفاده از setInterval برای صفحات وب

در بسیاری از مواقع شما نیاز دارید در صفحات وب خود از یک تایمر برای اجرا و تکرار دستورات استفاده کنید، در ASP.NET می‌توانستید از یک Timer در پنل‌های Ajax استفاده کنید اما در ASP.NET MVC باید این مشکل را به صورت زیر حل کنید....

ساخت یک Timer با استفاده از setInterval برای صفحات وب
مطالب
فعال سازی و پردازش Inline Add در jqGrid
پیشنیازها
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
اعتبارسنجی سفارشی سمت کاربر و سمت سرور در jqGrid


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


فعال سازی افزودن، ویرایش و حذف Inline


فعال سازی ویرایش و حذف Inline را پیشتر نیز بررسی کرده بودیم. تنها کافی است یک ستون جدید را با 'formatter: 'actions تعریف کنیم. به صورت خودکار، دکمه‌ی ویرایش، حذف، ذخیره سازی و لغو Inline ظاهر می‌شوند و همچنین بدون نیاز به کدنویسی بیشتری کار می‌کنند.
اما در کدهای ذیل اندکی این ستون را سفارشی سازی کرده‌ایم. در قسمت formatter آن، دکمه‌های edit و delete یک سطر جدید توکار اضافه شده را حذف کرده‌ایم. زیرا در این حالت خاص، وجود این دکمه‌ها ضروری نیستند. بهتر است در این حالت دکمه‌‌های save و cancel ظاهر شوند:
            $('#list').jqGrid({
                caption: "آزمایش نهم",
                // ....
                colModel: [
                    {
                        name: 'myac', width: 80, fixed: true, sortable: false,
                        resize: false,
                        //formatter: 'actions',
                        formatter: function (cellvalue, options, rowObject) {
                            if (cellvalue === undefined && options.rowId === "_empty") {
                                // در حالت نمایش ردیف توکار جدید دکمه‌های ویرایش و حذف معنی ندارند
                                options.colModel.formatoptions.editbutton = false;
                                options.colModel.formatoptions.delbutton = false;
                            }
                            return $.fn.fmatter.actions(cellvalue, options, rowObject);
                        },
                        formatoptions: {
                            keys: true,
                            afterSave: function (rowid, response) {
                            },
                            delbutton: true,
                            delOptions: {
                                url: "@Url.Action("DeleteUser", "Home")"
                            }
                        }
                    }
                ],
                //...
            }).navGrid(
                '#pager',
//...
)
                .jqGrid('gridResize', { minWidth: 400, minHeight: 150 })
                .jqGrid('inlineNav', '#pager',
                {
                    edit: true, add: true, save: true, cancel: true,
                    edittext: "ویرایش", addtext: "جدید", savetext: "ذخیره", canceltext: "لغو",
                    addParams: {
                        // اگر می‌خواهید ردیف‌های جدید در ابتدا ظاهر شوند، این سطر را حذف کنید
                        position: "last", //ردیف‌های جدید در آخر ظاهر می‌شوند
                        rowID: '_empty',
                        useDefValues: true,
                        addRowParams: getInlineNavParams(true)
                    },
                    editParams: getInlineNavParams(false)
                });
قسمتی که کار فعال سازی Inline Add را انجام می‌دهد، تعریف مرتبط با inlineNav است که به انتهای تعاریف متداول گرید اضافه شده‌است.
در اینجا 4 دکمه‌ی ویرایش، جدید، ذخیره و لغو، در نوار pager پایین گرید ظاهر خواهند شد (سمت چپ؛ سمت راست همان دکمه‌‌های نمایش فرم‌های پویا هستند).


سپس باید دو قسمت مهم addParams و editParams آن‌را مقدار دهی کرد.
در قسمت addParams، مشخص می‌کنیم که ID ردیف اضافه شده، مساوی کلمه‌ی _empty باشد. اگر به کدهای formatter ستون action دقت کنید، از این ID برای تشخیص افزوده شدن یک ردیف جدید استفاده شده‌است.
position در اینجا به معنای محل افزوده شدن یک ردیف خالی است. مقدار پیش فرض آن first است؛ یعنی همیشه در اولین ردیف گرید، این ردیف جدید اضافه می‌شود. در اینجا به last تنظیم شده‌است تا در پایین گرید و پس از رکوردهای موجود، نمایش داده شود.
useDefValues سبب استفاده از مقادیر پیش فرض تعریف شده در ستون‌های گرید در حین افزوده شدن یک ردیف جدید می‌گردد.
addRowParams و editParams هر دو ساختار تقریبا یکسانی دارند که به نحو ذیل تعریف می‌شوند:
        function getInlineNavParams(isAdd) {
            return {
                // استفاده از آدرس‌های مختلف برای حالات ویرایش و ثبت اطلاعات جدید
                url: isAdd ? '@Url.Action("AddUser", "Home")' : '@Url.Action("EditUser","Home")',
                key: true,
                restoreAfterError: false, // این مورد سبب می‌شود تا اعتبارسنجی سمت سرور قابل اعمال شود
                oneditfunc: function (rowId) {
                    // نمایش دکمه‌های ذخیره و لغو داخل همان سطر
                    $("#jSaveButton_" + rowId).show();
                    $("#jCancelButton_" + rowId).show();
                },
                successfunc: function () {
                    var $self = $(this);
                    setTimeout(function () {
                        $self.trigger("reloadGrid"); // دریافت کلید اصلی ردیف از سرور
                    }, 50);
                },
                errorfunc: function (rowid, response, stat) {
                    if (stat != 'error') // this.Response.StatusCode == 200
                        return;

                    var result = $.parseJSON(response.responseText);
                    if (result.success === false) {
                        //نمایش خطای اعتبار سنجی سمت سرور پس از ویرایش یا افزودن
                        $.jgrid.info_dialog($.jgrid.errors.errcap,
                            '<div class="ui-state-error">' + result.message + '</div>',
                            $.jgrid.edit.bClose,
                            { buttonalign: 'center' });
                    }
                }
            };
        }
در ابتدای کار مشخص می‌کنیم که آدرس‌های ذخیره سازی اطلاعات در سمت سرور برای حالت‌های Add و Edit کدام‌اند.
تنظیم restoreAfterError به false بسیار مهم است. اگر در سمت سرور خطای اعتبارسنجی گزارش شود و restoreAfterError مساوی true باشد (مقدار پیش فرض)، کاربر مجبور خواهد شد اطلاعات را دوباره وارد کند.
در روال رویدادگران oneditfunc دکمه‌ی save و cancel ردیف را که مخفی هستند، ظاهر می‌کنیم (مکمل formatter ستون action است).
در قسمت successfunc، پس از پایان موفقیت آمیز کار، متد reloadGrid را فراخوانی می‌کنیم. اینکار سبب می‌شود تا Id واقعی رکورد، از سمت سرور دریافت شود. از این Id برای ویرایش و همچنین حذف، استفاده خواهد شد. علت استفاده از setTimeout در اینجا این است که اندکی به DOM فرصت داده شود تا کارش به پایان برسد.
در قسمت errorfunc خطاهای اعتبارسنجی سفارشی سمت سرور را می‌توان دریافت و سپس توسط متد توکار info_dialog به کاربر نمایش داد.



یک نکته‌ی مهم در مورد ارسال خطاهای اعتبارسنجی از سمت سرور در حالت Inline Add

            if (_usersInMemoryDataSource.Any(
                    user => user.Name.Equals(postData.Name, StringComparison.InvariantCultureIgnoreCase)))
            {
                this.Response.StatusCode = 500; //این مورد برای افزودن داخل ردیف‌های گرید لازم است
                return Json(new { success = false, message = "نام کاربر تکراری است" }, JsonRequestBehavior.AllowGet);
            }
روال رویداد گردان errorfunc، اگر مقدار StatusCode بازگشتی از سمت سرور مساوی 200 باشد (حالت عادی و موفقیت آمیز)، مقدار stat مساوی error را باز نمی‌گرداند. به همین جهت است که در کدهای فوق، مقدار دهی this.Response.StatusCode را به 500 مشاهده می‌کنید. هر عددی غیر از 200 در اینجا به error تفسیر می‌شود. همچنین اگر این StatusCode سمت سرور تنظیم نشود، گرید فرض را بر موفقیت آمیز بودن عملیات گذاشته و successfunc را فراخوانی می‌کند.



مدیریت StatusCodeهای غیر از 200 در حالت کار با فرم‌های jqGrid

اگر هر دو حالت Inline Add و فرم‌های پویا را فعال کرده‌اید، بازگشت StatusCode = 500 سبب می‌شود تا دیگر نتوان خطاهای سفارشی سمت سرور را در بالای فرم‌ها به کاربر نمایش داد و در این حالت تنها یک internal server error را مشاهده خواهند کرد. برای رفع این مشکل فقط کافی است روال رویدادگران errorTextFormat را مدیریت کرد:
            $('#list').jqGrid({
                caption: "آزمایش نهم",
                //.........
            }).navGrid(
                '#pager',
                //enabling buttons
                { add: true, del: true, edit: true, search: false },
                //edit option
                {
                    //......... 
                    errorTextFormat: serverErrorTextFormat
                },
                //add options
                {
                    //......... 
                    errorTextFormat: serverErrorTextFormat
                },
                //delete options
                {
                    //......... 
                })
                .jqGrid('gridResize', { minWidth: 400, minHeight: 150 })
                .jqGrid('inlineNav', '#pager',
                {
                    //......... 
                });

        function serverErrorTextFormat (response) {
            // در حالتیکه وضعیت خروجی از سرور 200 نیست فراخوانی می‌شود
            var result = $.parseJSON(response.responseText);
            if (result.success === false) {
                return result.message;
            }
            return "لطفا ورودی‌های وارد شده را بررسی کنید";
        }
errorTextFormat تنها در حالتیکه StatusCode بازگشتی از طرف سرور مساوی 200 نیست، فراخوانی می‌شود. در اینجا می‌توان response دریافتی را آنالیز و سپس پیام خطای سفارشی آن‌را جهت نمایش در فرم‌های پویای گرید، بازگشت داد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
jqGrid09.zip
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
روش رندر یک View در ASP.NET MVC، بر مبنای اطلاعاتی است که از کنترلر، در اختیار View آن قرار می‌گیرد. اما گاهی از اوقات نیاز است بعضی از قسمت‌های صفحه همواره نمایش داده شوند (مانند نمایش تعداد کاربران آنلاین، سخن روز، منوهای کنار صفحه و امثال آن). یک راه حل برای این مساله، اضافه کردن اطلاعات مورد نیاز View در ViewModel ارائه شده‌ی توسط کنترلر است. هرچند این روش کار می‌کند اما پس از مدتی به ViewModel هایی خواهیم رسید که تشکیل شده‌اند از چندین و چند خاصیت اضافی که الزاما مرتبط با تعریف آن ViewModel نیستند. راه حل بهتر، قرار دادن قسمت‌های مشترک صفحات در فایل layout برنامه است؛ اما فایل layout، به سادگی نمی‌تواند از دایرکتیو model@ برای مشخص سازی مدل و یا مدل‌های مورد نیاز خود استفاده کند (هر چند ممکن است؛ اما بیش از اندازه پیچیده خواهد شد).
در نگارش‌های پیشین ASP.NET MVC، یک چنین مسائلی را با معرفی Child Actionها
    public partial class SidebarMenuController : Controller
    {
        const int Min15 = 900;

        [ChildActionOnly]
        [OutputCache(Duration = Min15)]
        public virtual ActionResult Index()
        {
            return PartialView("_SidebarMenu");
        }
    }
و سپس نمایش آن‌ها توسط Html.RenderAction در فایل layout برنامه، حل می‌کنند. در ASP.NET Core، جایگزین Child Actionها، مفهوم جدیدی است به نام View Components.


یک مثال: تهیه‌ی اولین View Component

ساختار یک View Component، بسیار شبیه است به ساختار یک Controller، اما با عملکردی محدود. به همین جهت کار تعریف آن با افزودن یک کلاس سی‌شارپ شروع می‌شود و این کلاس را می‌توان در پوشه‌ای به نام ViewComponents در ریشه‌ی پروژه قرار داد (اختیاری).


سپس برای نمونه، کلاس ذیل را به این پوشه اضافه کنید:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Core1RtmEmptyTest.Services;
 
namespace Core1RtmEmptyTest.ViewComponents
{
    public class SiteCopyright : ViewComponent
    {
        private readonly IMessagesService _messagesService;
 
        public SiteCopyright(IMessagesService messagesService)
        {
            _messagesService = messagesService;
        }
 
        public IViewComponentResult Invoke(int numberToTake)
        {
            var name = _messagesService.GetSiteName();
            return View(viewName: "Default", model: name);
        }
 
        //public async Task<IViewComponentResult> InvokeAsync(int numberToTake)
        //{
        //    return View();
        //}
    }
}
همانطور که پیشتر نیز عنوان شد، تزریق وابستگی‌ها در تمام قسمت‌های ASP.NET Core در دسترس هستند. در اینجا نیز از سرویس MessagesService بررسی شده‌ی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویس‌ها و تزریق وابستگی‌ها» برای نمایش نام سایت استفاده می‌کنیم.

ساختار کلی یک کلاس ViewComponent شامل دو جزء اصلی است:
الف) از کلاس پایه ViewComponent مشتق می‌شود. به این ترتیب توسط ASP.NET Core قابل شناسایی خواهد شد.
ب) دارای متد Invoke ایی است که بجای Html.RenderAction در نگارش‌های پیشین ASP.NET MVC، قابل فراخوانی است. این متد یک View را باز می‌گرداند.
ج) در اینجا امکان تعریف نمونه‌ی Async متد Invoke نیز وجود دارد (برای مثال جهت کار با متدهای Async بانک اطلاعاتی).
روش فراخوانی این متدها نیز به این صورت است: ابتدا به دنبال نمونه‌ی async می‌گردد. اگر یافت شد، همینجا کار خاتمه می‌یابد. اگر یافت نشد، نمونه‌ی sync یا معمولی آن فراخوانی می‌شود و اگر این هم یافت نشد، یک استثناء صادر خواهد شد.
د) متد Invoke می‌تواند دارای پارامترهای دلخواهی نیز باشد و حالت پیش فرض آن بدون پارامتر است.

روش یافتن یک view component توسط ASP.NET Core به این صورت است:
الف) این کلاس باید عمومی بوده و همچنین abstract نباشد.
ب) «یکی» از مشخصه‌های ذیل را داشته باشد:
1) نامش به ViewComponent ختم شده باشد.
2) از کلاس ViewComponent ارث بری کرده باشد.
3) با ویژگی ViewComponent مزین شده باشد.


نحوه و محل تعریف View یک View Component

پس از تعریف کلاس ViewComponent مورد نظر، اکنون نیاز است View آن‌را اضافه کرد. روش یافتن این Viewها توسط ASP.NET Core نیز بر این مبنا است که
الف) اگر این View Component عمومی و سراسری است، باید درون پوشه‌ی shared، پوشه‌ی جدیدی را به نام Components ایجاد کرده و سپس ذیل این پوشه، بر اساس نام کلاس ViewComponent، یک زیر پوشه‌ی دیگر را ایجاد و داخل آن، View مدنظر را اضافه کرد (تصویر ذیل).
 /Views/Shared/Components/[NameOfComponent]/Default.cshtml
ب) اگر این View Component تنها باید از طریق Viewهای یک کنترلر خاص قابل دسترسی باشند، زیر پوشه‌ی Component یاد شده را ذیل پوشه‌ی View همان کنترلر قرار دهید (و آن‌را از قسمت Shared خارج کنید).
 /Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml


یک نکته: اگر نام کلاسی به ViewComponent  ختم شده بود، نیازی نیست تا ViewComponent  را هم در حین ساخت پوشه‌ی آن ذکر کرد.


نحوه‌ی استفاده‌ی از View Component تعریف شده و ارسال پارامتر به آن

و در آخر برای استفاده‌ی از این View Component تعریف شده، به فایل layout برنامه مراجعه کرده و آن‌را به نحو ذیل فراخوانی کنید:
 <footer>
    <p>@await Component.InvokeAsync("SiteCopyright", new { numberToTake = 5 })</p>
</footer>
اولین پارامتر متد InvokeAsync، همان نام کلاس View Component است. اگر خواستید پارامتر(های) دلخواهی را به متد Invoke کلاس View Component ارسال کنید (مانند پارامتر int numberToTake در مثال فوق)، آن‌را در همینجا می‌توان ذکر کرد (با فرمت dictionary و یا  anonymous type).

یک نکته: متدهای قدیمی Component.Invoke و Component.Renderدر اینجا حذف شده‌اند (اگر مقالات پیش از RTM را مطالعه کردید) و روش توصیه شده‌ی در اینجا، کار با متدهای async است.


تفاوت‌های View Components با Child Actions نگارش‌های پیشین ASP.NET MVC

پارامترهای یک View Component از طریق یک HTTP Request تامین نمی‌شوند و همانطور که ملاحظه کردید در همان زمان فراخوانی آن‌ها به صورت مستقیم فراهم خواهند شد. بنابراین مباحث model binding در اینجا دیگر وجود خارجی ندارند. همچنین View Components جزئی از طول عمر یک کنترلر نیستند. بنابراین اکشن فیلترهای مختلف تعریف شده، تاثیری را بر روی آن‌ها نخواهند داشت (این مشکلی بود که با Child Actions در نگارش‌های قبلی مشاهده می‌شد). همچنین View Components به صورت مستقیم از طریق درخواست‌های HTTP قابل دسترسی نیستند. به علاوه Child actions قدیمی، از فراخوانی‌های async پشتیبانی نمی‌کنند.
زمانیکه کلاسی از کلاس پایه ViewComponent ارث بری می‌کند، تنها به این خواص عمومی از درخواست HTTP جاری دسترسی خواهد داشت:
[ViewComponent]
public abstract class ViewComponent
{
   protected ViewComponent();
   public HttpContext HttpContext { get; }
   public ModelStateDictionary ModelState { get; }
   public HttpRequest Request { get; }
   public RouteData RouteData { get; }
   public IUrlHelper Url { get; set; }
   public IPrincipal User { get; }

   [Dynamic]  
   public dynamic ViewBag { get; }
   [ViewComponentContext]
   public ViewComponentContext ViewComponentContext { get; set; }
   public ViewContext ViewContext { get; }
   public ViewDataDictionary ViewData { get; }
   public ICompositeViewEngine ViewEngine { get; set; }

   //...
}


فراخوانی Ajax ایی یک View Component

در ASP.NET Core، یک اکشن متد می‌تواند خروجی ViewComponent نیز داشته باشد و این تنها روشی است که می‌توان یک View Component را از طریق درخواست‌های HTTP، مستقیما قابل دسترسی کرد:
public IActionResult AddURLTest()
{
   return ViewComponent("AddURL");
}
در این حالت می‌توان این اکشن متد را به صورت Ajax ایی نیز بارگذاری و به صفحه اضافه کرد:
$(document).ready (function(){
    $("#LoadSignIn").click(function(){
         $('#UserControl').load("/Home/AddURLTest");
    });
});


امکان بارگذاری View Components از اسمبلی‌های دیگر نیز وجود دارد

در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 10 - بررسی تغییرات Viewها» روش دسترسی به Viewهای برنامه را که در اسمبلی آن قرار گرفته بودند، بررسی کردیم. دقیقا همان روش در مورد view components نیز صادق است و کاربرد دارد. جهت یادآوری، این مراحل باید طی شوند:
الف) اسمبلی ثالث حاوی View Component‌های برنامه باید ارجاعاتی را به ASP.NET Core و قابلیت‌های Razor آن داشته باشد:
"dependencies": {
   "NETStandard.Library": "1.6.0",
   "Microsoft.AspNetCore.Mvc": "1.0.0",
   "Microsoft.AspNetCore.Razor.Tools": {
   "version": "1.0.0-preview2-final",
   "type": "build"
  }
},
"tools": {
   "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
}
ب) محل قرارگیری viewهای این اسمبلی ثالث نیز همانند قسمت «نحوه و محل تعریف View یک View Component» مطلب جاری است و تفاوتی نمی‌کند. فقط برای  قرار دادن این Viewها در اسمبلی برنامه باید گزینه‌ی embed را مقدار دهی کرد:
"buildOptions": {
   "embed": "Views/**/*.cshtml"
}
ج) مرحله‌ی آخر هم معرفی این اسمبلی ثالث، به RazorViewEngineOptions به صورت یک EmbeddedFileProvider جدید است. در این مثال، ViewComponentLibrary نام فضای نام این اسمبلی است.
public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
   //Get a reference to the assembly that contains the view components
   var assembly = typeof(ViewComponentLibrary.ViewComponents.SimpleViewComponent).GetTypeInfo().Assembly;
   //Create an EmbeddedFileProvider for that assembly
   var embeddedFileProvider = new EmbeddedFileProvider(assembly,"ViewComponentLibrary");
   //Add the file provider to the Razor view engine
   services.Configure<RazorViewEngineOptions>(options =>
   {
      options.FileProviders.Add(embeddedFileProvider);
   });
د) جهت رفع تداخلات احتمالی این اسمبلی با سایر اسمبلی‌ها بهتر است ویژگی ViewComponent را به همراه نامی مشخص ذکر کرد (در حین تعریف کلاس View Component):
 [ViewComponent(Name = "ViewComponentLibrary.Simple")]
public class SimpleViewComponent : ViewComponent
و در آخر فراخوانی این View Component بر اساس این نام صورت خواهد گرفت:
 @await Component.InvokeAsync("ViewComponentLibrary.Simple", new { number = 5 })
مطالب
معرفی افزونه‌های مفید VSCode جهت کار با Angular
VSCode یکی از بهترین ادیتورهایی است که از آن می‌توان برای توسعه‌ی برنامه‌های Angular استفاده کرد و در این بین افزونه‌های ویژه‌ای جهت کار با Angular برای آن تدارک دیده شده‌اند که در ادامه تعدادی از مهم‌ترین‌های آن‌ها را بررسی می‌کنیم.


Angular Essentials

این افزونه گروهی از مهم‌ترین افزونه‌های موجود را به صورت بسته بندی شده ارائه می‌دهد و با نصب آن، تعدادی از افزونه‌هایی را که در ادامه نامبرده خواهند شد، به صورت یکجا و خودکار دریافت خواهید کرد.


Angular Language Service

نگارش‌های اخیر Angular به همراه یک سرویس زبان نیز می‌باشند که به ادیتورهای مختلف این امکان را می‌دهد تا توسط این ویژگی بتوانند قابلیت‌های ویرایشی بهتری را جهت برنامه‌های Angular ارائه کنند. برای مثال ویرایش مطلوب قالب‌های کامپوننت‌های Angular و استفاده‌ی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمی‌شود. اکنون به کمک سرویس زبان Angular و افزونه‌ی ویژه‌ی آن برای VSCode که توسط تیم اصلی Angular توسعه یافته‌است، امکان ویرایش غنی قالب‌های HTML ایی آن فراهم شده‌است. این افزونه یک چنین قابلیت‌هایی را فراهم می‌کند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:


ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شده‌ی در قالب.
د) Quick info که با نزدیک کردن اشاره‌گر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش می‌دهد.


angular2-inline

علاوه بر افزونه‌ی سرویس زبان‌های Angular، این افزونه نیز قابلیت درک قالب‌های inline کامپوننت‌ها را داشته و به همراه syntax highlighting و همچنین Intellisense است.


Auto Import

حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونه‌ی Auto Import با بررسی ماژول‌های موجود و فراهم آوردن Intellisense ایی بر اساس آن‌ها، این‌کار را ساده‌تر می‌کند:


بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.


TSLint

این افزونه ابزار TSLint را با VSCode یکپارچه می‌کند. بنابراین نیاز است پیش از نصب این افزونه، وابستگی‌های ذیل را نیز به صورت سراسری نصب کرد:
 > npm install -g tslint typescript
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونه‌هایی مانند ری‌شارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه می‌دهد.
تعدادی از امکانات آن‌را پس از نصب، با فشردن دکمه‌ی F1 می‌توان مشاهده کرد:


برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شونده‌ی توسط این افزونه را فراهم می‌کند و اگر برنامه‌ی خود را توسط Angular CLI ایجاد کرده‌اید، این فایل هم اکنون در ریشه‌ی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامه‌ی جدید» بیشتر توضیح داده شده‌است و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچه‌ای مشاهده خواهید کرد.



Angular v4 TypeScript Snippets

سیستم کار VSCode مبتنی بر ایجاد فایل‌های خالی است و مفهوم قالب‌های از پیش آماده‌ی فایل‌ها در آن وجود ندارد. اما با کمک Code Snippets می‌توان این خلاء را پر کرد. افزونه‌ی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شده‌است و زمانیکه حروف -a یا -rx را در صفحه تایپ می‌کنید، منویی ظاهر خواهد شد که توسط آن می‌توان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژه‌های Angular را تهیه کرد.



Path Intellisense

این افزونه مسیر فایل‌های موجود را به صورت یک Intellisense ارائه می‌کند و به این صورت به سادگی می‌توان مسیرهای اسکریپت‌ها و یا شیوه‌نامه‌ها را در ادیتور انتخاب و وارد کرد.

پاسخ به بازخورد‌های پروژه‌ها
مشکل با نوشتن تابع تجمعی سفارشی(از طریق پیاده سازی IAggregateFunction)
با استفاده از یک فیلد محاسباتی خیلی ساده می‌توانید هر دو مورد را پیاده سازی کنید:
ابتدا قبل از شروع گزارش یک متغیر تعریف کنید:
 long lastBalance = 0;
بعد، از این متغیر که با هر بار فراخوانی callback رندر ردیف جاری به روز خواهد شد، در یک فیلد محاسباتی استفاده نمائید:
 column.CalculatedField(
                         list =>
                         {
                             if (list == null) return string.Empty;
                             var currentRowCredit = (long)list.GetValueOf<Payment>(x => x.Credit);
                             var currentRowDebit = (long)list.GetValueOf<Payment>(x => x.Debit);
                             lastBalance = lastBalance + currentRowCredit - currentRowDebit;
                             return lastBalance;
                         });
جزو مثال‌های این کتابخانه است:
AccountingBalanceColumn

برای نمایش بدهکار و بستانکار هم به همین نحو عمل کنید. یک فیلد محاسباتی اضافه کنید. در ادامه، list.GetValueOf مقادیر فیلدهای ستون جاری را به شما می‌دهد. این‌ها را با هم مقایسه کرده و سپس بر این اساس بدهکار یا بستانکار را بازگشت دهید.