مطالب
مدیریت همزمانی و طول عمر توالی‌های Reactive extensions در یک برنامه‌ی دسکتاپ
پس از معرفی و مشاهده‌ی نحوه‌ی ایجاد توالی‌ها در Rx، بهتر است با نمونه‌ای از نحوه‌ی استفاده از آن در یک برنامه‌ی WPF آشنا شویم.
بنابراین ابتدا دو بسته‌ی Rx-Main و Rx-WPF را توسط نیوگت، به یک برنامه‌ی جدید WPF اضافه کنید:
 PM> Install-Package Rx-Main
PM> Install-Package Rx-WPF
فرض کنید قصد داریم محتوای یک فایل حجیم را به نحو ذیل خوانده و توسط Rx نمایش دهیم.
        private static IEnumerable<string> readFile(string filename)
        {
            using (TextReader reader = File.OpenText(filename))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Thread.Sleep(100);
                    yield return line;
                }
            }
        }
در اینجا برای ایجاد یک توالی IEnumerable ، از yield return استفاده شده‌است. همچنین Thread.Sleep آن جهت بررسی قفل شدن رابط کاربری در حین خواندن فایل به عمد قرار گرفته است.
UI برنامه نیز به نحو ذیل است:
<Window x:Class="WpfApplicationRxTests.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition  Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Name="btnGenerateSequence" Click="btnGenerateSequence_Click">Generate sequence</Button>
        <ListBox Grid.Row="1" Name="lstNumbers"  />
        <Button Grid.Row="2" IsEnabled="False" Name="btnStop" Click="btnStop_Click">Stop</Button>
    </Grid>
</Window>
با این کدها
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;

namespace WpfApplicationRxTests
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private static IEnumerable<string> readFile(string filename)
        {
            using (TextReader reader = File.OpenText(filename))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Thread.Sleep(100);
                    yield return line;
                }
            }
        }

        private IDisposable _subscribe;
        private void btnGenerateSequence_Click(object sender, RoutedEventArgs e)
        {
            btnGenerateSequence.IsEnabled = false;
            btnStop.IsEnabled = true;

            var items = new ObservableCollection<string>();
            lstNumbers.ItemsSource = items;
            _subscribe = readFile("test.txt").ToObservable()
                               .SubscribeOn(ThreadPoolScheduler.Instance)
                               .ObserveOn(DispatcherScheduler.Current)
                               .Finally(finallyAction: () =>
                               {
                                   btnGenerateSequence.IsEnabled = true;
                                   btnStop.IsEnabled = false;
                               })
                               .Subscribe(onNext: line =>
                               {
                                   items.Add(line);
                               },
                               onError: ex => { },
                               onCompleted: () =>
                               {
                                   //lstNumbers.ItemsSource = items;
                               });
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            _subscribe.Dispose();
        }
    }
}

توضیحات

حاصل متد readFile را که یک توالی معمولی IEnumerable را ایجاد می‌کند، توسط فراخوانی متد ToObservable، تبدیل به یک خروجی IObservable کرده‌ایم تا بتوانیم هربار که سطری از فایل مدنظر خوانده می‌شود، نسبت به آن واکنش نشان دهیم.
متد SubscribeOn مشخص می‌کند که این توالی Observable باید بر روی چه تردی اجرا شود. در اینجا از ThreadPoolScheduler.Instance استفاده شده‌است تا در حین خواندن فایل، رابط کاربری در حالت هنگ به نظر نرسد و ترد جاری (ترد اصلی برنامه) به صورت خودکار آزاد گردد.
از متد ObserveOn با پارامتر DispatcherScheduler.Current استفاده کرده‌ایم، تا نتیجه‌ی واکنش‌های به خوانده شدن سطرهای یک فایل مفروض، در ترد اصلی برنامه صورت گیرد. در غیر اینصورت امکان کار کردن با عناصر رابط کاربری در یک ترد دیگر وجود نخواهد داشت و برنامه کرش می‌کند.
در قسمت‌های قبل، صرفا متد Subscribe را مشاهده کرده بودید. در اینجا از متد Finally نیز استفاده شده‌است. علت اینجا است که اگر در حین خواندن فایل خطایی رخ دهد، قسمت onError متد Subscribe اجرا شده و دیگر به پارامتر onCompleted آن نخواهیم رسید. اما متد Finally آن همیشه در پایان عملیات اجرا می‌شود.
خروجی حاصل از متد Subscribe، از نوع IDisposable است. Rx به صورت خودکار پس از پردازش آخرین عنصر توالی، این شیء را Dispose می‌کند. اینجا است که callback متد Finally یاد شده فراخوانی خواهد شد. اما اگر در حین خواندن یک فایل طولانی، کاربر علاقمند باشد تا عملیات را متوقف کند، تنها کافی است که به صورت صریح، این شیء را Dispose نماید. به همین جهت است که مشاهده می‌کنید، این خروجی به صورت یک فیلد تعریف شده‌است تا در متد Stop بتوانیم آن‌را در صورت نیاز Dispose کنیم.


مثال فوق را از اینجا نیز می‌توانید دریافت کنید:
WpfApplicationRxTests.zip
  
اشتراک‌ها
پیاده‌سازی فرآیند Impersonation در ASP.NET Core

Impersonation Process

Impersonation is when an admin user is logged in with the same privileges as a user, but without knowing their password or other credentials. I’ve used this in couple applications and it was invaluable for support cases and debugging user permissions. 

[Authorize(Roles = "Admin")] // <-- Make sure only admins can access this 
public async Task<IActionResult> ImpersonateUser(String userId)
{
    var currentUserId = User.GetUserId();

    var impersonatedUser = await _userManager.FindByIdAsync(userId);

    var userPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonatedUser);

    userPrincipal.Identities.First().AddClaim(new Claim("OriginalUserId", currentUserId));
    userPrincipal.Identities.First().AddClaim(new Claim("IsImpersonating", "true"));

    // sign out the current user
    await _signInManager.SignOutAsync();

    await HttpContext.Authentication.SignInAsync(cookieOptions.ApplicationCookieAuthenticationScheme, userPrincipal);

    return RedirectToAction("Index", "Home");
}


پیاده‌سازی فرآیند Impersonation در ASP.NET Core
مطالب
توصیف فیلدها توسط Tag Helper و Data annotation

همه ما با DisplayAttribute در DataAnnotaion آشنا هستیم. چیزی شبیه زیر برای یک موجودیت:

public class Student{
    [Display(Name="نام خانوادگی")]
    public string FamilyName { get; set;}
}

با استفاده از tag helper ای به نام asp-for می‌توان متادیتای Name را به کاربر، در سمت رابط کاربری نشان داد؛ برای مثال:

<label asp-for="FamilyName"></label>

و یا موقع اعتبارسنجی می‌توان به جای نشان دادن نام FamilyName از نام مفهوم‌تری مانند نام خانوادگی استفاده نمود.

چه خوب بود اگر می‌شد علاوه بر نام، توصیفی از فیلد نیز برای آن در این قسمت وجود داشته باشد؛ به عبارت دیگر اگر کد زیر را داشتیم:

[Display(
     Name = "نام خانوادگی",
     Description = "بهتر است فقط در اینجا نام خانوادگی شخص وارد شود")]
public string FamilyName{ get; set; }

بتوان از tag helper ای مانند زیر استفاده نمود:

<span asp-description-for="FamilyName"></span>

که در نهایت چنین خروجی html ای داشته باشیم:

<span>بهتر است فقط در اینجا نام خانوادگی شخص وارد شود</span>

برای این منظور می‌توان از کلاس زیر بهره برد:

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

[HtmlTargetElement("div", Attributes = ForAttributeName)]
[HtmlTargetElement("p", Attributes = ForAttributeName)]
[HtmlTargetElement("span", Attributes = ForAttributeName)]
public sealed class DescriptionForTagHelper : TagHelper
{
    private const string ForAttributeName = "asp-description-for";

    [HtmlAttributeName(ForAttributeName)] 
    public ModelExpression For { get; set; } = default!;

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (output == null)
        {
            throw new ArgumentNullException(nameof(output));
        }

        var description = For.Metadata.Description;
        if (description != null)
        {
            // Do not update the content if another tag helper
            // targeting this element has already done so.
            if (!output.IsContentModified)
            {
                var childContent = await output.GetChildContentAsync();
                if (childContent.IsEmptyOrWhiteSpace)
                {
                    output.Content.SetHtmlContent(description);
                }
                else
                {
                    output.Content.SetHtmlContent(childContent);
                }
            }
        }
    }
}

کلاس DescriptionForTagHelper از کلاس پایه TagHelper ارث بری نموده است و متد ProcessAsync آن به نحوی که  asp-description-for را بپذیرد override شده است.

حوزه اعمال این tag helper به span، p و div محدود شده است؛ اما می‌توان با گذاشتن یک ستاره (*) آن را به کل المان‌های html اعمال کرد.

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
سلام معادل 
@using Menu.Helper
@model IEnumerable<.Models.Entities.Category>
@ShowTree(Model)

@helper ShowTree(IEnumerable<Menu.Models.Entities.Category> categories)
{
    foreach (var item in categories)
    {
    <lidropdown-submenu" : "")">

        @Html.ActionLink(item.Name, actionName: "Category", controllerName: "Product", routeValues: new { Id = item.Id, productName = item.Name.ToSeoUrl() }, htmlAttributes: null)
        @if (item.Children.Any())
        {
            <ul>
                @ShowTree(item.Children)
            </ul>
                }
    </li>

        }
}
با استفاده از @function امکان پذیر هست؟  
@model IEnumerable<TR_Mahmodabad.Context.Entities.Navbar>

@functions
{
    public void ShowTree(IEnumerable<Navbar> navbars)
    {
        foreach (var item in navbars)
        {
            <lidropdown-submenu" : "")">
                @Html.ActionLink(item.Title, actionName: "", controllerName: "Product", routeValues: new {Id = item.Id, title = item.Title},htmlAttributes:null)
                @if (item.HasChiled)
                {
                    <ul>
                        @ShowTree(item.Children) // خطا
                    </ul>
                }
            </li>
        }
    }
}
مطالب
نحوه کار Expression و ایجاد یک DynamicFilter
ساختار Expression‌ها شبیه به ساختار یک درخت است. به عنوان مثال زمانیکه شما یک فیلتر ساده را مانند دستور زیر اجرا میکنید:
Expression<Func<string, bool>> f = s => s.Length < 5;
Expression ایجاد شده از فیلتر شما به صورت زیر میباشد:

منبع : کتاب C# 8 in a Nutshell 

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

BinaryExpression شامل سه قسمت مهم Left , Right و NodeType میباشد. برای فیلتر بالا، مقدار پراپرتی Left برابر s.Length می‌باشد و پراپرتی Right شامل مقدار 5 و مقدار NodeType هم برابر LessThan میباشد. یعنی فیلتر بالا به یک درخت تبدیل شده که نود اصلی آن LessThan است و دو مقدار Left و Right را باهم مقایسه میکند. اما اگر یک شرط دیگر را به فیلتر بالا اعمال کنیم، ساختار Expression کمی تغییر میکند. برای مثال: 

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;

Expression ایجاد شده برای این فیلتر شامل همان ساختار قبلی است؛ اما با این تغییر که هر کدام از پراپرتی‌های Right و Left، خود یک BinaryExpression شده‌اند و مقدار NodeType اصلی از LessThan به AndAlso تغییر پیدا کرده‌است. Expression ایجاد شده از فیلتر بالا ( filter.Body ) به این صورت است که پراپرتی Left آن برابر است با یک BinaryExpression که مقدار NodeType آن برابر است با GreaterThan و پراپرتی Left آن شامل s.Length میباشد و پراپرتی Right آن برابر 5 میباشد. همچنین پراپرتی Right مربوط به filter.Body برابر یک ExpressionBinary است که مقدار NodeType آن برابر است با LessThan و پراپرتی Left آن برابر s.Length است و پراپرتی Right آن برابر 45 میباشد.

filter.Body شبیه به تصویر زیر میباشد :

اگر بخواهیم خودمان یک Expression tree را ایجاد کنیم، باید از پایین‌ترین نود آن شروع کنیم. یعنی ابتدا باید پراپرتی Left و Right را ایجاد کنیم و سپس این دو پراپرتی را با هم مقایسه کنیم (NodeType). در کد زیر Expression مربوط به فیلتر بالا را نوشته‌ایم:

ParameterExpression parameterExpression = Expression.Parameter(typeof(string));
MemberExpression memberExpression = Expression.Property(parameterExpression, "Length");

ConstantExpression greaterThanConstantExpression = Expression.Constant(5);
BinaryExpression greaterThanComparison = Expression.GreaterThan(memberExpression, greaterThanConstantExpression);
var greaterThan = Expression.Lambda<Func<string, bool>>(greaterThanComparison, parameterExpression);

ConstantExpression lessThanConstantExpression = Expression.Constant(45);
BinaryExpression lessThanComparsion = Expression.LessThan(memberExpression, lessThanConstantExpression);
var lessThan = Expression.Lambda<Func<string, bool>>(lessThanComparsion, parameterExpression);

var param = Expression.Parameter(typeof(string), "x");
var body = Expression.AndAlso(
            Expression.Invoke(greaterThan, param),
            Expression.Invoke(lessThan, param)
        );
Expression<Func<string, bool>> filter = Expression.Lambda<Func<string, bool>>(body, param);

ParameterExpression : نوع پارامتری را که میخواهیم روی آن شرط را روی آن اعمال کنیم، مشخص کرده‌ایم.

MemberExpression  : پراپرتی Length را معرفی کرده‌ایم که قرار است شرطی بر روی این پراپرتی اعمال شود.

ConstantExpression   : مقدار ثابتی که پراپرتی MemeberExpression قرار است با آن مقایسه شود.

BinaryExpression   : نود تایپ را مشخص کرده‌ایم که برابر است با GreaterThan.

سپس Expression مربوط به هرکدام را در greaterThan و lessThan ایجاد کرده‌ایم و این دو را باهم And کرده و در متغییر body قرار داده‌ایم و در نهایت filter را با دستور Expression.Lambda ایجاد کرده‌ایم که برابر است با :

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;


ساخت یک داینامیک فیلتر

در ادامه میخواهیم یک داینامیک فیلتر را ایجاد کنیم که به طور مثال برنامه نویس از سمت فرانت‌اند بتواند فیلتر‌های ساده‌ای را اعمال کند. برای این کار یک کلاس برای فیلتر ایجاد میکنیم :

public class DynamicModel
{
    public string Name { get; set; }
    public string Comparison { get; set; }
    public object Data { get; set; }
}

پراپرتی Data مقداری است که باید با آن مقایسه انجام شود.

Comparison نوع عملیات را مشخص میکند مانند : Equal, LessThan, GreaterThan و... .

پراپرتی Name نام پراپرتی است که باید شرط روی آن اعمال شود.

کلاس ثابت ها:

public static class ComparisonConstant
{
    public const string LessThan = "LesThan";
    public const string LessThanEqual = "LesThanEqual";
    public const string GreaterThan = "GreaterThan";
    public const string GreaterThanEqual = "GreaterThanEqual";
    public const string Equal = "Equal";
    public const string NotEqual = "NotEqual";
}

ساخت اکستنشن متد:

public static IQueryable<TModel> DynamicFilter<TModel>(this IQueryable<TModel> iqueryable, IEnumerable<DynamicModel> dynamicModel)
{
    return iqueryable.Where(Filter<TModel>(dynamicModel));
}  
public static Expression<Func<TModel, bool>> Filter<TModel>(IEnumerable<DynamicModel> dynamicModel)
{
    Expression<Func<TModel, bool>> result = a => true;
    foreach (var item in dynamicModel)
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(TModel));
        MemberExpression memberExpression = Expression.Property(parameterExpression, item.Name);
        ConstantExpression constantExpression = Expression.Constant(item.Data);
        BinaryExpression comparison = GetBinaryExpression(item.Comparison, memberExpression, constantExpression);
        var expression = Expression.Lambda<Func<TModel, bool>>(comparison, parameterExpression);
        var param = Expression.Parameter(typeof(TModel), "x");
        var body = Expression.AndAlso(
                    Expression.Invoke(result, param),
                    Expression.Invoke(expression, param)
                );
        result = Expression.Lambda<Func<TModel, bool>>(body, param);
    }
    return result;
}

ورودی این مدل، لیستی از DynamicModel میباشد که به ازای هر کدام از آیتم‌ها، یک BinaryExpression ایجاد میکند و آن را با result تعریف شده And میکند. یعنی تمامی آیتم‌های ارسال شده باهم And میشوند.

متد GetBinaryExpression بر اساس مقدار فیلد Comparison که از سمت فرانت ارسال میشود، کار میکند:

private static BinaryExpression GetBinaryExpression(string comparison, MemberExpression memberExpression, ConstantExpression constantExpression)
{
    switch (comparison)
    {
        case ComparisonConstant.Equal:
            return Expression.Equal(memberExpression, constantExpression);
        case ComparisonConstant.LessThan:
            return Expression.LessThan(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThan:
            return Expression.GreaterThan(memberExpression, constantExpression);
        case ComparisonConstant.NotEqual:
            return Expression.NotEqual(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThanEqual:
            return Expression.GreaterThanOrEqual(memberExpression, constantExpression);
        case ComparisonConstant.LessThanEqual:
            return Expression.LessThanOrEqual(memberExpression, constantExpression);
        default:
            return null;
    }
}

 کلاس Category را در نظر بگیرید که شامل دو پراپرتی Title و Id میباشد و میخواهیم از این داینامیک فیلتر، برای فیلتر کردن دیتاها استفاده کنیم از سمت فرانت‌اند. اگر از سمت فرانت‌اند چنین دیتایی ارسال شود:

[
   {
      "Name":"Title",
      "Comparison":"Equal",
      "Data":"Hi"
   },
   {
      "Name":"Id",
      "Comparison":"LesThanEqual",
      "Data": 100
   }
]

تمامی رکوردهایی که مقدار پراپرتی Title آنها برابر Hi باشد و Id آن کوچکتر مساوی 100 باشد، از دیتابیس خوانده میشود.

var categories = _dbContext.Categories
                         .DynamicFilter(filter)//filter => IEnumerable<DynamicModel>
                         .ToList();

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

بازخوردهای دوره
استفاده از Async و Await در برنامه‌های ASP.NET MVC
- پیشنیاز مطالعه قسمت جاری، مطالعه 6 قسمت اول این دوره است.
- «همزمان اجرا میشه»
خیر. متدهای Async واقعی مثل نمونه ارائه شده در EF غیرهمزمان اجرا می‌شوند. یعنی، ترد جاری را آزاد کرده و ASP.NET می‌تواند از آن ترد برای پاسخ دهی به یک درخواست رسیده دیگر استفاده کند.
- «باید منتظر پاسخ از db بمونه»
استفاده از await و async سبب بازنویسی بدنه متد توسط یک state machine در پشت صحنه می‌شوند. یعنی اینطور نیست که روش اجرای آن blocking است و تا رسیدن پاسخ از بانک اطلاعاتی، از این ترد دیگر نمی‌شود استفاده کرد. جایی که await فراخوانی می‌شود، ترد جاری برای استفاده بعدی آزاد خواهد شد. در ادامه مابقی کدها تبدیل به یک IEnumerator می‌شوند که هر دستور آن شامل یک yield return است. هر مرحله که تمام شد، MoveNext این IEnumerator فراخوانی می‌شود تا به مرحله‌ی بعدی برسد. به این روش استفاده از coroutines هم گفته می‌شود که در سی شارپ 5، کامپایلر کار تولید کدهای آن‌را انجام می‌دهد. برای مطالعه بیشتر:
انجام پی در پی اعمال Async به کمک Iterators - قسمت اول  
انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم
- «چون تا فایل آپلود نشه ذخیره آدرس تو db بی معنیه»
ذخیره آدرس هم یک قسمت از کار است و اتفاقا وابسته به سیستم جاری هم نیست. وابسته است به یک بانک اطلاعاتی که خارج از مرزهای سیستم، به صورت مستقل در حال فعالیت است (عموما البته؛ مثلا اگر از SQL Server استفاده می‌شود).
برای ذخیره فایل‌ها در سیستم هم متدهای Async به کلاس Stream در دات نت 4.5 اضافه شده‌اند؛ مثل WriteAsync . در این حالت هم می‌توان از await WriteAsync برای ذخیره اطلاعات و بازهم آزاد کردن ترد جاری استفاده کرد.
مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت دوم - پیاده سازی سمت کلاینت
در قسمت قبل، کار پیاده سازی سمت سرور نمایش اطلاعات یک گرید، به پایان رسید. در این قسمت می‌خواهیم از سمت کلاینت، اطلاعات صفحه بندی و مرتب سازی را به سمت سرور ارسال کرده و همچنین نتیجه‌ی دریافتی از سرور را نمایش دهیم.



پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات

در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» نحوه‌ی نصب و معرفی کتابخانه‌ی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش داده‌است.
بنابراین ابتدا فرض بر این است که دو بسته‌ی بوت استرپ و ngx-bootstrap را نصب کرده‌اید:
> npm install bootstrap --save
> npm install ngx-bootstrap --save
در فایل angular-cli.json. شیوه‌نامه‌ی بوت استرپ را نیز افزوده‌اید:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
پس از آن باید به‌خاطر داشت که کامپوننت نمایش صفحه بندی این مجموعه PaginationModule نام دارد و باید در نزدیک‌ترین ماژول مورد نیاز، ثبت و معرفی شود:
import { PaginationModule } from "ngx-bootstrap";

@NgModule({
  imports: [
    PaginationModule.forRoot()
  ]
برای نمونه در این مثال، ماژولی به نام simple-grid.module.ts دربرگیرنده‌ی گرید مطلب جاری است و به صورت ذیل به برنامه اضافه شده‌است:
 >ng g m SimpleGrid -m app.module --routing
بنابراین تعریف PaginationModule باید به قسمت imports این ماژول اضافه شود و تعریف آن در app.module.ts تاثیری بر روی این قسمت نخواهد داشت.

کامپوننتی هم که مثال جاری را نمایش می‌دهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شده‌است:
 >ng g c SimpleGrid/products-list


تهیه معادل‌های قراردادهای سمت سرور در سمت Angular

در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آن‌ها در سمت یک برنامه‌ی تایپ اسکریپتی Angular، کلاس‌های معادل آن‌ها را تهیه می‌کنیم.

ساختار شیء محصول دریافتی از سمت سرور
 >ng g cl SimpleGrid/app-product
با این محتوا
export class AppProduct {
  constructor(
    public productId: number,
    public productName: string,
    public price: number,
    public isAvailable: boolean
  ) {}
}
که در اینجا هر کدام از خواص ذکر شده، معادل camel case نمونه‌ی سمت سرور خود هستند (چون JSON.NET در ASP.NET Core، به صورت پیش فرض یک چنین خروجی را تولید می‌کند).

ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
 >ng g cl SimpleGrid/PagedQueryModel
با این محتوا
export class PagedQueryModel {
  constructor(
    public sortBy: string,
    public isAscending: boolean,
    public page: number,
    public pageSize: number
  ) {}
}
در اینجا همان ساختار IPagedQueryModel سمت سرور را مشاهده می‌کنید. از آن جهت مشخص سازی جزئیات صفحه بندی و نحوه‌ی مرتب سازی اطلاعات، استفاده می‌شود.

ساختار معادل اطلاعات صفحه بندی شده‌ی دریافتی از سمت سرور
 >ng g cl SimpleGrid/PagedQueryResult
با این محتوا
export class PagedQueryResult<T> {
  constructor(public totalItems: number, public items: T[]) {}
}
این ساختار جنریک نیز دقیقا معادل همان PagedQueryResult سمت سرور است و حاوی تعداد کل ردیف‌های یک کوئری و تنها قسمتی از اطلاعات صفحه بندی شده‌ی آن می‌باشد.

ساختار ستون‌های گرید نمایشی
 >ng g cl SimpleGrid/GridColumn
با این محتوا
export class GridColumn {
  constructor(
    public title: string,
    public propertyName: string,
    public isSortable: boolean
  ) {}
}
هر ستون نمایش داده شده، دارای یک برچسب، خاصیتی مشخص در سمت سرور و بیانگر قابلیت مرتب سازی آن می‌باشد. اگر isSortable به true تنظیم شود، با کلیک بر روی سرستون‌ها می‌توان اطلاعات را بر اساس آن ستون، مرتب سازی کرد.


تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن

پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال می‌کند و نتیجه را باز می‌گرداند، به صورت ذیل خواهد بود:
 >ng g s SimpleGrid/products-list -m simple-grid.module
این دستور سبب ایجاد کلاس ProductsListService شده و همچنین قسمت providers ماژول simple-grid را نیز بر این اساس به روز رسانی می‌کند.
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
  toQueryString(obj: any): string {
    const parts = [];
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        if (value !== null && value !== undefined) {
          parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
        }
      }
    }
    return parts.join("&");
  }
در قسمت قبل امضای متد GetPagedProducts دارای ویژگی HttpGet است. بنابراین، نیاز است اطلاعات را به صورت کوئری استرینگ از سمت کلاینت دریافت کند و متد toQueryString فوق به صورت خودکار بر روی تمام خواص یک شیء دلخواه حرکت کرده و آن‌ها را تبدیل به یک رشته‌ی حاوی کوئری استرینگ‌ها می‌کند.
[HttpGet("[action]")]
public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
برای نمونه متد toQueryString فوق است که سبب ارسال یک چنین درخواستی به سمت سرور می‌شود:
 http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7

پس از این تعریف، سرویس ProductsListService  به صورت ذیل تکمیل خواهد شد:
@Injectable()
export class ProductsListService {
  private baseUrl = "api/Product";

  constructor(private http: Http) {}

  getPagedProductsList(
    queryModel: PagedQueryModel
  ): Observable<PagedQueryResult<AppProduct>> {
    return this.http
      .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`)
      .map(res => {
        const result = res.json();
        return new PagedQueryResult<AppProduct>(
          result.totalItems,
          result.items
        );
      });
  }
در اینجا از متد toQueryString، جهت تکمیل متد get ارسالی به سمت سرور استفاده شده‌است تا پارامترها را به صورت کوئری استرینگ‌ها تبدیل کرده و ارسال کند.
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونه‌ی سمت کلاینت آن‌را که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).


تکمیل کامپوننت نمایش گرید

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


  <table class="table table-striped table-hover table-bordered table-condensed">
    <thead>
      <tr>
        <th class="text-center" style="width:3%">#</th>
        <th *ngFor="let column of columns" class="text-center">
          <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer">
            {{ column.title }}
            <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon"
              [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i>
          </div>
          <div *ngIf="!column.isSortable" style="cursor: pointer">
            {{ column.title }}
          </div>
        </th>
      </tr>
    </thead>
در اینجا ابتدا بررسی می‌شود که آیا یک ستون قابلیت مرتب سازی را دارد، یا خیر؟ اگر اینطور است، در کنار آن یک گلیف آیکن مرتب سازی درج می‌شود. اگر خیر، صرفا متن عنوان آن نمایش داده خواهد شد. می‌شد تمام این موارد را به ازای هر ستون به صورت مجزایی ارائه داد، اما در این حالت به کدهای تکراری زیادی می‌رسیدیم. به همین جهت از یک حلقه بر روی تعریف ستون‌های این گرید استفاده شده‌است. آرایه‌ی این ستون‌ها نیز به صورت ذیل تعریف می‌شود:
export class ProductsListComponent implements OnInit {
  columns: GridColumn[] = [
    new GridColumn("Id", "productId", true),
    new GridColumn("Name", "productName", true),
    new GridColumn("Price", "price", true),
    new GridColumn("Available", "isAvailable", true)
  ];

همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده می‌کنید:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);

  sortBy(columnName) {
    if (this.queryModel.sortBy === columnName) {
      this.queryModel.isAscending = !this.queryModel.isAscending;
    } else {
      this.queryModel.sortBy = columnName;
      this.queryModel.isAscending = true;
    }
    this.getPagedProductsList();
  }
}
در این‌حالت اگر ستونی که بر روی آن کلیک شده، پیشتر مرتب سازی شده‌است، صرفا خاصیت صعودی بودن آن برعکس خواهد شد. در غیراینصورت، نام خاصیت درخواستی مرتب سازی و جهت آن نیز مشخص می‌شود. سپس مجددا این گرید توسط متد getPagedProductsList رندر خواهد شد.

کار رندر بدنه‌ی اصلی گرید توسط همین چند سطر در قالب آن مدیریت می‌شود:
    <tbody>
      <tr *ngFor="let item of queryResult.items; let i = index">
        <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
        <td class="text-center">{{ item.productId }}</td>
        <td class="text-center">{{ item.productName }}</td>
        <td class="text-center">{{ item.price | number:'.0' }}</td>
        <td class="text-center">
          <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
            disabled="disabled" />
        </td>
      </tr>
    </tbody>
  </table>
اولین ستون آن، اندکی ابتکاری است. در اینجا شماره ردیف‌های خودکاری در هر صفحه درج خواهند شد. این شماره ردیف نیز جزو ستون‌های منبع داده‌ی فرضی برنامه نیست. به همین جهت برای درج آن، توسط let i = index در ngFor، به شماره ایندکس ردیف جاری دسترسی پیدا می‌کنیم. سپس توسط محاسباتی بر اساس تعداد ردیف‌های هر صفحه و شماره‌ی صفحه‌ی جاری، می‌توان شماره ردیف فعلی را محاسبه کرد.

در اینجا حلقه‌ای بر روی queryResult.items تشکیل شده‌است. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی می‌شود:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;
  isLoading = false;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);
  queryResult = new PagedQueryResult<AppProduct>(0, []);

  constructor(private productsListService: ProductsListService) {}

  ngOnInit() {
    this.getPagedProductsList();
  }

  private getPagedProductsList() {
    this.isLoading = true;
    this.productsListService
      .getPagedProductsList(this.queryModel)
      .subscribe(result => {
        this.queryResult = result;
        this.isLoading = false;
      });
  }
}
ابتدا سرویس ProductsListService را که در ابتدای بحث تکمیل شد، به سازنده‌ی این کامپوننت تزریق می‌کنیم. به کمک آن می‌توان در متد getPagedProductsList، ابتدا queryModel جاری را که شامل اطلاعات مرتب سازی و صفحه بندی است، به سرور ارسال کرده و سپس نتیجه‌ی نهایی را به queryResult انتساب دهیم. به این ترتیب تعداد کل رکوردها و همچنین آیتم‌های صفحه‌ی جاری دریافت می‌شوند. اکنون حلقه‌ی ngFor نمایش بدنه‌ی گرید، کار تکمیل صفحه‌ی جاری را انجام خواهد داد.

قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:


  <div align="center">
    <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems"
      [rotate]="false" previousText="&lsaquo;" nextText="&rsaquo;" firstText="&laquo;"
      lastText="&raquo;" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage"
      (pageChanged)="onPageChange($event)"></pagination>
  </div>
  <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
در اینجا از کامپوننت pagination مجموعه‌ی ngx-bootstarp استفاده شده‌است و یک سری از خواص مستند شده‌ی آن‌، مقدار دهی شده‌اند؛ مانند متن‌های صفحه‌ی بعد و قبل و امثال آن. مدیریت کلیک بر روی شماره‌های آن، در کامپوننت جاری به صورت ذیل است:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;

  onPageChange(event: any) {
    this.queryModel.page = event.page;
    this.getPagedProductsList();
  }
}
علت تعریف دو خاصیت اضافه‌ی currentPage و numberOfPages، استفاده‌ی از آن‌ها در قسمت ذیل این شماره‌ها (خارج از کامپوننت نمایش شماره صفحات) جهت نمایش page 1/x است.
هر زمانیکه کاربر بر روی شما‌ره‌ای کلیک می‌کند، رخ‌داد onPageChange فراخوانی شده و در این‌حالت تنها کافی است شماره صفحه‌ی درخواستی queryModel جاری را به روزرسانی کرده و سپس آن‌را در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحه‌ی درخواستی قرار دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب دوره‌ها
تولید پویای کد در زمان اجرا توسط Reflection.Emit
در ادامه قصد داریم توسط امکانات Reflection به همراه کدهای IL، اشیایی را در زمان اجرا ایجاد کنیم.


Reflection چیست؟

Reflection چیزهایی هستند که با نگاه در یک آینه قابل مشاهده‌اند. در این حالت شخص می‌تواند قسمت‌های مختلف ظاهر خود را برانداز کرده یا قسمتی را تغییر دهید. اما این مساله چه ربطی به دنیای دات نت دارد؟ در دات نت با استفاده از Reflection می‌توان به اطلاعات اشیاء یک برنامه‌ی در حال اجرا دسترسی یافت. برای مثال نام کلاس‌های مختلف آن چیست یا درون کلاسی خاص، چه متدهایی قرار دارند. همچنین با استفاده از Reflection می‌توان رفتارهای جدیدی را نیز به کلاس‌ها و اشیاء افزود یا آن‌ها را تغییر داد.
همواره عنوان می‌شود که از Reflection به دلیل سربار بالای آن پرهیز کنید و تنها از آن به عنوان آخرین راه حل موجود استفاده نمائید و این دقیقا موردی است که در مباحث جاری بیشتر از آن استفاده خواهد شد: ساخت اشیاء جدید در زمان اجرا به کمک کدهای IL و امکانات Reflection


نگاهی به امکانات متداول Reflection

در مثال بعد، نگاهی خواهیم داشت به امکانات متداول Reflection، مانند دسترسی به متدها و خواص یک کلاس و تعویض مقدار یا فراخوانی آن‌ها:
using System;

namespace FastReflectionTests
{
    class Person
    {
        public string Name { set; get; }

        public string Speak()
        {
            return string.Format("Hello, my name is {0}.", this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //روش متداول
            var vahid = new Person { Name = "Vahid" };
            Console.WriteLine(vahid.Speak());

            var type = vahid.GetType();

            //نمایش متدهای یک کلاس
            var methods = type.GetMethods();
            foreach (var method in methods)
            {
                Console.WriteLine(method.Name);
            }

            //تغییر مقدار یک خاصیت
            var setNameMethod = type.GetMethod("set_Name");
            setNameMethod.Invoke(obj: vahid, parameters: new[] { "Ali" });

            //فراخوانی یک متد
            var speakMethod = type.GetMethod("Speak");
            var result = speakMethod.Invoke(obj: vahid, parameters: null);
            Console.WriteLine(result);
        }
    }
}
با خروجی ذیل
 Hello, my name is Vahid.
set_Name
get_Name
Speak
ToString
Equals
GetHashCode
GetType
Hello, my name is Ali.
توضیحات:
در اینجا یک کلاس شخص با خاصیت نام او تعریف شده است؛ به همراه متدی که رشته‌ای را نمایش خواهد داد.
در متد Main برنامه، ابتدا یک وهله جدید از این شخص ایجاد شده و سپس به روش متداول، متد Speak آن فراخوانی گردیده است. در ادامه کار از امکانات Reflection برای انجام همین امور کمک گرفته شده است.
کار با دریافت نوع یک وهله شروع می‌شود. برای نمونه در اینجا توسط vahid.GetType به نوع وهله ساخته شده دسترسی یافته‌ایم. سپس با داشتن این type، می‌توان به کلیه امکانات Reflection دسترسی یافت. برای مثال توسط GetMethods، لیست کلیه متدهای موجود در کلاس شخص بازگشت داده می‌شود.
اگر به خروجی فوق دقت کنید، پس از سطر اول، 7 سطر بعدی نمایانگر متدهای موجود در کلاس شخص هستند. شاید عنوان کنید که این کلاس به نظر یک متد بیشتر ندارد. اما در دات نت اشیاء از شیء Object مشتق می‌شوند و چهار متد ToString، Equals، GetHashCode و GetType متعلق به آن هستند. همچنین خواص تعریف شده نیز در اصل به دو متد set و get به صورت خودکار در کدهای IL برنامه ترجمه خواهند شد. از همین متد set_Name در ادامه برای مقدار دهی خاصیت نام وهله ایجاد شده استفاده شده است.
همانطور که ملاحظه می‌کنید برای فراخوانی یک وهله از طریق Reflection، ابتدا توسط متد type.GetMethod می‌توان به آن دسترسی یافت و سپس با فراخوانی متد Invoke، می‌توان متد مدنظر را بر روی یک شیء مهیا با پارامترهایی که ذکر می‌کنیم، فراخوانی کرد. اگر این متد پارامتری ندارد، آن‌را نال قرار خواهیم داد.

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


ایجاد اشیاء در زمان اجرای برنامه

یکی از کلاس‌های مهم Reflection که در منابع مختلف کمتر به آن پرداخته شده است، کلاس DynamicMethod آن است که از آن می‌توان برای ایجاد اشیاء و یا متدهایی پویا در زمان اجرا استفاده کرد. این کلاس قرار گرفته در فضای نام System.Reflection.Emit، دارای یک ILGenerator است که می‌توان به آن OpCodeهایی را اضافه کرد. زمانیکه کار ایجاد این متدپویا به پایان رسید، با استفاده از Delegates امکان دسترسی و اجرای این متد پویا وجود خواهد داشت.
یک مثال کامل را در این زمینه در ادامه ملاحظه می‌نمائید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static double Divider(int a, int b)
        {
            return a / b;
        }

        delegate double DividerDelegate(int a, int b);
        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Divider(10, 2));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "DividerMethod",
                                        returnType: typeof(double),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(opcode: OpCodes.Ldarg_0); //بارگذاری پارامتر اول بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Ldarg_1); //بارگذاری پارامتر دوم بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Div); // دو پارامتر از پشته ارزیابی دریافت و تقسیم خواهند شد
            il.Emit(opcode: OpCodes.Ret); // دریافت نتیجه نهایی از پشته ارزیابی و بازگشت آن

            //فراخوانی متد پویا
            //روش اول
            var result = myMethod.Invoke(obj: null, parameters: new object[] { 10, 2 });
            Console.WriteLine(result);

            //روش دوم
            var method = (DividerDelegate)myMethod.CreateDelegate(delegateType: typeof(DividerDelegate));
            Console.WriteLine(method(10, 2));
        }
    }
}
توضیحات
در ابتدای این مثال جدید یک متد متداول تقسیم کننده دو عدد را ملاحظه می‌کنید. در ادامه قصد داریم overload دیگری از این متد را توسط کدهای MSIL در زمان اجرا ایجاد کنیم که دو پارامتر int را قبول می‌کند.
کار با وهله سازی کلاس DynamicMethod موجود در فضای نام System.Reflection.Emit شروع می‌شود. در اینجا کار تعریف امضای متد جدید باید صورت گیرد. برای مثال نام آن چیست، نوع خروجی آن کدام است. نوع پارامترهای آن چیست و نهایتا این متدی که قرار است به صورت پویا به برنامه اضافه شود، باید در کجا قرار گیرد. برای اینکار از Module خود کلاس Program برنامه استفاده شده است.
پس از تعریف امضای متد پویا، نوبت به تعریف بدنه‌ی آن می‌رسد. کار با دریافت یک ILGenerator که می‌توان در آن کدهای IL را وارد کرد شروع می‌شود. مابقی آن تعریف کدهای IL توسط متد Emit است و پیشتر با مقدمات اسمبلی دات نت در قسمت‌های قبلی مبحث جاری آشنا شده‌ایم. ابتدا دو Ldarg فراخوانی شده‌اند تا دو پارامتر ورودی متد را دریافت کنند. سپس Div بر روی آن‌ها صورت گرفته و نهایتا نتیجه بازگشت داده شده است.
خوب؛ تا اینجا موفق شدیم اولین متد پویای خود را ایجاد نمائیم. برای اجرا آن حداقل دو روش وجود دارد:
الف) فراخوانی متد Invoke بر روی آن. با توجه به اینکه قرار نیست این متد بر روی وهله‌ی خاصی اجرا شود، اولین پارامتر آن null وارد شده است و سپس پارامترهای این متد پویا توسط آرگومان دوم متد Invoke وارد شده‌اند.
ب) می‌توان این عملیات را اندکی شکیل‌تر کرد. برای اینکار پیش از متد Main برنامه یک delegate به نام DividerDelegate تعریف شده است. سپس با استفاده از متد CreateDelegate، خروجی این متد پویا را تبدیل به یک delegate کرده‌ایم. اینبار فراخوانی متد پویا بسیار شبیه به متدهای معمولی می‌شود.
مطالب
ASP.NET MVC #8

معرفی HTML Helpers

یک HTML Helper تنها یک متد است که رشته‌ای را بر می‌گرداند و این رشته می‌تواند حاوی هر نوع محتوای دلخواهی باشد. برای مثال می‌توان از HTML Helpers برای رندر تگ‌های HTML، مانند img و input استفاده کرد. یا به کمک HTML Helpers می‌توان ساختارهای پیچیده‌تری مانند نمایش لیستی از اطلاعات دریافت شده از بانک اطلاعاتی را پیاده سازی کرد. به این ترتیب حجم کدهای تکراری تولید رابط کاربری در Viewهای برنامه‌های ASP.NET MVC به شدت کاهش خواهد یافت، به همراه قابلیت استفاده مجدد از متدهای الحاقی HTML Helpers در برنامه‌های دیگر.
HTML Helpers در ASP.NET MVC معادل کنترل‌های ASP.NET Web forms هستند اما نسبت به آن‌ها بسیار سبک‌تر می‌باشند؛ برای مثال به همراه ViewState و همچنین Event model نیستند.
ASP.NET MVC به همراه تعدادی متد HTML Helper توکار است و برای دسترسی به آن‌ها شیء Html که وهله‌ای از کلاس توکار HtmlHelper می‌باشد، در تمام Viewها قابل استفاده است.


نحوه ایجاد یک HTML Helper سفارشی

از دات نت سه و نیم به بعد امکان توسعه اشیاء توکار فریم ورک، به کمک متدهای الحاقی (extension methods) میسر شده است. برای نوشتن یک HTML Helper نیز باید همین شیوه عمل کرد و کلاس HtmlHelper را توسعه داد. در ادامه قصد داریم یک HTML Helper را جهت رندر تگ label در صفحه ایجاد کنیم. برای این منظور پوشه‌ی جدیدی به نام Helper را به پروژه اضافه نمائید (جهت نظم بیشتر). سپس کلاس زیر را به آن اضافه کنید:

using System;
using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static string MyLabel(this HtmlHelper helper, string target, string text)
{
return string.Format("<label for='{0}'>{1}</label>", target, text);
}
}
}

همانطور که ملاحظه می‌کنید متد Label به شکل یک متد الحاقی توسعه دهنده کلاس HtmlHelper که تنها یک رشته را بر می‌گرداند، تعریف شده است. اکنون برای استفاده از این متد در View دلخواهی خواهیم داشت:

@using MvcApplication4.Helpers
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@Html.MyLabel("firstName", "First Name:")

ابتدا فضای نام مرتبط با متد الحاقی باید پیوست شود و سپس از طریق شیء Html می‌توان به این متد الحاقی دسترسی پیدا کرد. اگر برنامه را اجرا کنید، این خروجی را مشاهده خواهیم کرد. چرا؟

Index
<label for='firstName'>First Name:</label>

علت این است که Razor، اطلاعات را Html encoded به مرورگر تحویل می‌دهد. برای تغییر این رویه باید اندکی متد الحاقی تعریف شده را تغییر داد:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyLabel(this HtmlHelper helper, string target, string text)
{
return MvcHtmlString.Create(string.Format("<label for='{0}'>{1}</label>", target, text));
}
}
}

تنها تغییر صورت گرفته، استفاده از MvcHtmlString بجای string معمولی است تا Razor آن‌را encode نکند.

تعریف HTML Helpers سفارشی به صورت عمومی:

می‌توان فضای نام MvcApplication4.Helpers این مثال را عمومی کرد. یعنی بجای اینکه بخواهیم در هر View  آن‌را ابتدا تعریف کنیم، یکبار آن‌را همانند تعاریف اصلی یک برنامه ASP.NET MVC، عمومی معرفی می‌کنیم. برای این منظور فایل web.config موجود در پوشه Views را باز کنید (و نه فایل web.config قرار گرفته در ریشه اصلی برنامه). سپس فضای نام مورد نظر را در قسمت namespaces صفحات اضافه نمائید:

<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="MvcApplication4.Helpers"/>
</namespaces>

به این ترتیب متدهای الحاقی تعریف شده در فضای نام MvcApplication4.Helpers،‌ در تمام Viewهای برنامه در دسترس خواهند بود.

استفاده از کلاس TagBuilder برای تولید HTML Helpers سفارشی:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyNewLabel(this HtmlHelper helper, string target, string text)
{
var labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", target);
labelTag.InnerHtml = text;
return MvcHtmlString.Create(labelTag.ToString());
}
}
}

در فضای نام System.Web.Mvc، کلاسی وجود دارد به نام TagBuilder که کار تولید تگ‌های HTML، مقدار دهی ویژگی‌ها و خواص آن‌ها را بسیار ساده می‌کند و روش توصیه شده‌ای است برای تولید متدهای HTML Helper. یک نمونه از کاربرد آن‌را برای بازنویسی متد MyLabel ذکر شده در اینجا ملاحظه می‌کنید.
شبیه به همین کلاس، کلاس دیگری به نام HtmlTextWriter در فضای نام System.Web.UI برای انجام اینگونه کارها وجود دارد.


نوشتن HTML Helpers ویژه، به کمک امکانات Razor

نوع دیگری از این متدهای کمکی، Declarative HTML Helpers نام دارند. از این جهت هم Declarative نامیده شده‌اند که مستقیما درون فایل‌های cshtml یا vbhtml به کمک امکانات Razor قابل تعریف هستند. تولید این نوع متدهای کمکی به این شکل نسبت به مثلا روش TagBuilder ساده‌تر است،‌ چون توسط Razor به سادگی و به نحو طبیعی‌تری می‌توان تگ‌های HTML و کدهای مورد نظر را با هم ترکیب کرد (این رفتار طبیعی و روان، یکی از اهداف Razor است).
به عنوان مثال، تعاریف همان کلاس‌های Product و Products قسمت قبل (قسمت هفتم) را در نظر بگیرید. با همان کنترلر و View ایی که ذکر شد.
سپس برای تعریف این نوع خاص از HTML Helpers/Razor Helpers باید به این نحو عمل کرد:
الف) در ریشه پروژه یا سایت، پوشه‌ی جدیدی به نام App_Code ایجاد کنید (دقیقا به همین نام. این پوشه، جزو پوشه‌های ویژه ASP.NET است).
ب) بر روی این پوشه کلیک راست کرده و گزینه Add|New Item را انتخاب کنید.
ج) در صفحه باز شده، MVC 3 Partial Page/Razor را یافته و مثلا نام ProductsList.cshtml را وارد کرده و این فایل را اضافه کنید.
د) محتوای این فایل جدید را به نحو زیر تغییر دهید:

@using MvcApplication4.Models
@helper GetProductsList(List<Product> products)
{
<ul>
@foreach (var item in products)
{
<li>@item.Name ($@item.Price)</li>
}
</ul>
}

در اینجا نحوه تعریف یک helper method مخصوص Razor را مشاهده می‌کنید که با کلمه @helper شروع شده است. مابقی آن هم ترکیب آشنای code و markup هستند که به کمک امکانات Razor به این شکل روان میسر شده است.
اکنون اگر Viewایی بخواهد از این اطلاعات استفاده کند تنها کافی است به نحو زیر عمل نماید:

@model List<MvcApplication4.Models.Product>
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@ProductsList.GetProductsList(@Model)

ابتدا نام فایل ذکر شده بعد نام متد کمکی تعریف شده در آن. Model هم در اینجا به لیستی از محصولات اشاره می‌کند.
همچنین چون در پوشه app_code قرار گرفته، تمام Viewها به اطلاعات آن دسترسی خواهند داشت. علت هم این است که ASP.NET به صورت خودکار محتوای این پوشه ویژه را همواره کامپایل می‌کند و در اختیار برنامه قرار می‌دهد.
به علاوه در این فایل ProductsList.cshtml، باز هم می‌توان متدهای helper دیگری را اضافه کرد و از این بابت محدودیتی ندارد. همچنین می‌توان این متد helper را مستقیما داخل یک View هم تعریف کرد. بدیهی است در این حالت قابلیت استفاده مجدد از آن‌را به همراه داشتن Viewهایی تمیز و کم حجم، از دست خواهیم داد.

جهت تکمیل بحث
Turn your Razor helpers into reusable libraries


نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
ارتقاء به دات نت 8
اگر این پروژه رو به دات نت 8 ارتقاء بدیم در کلاس TokenValidatorService دیگر نمیتوان با استفاده از accessToken.RawData توکن را خواند که آن را در پایگاه داده مورد بررسی قرار بدیم.
if (!(context.SecurityToken is JwtSecurityToken accessToken) ||
    string.IsNullOrWhiteSpace(accessToken.RawData) ||
    !await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId))
{
    context.Fail("This token is not in our database.");
    return;
}
علت اینکار این است که در دات نت 8 کلاس JsonWebToken جایگزین JwtSecurityToken شده است ^ و همچنین برای گرفتن توکن باید از متد UnsafeToString استفاده کرد.
if (!(context.SecurityToken is JsonWebToken accessToken) ||
    string.IsNullOrWhiteSpace(accessToken.UnsafeToString()) ||
    !await tokenStoreService.IsValidTokenAsync(accessToken.UnsafeToString(), userId))
{
    context.Fail("This token is not in our database.");
    return;
}
علت این جایگزینی بهبود عملکرد تا 30 درصد و پردازش Async در کلاس JsonWebToken میباشد.