نظرات مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
امکان دریافت تائید از کاربر، در حین ترک صفحه‌ی یک فرم ذخیره نشده در دات نت 7

Blazor در دات نت 7 به همراه امکانات مدیریت بهتر تغییرات آدرس صفحات است. برای مثال توسط آن می‌توان به کاربران در مورد کارهای ذخیره نشده، در صورت شروع به هدایت به صفحه‌ای دیگر، هشدار داد.
برای مدیریت تغییرات آدرس صفحات، می‌توان از سرویس NavigationManager و متد RegisterLocationChangingHandler آن به صورت زیر استفاده کرد:
var registration = NavigationManager.RegisterLocationChangingHandler(async cxt =>
{
    if (cxt.TargetLocation.EndsWith("counter"))
    {
        cxt.PreventNavigation();
    }
});
- در این مثال اگر آدرس درخواستی به counter ختم شود، با فراخوانی متد PreventNavigation، مانع آن خواهیم شد.
- خروجی این متد برای مثال registration در اینجا، از نوع IDisposable است و dispose آن سبب حذف Handler ثبت شده می‌شود.
- باید بخاطر داشت که امکانات فوق تنها آدرس‌های درون برنامه‌ای را مدیریت می‌کند. برای مدیریت هدایت به آدرس‌های خارجی باید از رخ‌داد beforeunload جاوا اسکریپت استفاده کرد.


ساده سازی کار با سرویس مدیریت تغییرات آدرس با کامپوننت جدید NavigationLock

نکات عنوان شده را توسط کامپوننت جدید NavigationLock نیز می‌توان به نحو ساده‌تری مدیریت کرد:
<NavigationLock OnBeforeInternalNavigation="ConfirmNavigation" ConfirmExternalNavigation />
در این کامپوننت، OnBeforeInternalNavigation یک callback است که کار ردیابی و مدیریت تغییرات آدرس‌های درون برنامه‌ای را انجام می‌دهد. اگر می‌خواهید هدایت به آدرس‌های خارجی را نیز کنترل کرده و همچنین علاقمند به پیاده سازی دستی رخ‌داد beforeunload جاوا اسکریپت نیستید، می‌توانید خاصیت ConfirmExternalNavigation را نیز قید کنید که با یک browser-specific prompt مدیریت می‌شود.
از این کامپوننت می‌توان جهت مدیریت یک فرم ذخیره نشده درصورت شروع به هدایت کاربر به آدرسی دیگر، به صورت زیر استفاده کرد:
<EditForm EditContext="editContext" OnValidSubmit="Submit">
    ...
</EditForm>
<NavigationLock OnBeforeInternalNavigation="ConfirmNavigation" ConfirmExternalNavigation />

    @code {
        private readonly EditContext editContext;
        ...

        // Called only for internal navigations.
        // External navigations will trigger a browser specific prompt.
        async Task ConfirmNavigation(LocationChangingContext context)
        {
            if (editContext.IsModified())
            {
                var isConfirmed = await JS.InvokeAsync<bool>("window.confirm", 
                   "Are you sure you want to leave this page?");
                
                if (!isConfirmed)
                {
                    context.PreventNavigation();
                }
            }
        }
    }
توضیحات:
- با استفاده از EditContext یک EditForm و متد IsModified آن می‌توان تشخیص داد که اطلاعات فرم جاری توسط کاربر تغییر کرده‌است و هنوز ذخیره شده یا نشده.
- در اینجا پیاده سازی OnBeforeInternalNavigation را توسط متد ConfirmNavigation مشاهده می‌کنید که در آن بررسی می‌شود آیا کاربر فرم را تغییر داده‌است یا خیر؟ اگر بله، متد confirm جاوا اسکریپت را جهت تائید ترک صفحه نمایش می‌دهد. اگر کاربر آن‌را تائید نکند، توسط متد PreventNavigation، مانع تغییر آدرس صفحه و از دست رفتن اطلاعات خواهد شد.
مطالب
تفاوت بین IQueryable و IEnumerable در حین کار با ORMs

متد زیر را که یکی از اشتباهات رایج حین استفاده از LINQ خصوصا جهت Binding اطلاعات است، در نظر بگیرید:
IQueryable<Customer> GetCustomers()

این متد در حقیقت هیچ چیزی را Get نمی‌کند! نام اصلی آن GetQueryableCustomers و یا GetQueryObjectForCustomersاست.
IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی می‌باشد که مد نظر شما است و نه بیشتر.
IQueryable<Customer> youngCustomers = repo.GetCustomers().Where(m => m.Age < 15);
برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر می‌کنید نیز هنوز چیزی از بانک اطلاعاتی یا منبع داده‌ای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع داده‌ای صورت نگرفته است.
به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شده‌ی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته می‌شود (باید دقت داشت که IQueryable هم یک نوع IEnumerable است به علاوه expression trees که مهم‌ترین وجه تمایز آن نیز می‌باشد).
برای مثال در عبارت زیر تنها در زمانیکه متد ToList فراخوانی می‌شود، کل عبارت LINQ ساخته شده، به عبارت SQL متناظر با آن ترجمه شده، اطلاعات از دیتابیس اخذ گردیده و حاصل به صورت یک لیست بازگشت داده می‌شود:
IList<Competitor> competitorRecords =  competitorRepository
.Competitors
.Where(m => !m.Deleted)
.OrderBy(m => m.countryId)
.ToList(); //فقط اینجا است که اس کیوال نهایی تولید می‌شود

در مورد IEnumerable ها چطور؟
IEnumerable<Product> products = repository.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
دو سطر فوق به این معنا است:
لطفا ابتدا به بانک اطلاعاتی رجوع کن و تمام رکوردهای محصولات موجود را بازگشت بده. سپس بر روی این حجم بالای اطلاعات، محصولاتی را که قیمت بالای 25 دارند، فیلتر کن.

اگر همین دو سطر را با IQueryable بازنویسی کنیم چطور؟
 IQueryable<Product> products = repository.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
در سطر اول تنها یک عبارت LINQ ساخته شده است و بس. در سطر دوم نیز به همین صورت. در طی این دو سطر حتی یک رفت و برگشت به بانک اطلاعاتی صورت نخواهد گرفت. در ادامه اگر این اطلاعات به نحوی Select شوند (یا ToList فراخوانی شود، یا در طی یک حلقه برای مثال Iteration ایی روی این حاصل صورت گیرد یا موارد مشابه دیگر)، آنگاه کوئری SQL متناظر با عبارت LINQ فوق ساخته شده و بر روی بانک اطلاعاتی اجرا خواهد شد.
بدیهی است این روش منابع کمتری را نسبت به حالتی که تمام اطلاعات ابتدا دریافت شده و سپس فیلتر می‌شوند، مصرف می‌کند (حالت بازگشت تمام اطلاعات ممکن است شامل 20000 رکورد باشد، اما حالت دوم شاید فقط 5 رکورد را بازگشت دهد).

سؤال: پس IQueryable بسیار عالی است و از این پس کلا از IEnumerable ها دیگر نباید استفاده کرد؟
خیر! توصیه اکید طراحان این است که لطفا تا حد امکان متدهایی که IQueryable بازگشت می‌دهند ایجاد نکنید! IQueryable یعنی اینکه این نقطه‌ی آغازین کوئری در اختیار شما، بعد برو هر کاری که دوست داشتی با آن در طی لایه‌های مختلف انجام بده و هر زمانیکه دوست داشتی از آن یک خروجی تهیه کن. خروجی IQueryable به معنای مشخص نبودن زمان اجرای نهایی کوئری و همچنین مبهم بودن نحوه‌ی استفاده از آن است. به همین جهت متدهایی را طراحی کنید که IEnumerable بازگشت می‌دهند اما در بدنه‌ی آن‌ها به نحو صحیح و مطلوبی از IQueryable استفاده شده است. به این صورت حد و مرز یک متد کاملا مشخص می‌شود. متدی که واقعا همان فیلتر کردن محصولات را انجام می‌دهد، همان 5 رکورد را بازگشت خواهد داد؛ اما با استفاده از یک لیست یا یک IEnumerable و نه یک IQueryable که پس از فراخوانی متد نیز به هر نحو دلخواهی قابل تغییر است.

مطالب
استثنای Sequence contains no elements در حین استفاده از LINQ

در ابتدا مثال‌های زیر را در نظر بگیرید:

using System;
using System.Collections.Generic;
using System.Linq;

namespace testWinForms87
{
public class Data
{
public int id { get; set; }
public string name { get; set; }
}

class CLinqTests
{
public static int TestGetListMin1()
{
var lst = new List<Data>
{
new Data{ id=1, name="id1"},
new Data{ id=2, name="id2"},
new Data{ id=3, name="name3"}
};

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}

public static int TestGetListMin2()
{
var lst = new List<Data>();

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}
}
}
در متد TestGetListMin1 قصد داریم کوچکترین آی دی رکوردهایی را که نام آن‌ها حاوی id است، از لیست تشکیل شده از کلاس Data بدست آوریم (همانطور که مشخص است سه رکورد از نوع Data در لیست lst ما قرار گرفته‌اند).
محاسبات آن کار می‌کند و مشکلی هم ندارد. اما همیشه در دنیای واقعی همه چیز قرار نیست به این خوبی پیش برود. ممکن است همانند متد TestGetListMin2 ، لیست ما خالی باشد (برای مثال از دیتابیس، رکوردی مطابق شرایط کوئری‌های قبلی بازگشت داده نشده باشد). در این حالت هنگام فراخوانی متد Min ، استثنای Sequence contains no elements رخ خواهد داد و همانطور که در مباحث defensive programming عنوان شد، وظیفه‌ی ما این نیست که خودرو را به دیوار کوبیده (یا منتظر شویم تا کوبیده شود) و سپس به فکر چاره بیفتیم که خوب، عجب! مشکلی رخ داده است!
اکنون چه باید کرد؟ حداقل یک مرحله بررسی اینکه آیا کوئری ما حاوی رکوردی می‌باشد یا خیر باید به این متد اضافه شود (به صورت زیر):

public static int TestGetListMin3()
{
var lst = new List<Data>();
var query = from c in lst
where c.name.Contains("id")
select c.id;

if (query.Any())
return query.Min();
else
return -1;
}
البته می‌شد اگر هیچ رکوردی بازگشت داده نمی‌شد، یک استثنای سفارشی را ایجاد کرد، اما به شخصه ترجیح می‌دهم عدد منهای یک را بر گردانم (چون می‌دانم رکوردهای من عدد مثبت هستند و اگر حاصل منفی شد نیازی به ادامه‌ی پروسه نیست).

شبیه به این مورد در هنگام استفاده از تابع Single مربوط به LINQ نیز ممکن است رخ دهد (تولید استثنای ذکر شده) اما در اینجا مایکروسافت تابع SingleOrDefault را نیز پیش بینی کرده است. در این حالت اگر کوئری ما رکوردی را برنگرداند، SingleOrDefault مقدار نال را برگشت داده و استثنایی رخ نخواهد داد (نمونه‌ی دیگر آن متدهای First و FirstOrDefault هستند).
در مورد متدهای Min و Max ، متدهای MinOrDefault یا MaxOrDefault در دات نت فریم ورک وجود ندارند. می‌توان این نقیصه را با استفاده از extension methods برطرف کرد.

using System;
using System.Collections.Generic;
using System.Linq;

public static class LinqExtensions
{
public static T MinOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Min<T>();

return defaultValue;
}

public static T MaxOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Max<T>();

return defaultValue;
}
}
اکنون با استفاده از extension methods فوق، کد ما به صورت زیر تغییر خواهد کرد:

public static int TestGetListMin4()
{
var lst = new List<Data>();
return (from c in lst
where c.name.Contains("id")
select c.id).MinOrDefault(-1);
}

مطالب
مسیریابی در Angular - قسمت دهم - Lazy loading
می‌خواهیم زمان نمایش اولین قالب برنامه را به حداقل برسانیم تا تاثیر روانی بهتری را بر روی کاربرانی که برنامه را اجرا می‌کنند، بگذاریم. برای این منظور در Angular، از Lazy loading استفاده می‌شود. همچنین این فریم ورک به همراه قابلیت پیش بارگذاری ماژول‌ها نیز هست تا سایر مسیرهای درخواستی را نیز با سرعت هرچه تمام‌تر نمایش دهد.
زمانیکه کاربری برنامه‌ی تک صفحه‌ای وب را در مرورگر باز می‌کند، ابتدا فایل index.html را در پاسخ دریافت خواهد کرد. این فایل تعاریف مداخل مورد نیاز برای رندر آن‌را مانند فایل‌های جاوا اسکریپت و CSS، به همراه دارد. سپس این فایل‌ها توسط مرورگر از سرور دریافت می‌شوند. در این حالت با پردازش این فایل‌ها، کامپوننت ریشه‌ی سایت بارگذاری می‌شود. پس از پایان آن، قالب این کامپوننت به کاربر نمایش داده خواهد شد. بنابر سرعت دریافت فایل‌ها توسط کاربر، این آغاز می‌تواند اندکی کند باشد. البته با رعایت نکات گفته‌ی شده‌ی در مطلب «Angular CLI - قسمت پنجم - ساخت و توزیع برنامه» می‌توان این حجم را توسط AoT و Tree-Shaking به میزان قابل ملاحظه‌ای کاهش داد. به علاوه با فعالسازی Lazy loading می‌توان قسمت‌های مختلف برنامه را تبدیل به یک سری Bundle کرد که در زمان درخواست، بارگذاری می‌شوند. به این ترتیب حجم فایل‌های ابتدایی که باید از سرور دریافت شوند بسیار کمتر شده و به علاوه با کاهش این حجم، مرورگر نیز باید میزان کمتری از کدها را در جهت نمایش اولین کامپوننت، پردازش و اجرا کند. در این حالت زمانیکه کاربری شروع به پیمایش مسیر یک ماژول خاص را می‌کند، آنگاه فایل‌های مرتبط با آن از سرور دریافت و در مرورگر پردازش می‌شوند. بنابراین اگر کاربری به قسمتی دسترسی ندارد، نیازی هم به دریافت فایل‌های آن نخواهد داشت؛ چون کار به فعالسازی مسیریابی آن ماژول نمی‌رسد.


آماده شدن جهت Lazy loading

پیش از Lazy loading یک قسمت از برنامه (که به آن async routing هم می‌گویند)، این قسمت باید دارای شرایطی باشد:
 - این قسمت از برنامه حتما باید در یک ماژول تعریف شده باشد. از این جهت که Lazy loading، لیست کامپوننت‌های قید شده‌ی در تعریف یک ماژول را بارگذاری می‌کند.
 - تمام مسیرهای این ماژول باید در ذیل یک مسیر والد، گروه بندی شده باشند. از این جهت که Lazy loading فقط بر روی مسیر ریشه‌ی والد تنظیم و بارگذاری می‌شود.
 - این ماژول نباید در هیچ ماژول دیگری import شده باشد. اگر این ماژول ارجاعی را در سایر ماژول‌ها داشته باشد، هیچ راهی بجز دریافت و کامپایل کامل آن توسط Angular وجود نخواهد داشت.


در مثال جاری این سری:
 - تمام ویژگی‌های قسمت مدیریت محصولات، داخل ماژول product.module.ts تعریف شده‌اند. بنابراین اولین شرط Lazy loading آن برقرار است.
 - در فایل product-routing.module.ts، کار گروه بندی مسیریابی‌ها ذیل یک والد مشخص انجام شده‌است (همان قسمت ششم این سری). بنابراین شرط دوم lazy loading این ماژول نیز پیشتر پیاده سازی شده‌است.
 - اما اگر به فایل src\app\app.module.ts مراجعه کنیم، ارجاعی به این ماژول در قسمت imports آن وجود دارد. بنابراین باید این ارجاع را حذف کنیم. در غیراینصورت کار دریافت کامل آن به همراه سایر ماژول‌های برنامه، در همان ابتدای کار صورت خواهد گرفت.
بنابراین در فایل src\app\app.module.ts، ابتدا import فایل آن‌را از ابتدای ماژول حذف و سپس ارجاع به نام کلاس کامپوننت ProductModule را نیز حذف می‌کنیم. در این حالت اگر از طریق منوی سایت سعی در دسترسی به این مسیرها کنیم، خطای 404 را دریافت خواهیم کرد؛ چون اکنون برنامه اطلاعاتی را در مورد نحوه‌ی مسیریابی قسمت محصولات برنامه، ندارد.

 
Lazy loading یک ماژول

برای بارگذاری غیرهمزمان یک ماژول و یا همان Lazy loading، می‌توان از خاصیت loadChildren تنظیمات مسیریابی، استفاده کرد:
{
   path: 'products',
   loadChildren:'app/product/product.module#ProductModule'
},
مقدار خاصیت loadChildren به صورت ذکر مسیر ماژول مرتبط به همراه یک # و سپس ذکر نام کلاس ماژول آن انجام می‌شود. مسیری هم که در اینجا ذکر می‌شود بر اساس محل قرارگیری فایل index.html، مقدار دهی شود.
با این تنظیم، زمانیکه مسیر ریشه‌ی produtcs درخواست شد، کار بارگذاری ماژول آن صورت گرفته و تنظیمات مسیریابی آن به سیستم اضافه می‌شود. به علاوه کار فعالسازی و نمایش کامپوننت آن را نیز انجام خواهد داد.

به همین منظور فایل src\app\app-routing.module.ts را گشوده و تنظیم فوق را به آن اضافه می‌کنیم:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: 'products', loadChildren: 'app/product/product.module#ProductModule' },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
در این حالت اگر دستور ng serve -o را صادر کنید، خروجی آن اندکی متفاوت خواهد بود:
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 165 kB {4} [initial]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 32.7 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 129 kB {4} [initial]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.72 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry]
chunk    {5} 5.chunk.js, 5.chunk.js.map 51.1 kB {1} [rendered]
مورد {5} با فعالسازی lazy loading به لیست فایل‌های موجود اضافه شده‌است. این فایلی است که تنها درصورت درخواست مسیر نمایش لیست محصولات، توسط مرورگر دریافت خواهد شد و هیچ ارجاع مستقیمی به آن در فایل index.html تولیدی نهایی وجود ندارد.

به علاوه اگر در منوی سایت بر روی لینک نمایش لیست محصولات کلیک کنیم، هنوز خروجی نمایش داده نمی‌شود (هرچند خطای 404 را هم دریافت نمی‌کنیم). علت اینجا است که اگر به فایل src\app\product\product-routing.module.ts مراجعه کنیم، تعریف این مسیر ریشه، در این فایل نیز وجود دارد:
const routes: Routes = [
  {
    path: 'products',
    canActivate: [ AuthGuard ],
    children: [   ]
  }
];
بنابراین اکنون برای دسترسی به آن باید مسیر products/products را درخواست داد. به همین جهت، path و canActivate آن‌را حذف کرده و هر دو را به فایل src\app\app-routing.module.ts منتقل می‌کنیم:
import { AuthGuard } from './user/auth.guard';

const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    canActivate: [AuthGuard]
  },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

یک نکته: اکنون تنظیمات مسیریابی فایل src\app\product\product-routing.module.ts چنین شکلی را پیدا کرده‌است:
const routes: Routes = [
  {
    path: '',
    component: ProductListComponent
  },
  {
    path: ':id',
    component: ProductDetailComponent,
    resolve: { product: ProductResolverService }
  },
  {
    path: ':id/edit',
    component: ProductEditComponent,
    resolve: { product: ProductResolverService },
    canDeactivate: [ProductEditGuard],
    children: [
      { path: '', redirectTo: 'info', pathMatch: 'full' },
      { path: 'info', component: ProductEditInfoComponent },
      { path: 'tags', component: ProductEditTagsComponent }
    ]
  }
];
تنظیمات مسیر والد به طور کامل حذف شده‌اند. به علاوه دیگر نیازی به ذکر خاصیت children آن نیست و تمام تنظیمات مسیریابی فرزندان، داخل [] اصلی قرار گرفته‌اند. همچنین دیگر نیازی به الحاق AuthGuard در ابتدای importهای این ماژول نیست؛ چون به فایل src\app\app-routing.module.ts منتقل شده‌است.

در این حالت اگر مسیر نمایش لیست محصولات را درخواست دهیم، مشاهده خواهیم کرد فایل 5.chunk.js که حاوی اطلاعات این ماژول است، به صورت مجزایی بارگذاری شده (lazy loading) و سپس با فعال شدن محافظ مسیر آن، صفحه‌ی لاگین نمایش داده می‌شود:


این بارگذاری با تاخیر و در صورت نیاز، به دو علت آغاز برنامه را سریعتر می‌کند:
الف) مرورگر اطلاعی از وجود فایل 5.chunk.js در ابتدای کار نداشته و آن‌را بارگذاری نمی‌کند (دریافت حجم کمتر، در آغاز نمایش برنامه).
ب) چون حجم کمتری از کدهای جاوا اسکریپت توسط مرورگر در آغاز کار دریافت می‌شود، کار پردازش و اجرای آن‌ها نیز بسیار سریعتر خواهد شد.


بررسی محافظ canLoad

تعدادی از محافظ‌های مسیرها را در قسمت قبل بررسی کردیم. هنگامیکه کامپوننت‌ها به صورت lazy loading فعالسازی شده و قالب آن‌ها نمایش داده می‌شوند، می‌توان از محافظ مسیر دیگری به نام canLoad نیز استفاده کرد و هدف از آن، بررسی منطقی، پیش از فعالسازی یک مسیر غیرهمزمان است. بنابراین اگر این محافظ false را برگرداند، حتی فایل‌های اسکریپت این ماژول، بارگذاری اولیه نیز نخواهد شد. به این ترتیب کسانیکه دسترسی به یک مسیر را نداشته باشند، فایل‌های اسکریپت متناظر با آن‌را نیز دریافت نخواهند کرد.

در مثال جاری، اگر به برگه‌ی network ابزار developer مرورگر دقت کنید، با درخواست نمایش مسیر لیست محصولات، ابتدا فایل js آن دریافت می‌شود که حاوی اطلاعات تمام کامپوننت‌ها و قالب‌های مرتبط با این مسیر است و سپس صفحه‌ی login نمایش داده خواهد شد. بنابراین اگر کاربر به این قسمت دسترسی نداشته باشد، فایل js آن بی‌جهت دریافت و بارگذاری شده‌است. برای بهبود این وضعیت می‌توان نمایش لاگین را پیش از بارگذاری فایل js این ماژول فعالسازی کرد و این مورد هدف اصلی محافظ canLoad است.

در ادامه برای تکمیل مثال جاری، می‌توان AuthGuard را طوری تنظیم کرد که علاوه بر پیاده سازی CanActivate، اینترفیس CanLoad را نیز پیاده سازی کند:
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router, CanLoad, Route } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate, CanLoad {

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.checkLoggedIn(state.url);
  }

  canLoad(route: Route): boolean {
    return this.checkLoggedIn(route.path);
  }

 // … the same as before

}
همانطور که ملاحظه می‌کنید، متد canLoad بر خلاف متد canActivate دسترسی به سرویس‌های اطلاعات مسیریابی و وضعیت مسیریابی را ندارد؛ از این جهت که هنوز در این مرحله، ماژول درخواستی حاوی تنظیمات مسیریابی، بارگذاری و فعالسازی نشده‌است.

مرحله‌ی بعد، تغییر فایل src\app\app-routing.module.ts و جایگزین کردن تعریف فعلی canActivate با canLoad است:
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    canLoad: [AuthGuard]
  },
پس از این تغییر، برنامه را مجدا اجرا کرده و صفحه را refresh کنید. سپس برگه‌ی network ابزار developers را نیز باز نگه دارید. اکنون بر روی لینک نمایش لیست محصولات کلیک کنید. مشاهده خواهید کرد که در این حالت صفحه‌ی لاگین، بدون بارگذاری ماژول Js ایی نمایش داده می‌شود. در ادامه اگر لاگین کنیم، آنگاه فایل js این ماژول توسط مرورگر دریافت شده و بارگذاری می‌شود.


پیش بارگذاری ماژول‌ها

با فعالسازی lazy loading، ماژول‌های مورد نیاز کاربر دیگر به همراه فایل‌های js ابتدایی برنامه که در فایل index.html ارجاع مستقیمی به آن‌ها دارند، ارائه نمی‌شوند و تنها در صورت درخواست مشاهده‌ی مسیری، کار بارگذاری آن‌ها توسط برنامه صورت خواهد گرفت. همین مساله می‌تواند در بار اول نمایش این ماژول‌ها تاخیر کوتاهی را سبب شود. به همین جهت قابلیت پیش بارگذاری ماژول‌ها نیز در سیستم مسیریاب Angular پیش بینی شده‌است. به این قابلیت preloading و یا eager lazy loading نیز می‌گویند. در این حالت برنامه در پشت صحنه، کار پیش واکشی ماژول‌ها را انجام می‌دهد و زمانیکه کاربری مسیری را درخواست می‌دهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.
بدیهی است این قابلیت نباید برای ماژول‌هایی که قرار است توسط کاربرانی خاص مشاهده شوند فعال شود و هدف آن دسترسی سریع به ماژول‌های پرکاربرد برنامه‌است.

در اینجا سه استراتژی پیش بارگذاری ماژول‌ها میسر است:
 - No preloading که حالت پیش فرض است.
 - Preload all سبب پیش بارگذاری تمام قسمت‌های lazy load برنامه می‌شود.
 - Custom که اجازه‌ی تعریف یک استراتژی سفارشی را می‌دهد.

برای مثال برای فعالسازی حالت Preload all، باید به فایل src\app\app-routing.module.ts مراجعه کرده و تغییرات ذیل را اعمال کنیم:
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

@NgModule({
  imports: [RouterModule.forRoot(
    routes,
    { enableTracing: true, preloadingStrategy: PreloadAllModules  /*, useHash: true*/ }
  )],
در اینجا نحوه‌ی تنظیم preloadingStrategy را به PreloadAllModules مشاهده می‌کنید. در این حالت پس از آغاز ابتدایی برنامه، مسیریاب بلافاصله تمام مسیرهای lazy load را در پشت صحنه بارگذاری می‌کند.

یک نکته: وجود محافظ canLoad، هر نوع استراتژی prealoading را غیرفعال می‌کند. اما prealoading با سایر انواع محافظ‌ها کار می‌کند.
بنابراین برای آزمایش تنظیم  preloadingStrategy: PreloadAllModules، تعریف canLoad را به canActivate تغییر دهید.


تعریف استراتژی‌های سفارشی پیش بارگذاری ماژول‌ها

اگر نیاز به یک استراتژی پیش بارگذاری بهتر از هیچ یا همه باشد، می‌توان یک استراتژی سفارشی را نیز تدارک دید و ایجاد آن سه مرحله‌ی ایجاد سرویس مرتبط، ثبت آن سرویس در ماژول و در آخر تنظیم مسیریابی را به همراه دارد.
برای این منظور ابتدا دستور ذیل را صادر کنید تا قالب ابتدایی سرویس SelectiveStrategy ایجاد شود:
 >ng g s SelectiveStrategy -m app.module
که سبب تولید و به روز رسانی فایل‌های ذیل در پوشه‌ی src\app خواهد شد (چون مرتبط است به کل برنامه):
 installing service
  create src\app\selective-strategy.service.spec.ts
  create src\app\selective-strategy.service.ts
  update src\app\app.module.ts
در این حالت لیست providers فایل app.module.ts نیز به صورت خودکار تکمیل می‌گردد.

سپس کدهای SelectiveStrategyService را به نحو ذیل تغییر دهید:
import { Injectable } from '@angular/core';
import { Route, PreloadingStrategy } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';

@Injectable()
export class SelectiveStrategyService implements PreloadingStrategy {

  preload(route: Route, load: Function): Observable<any> {
    if (route.data && route.data['preload']) {
      return load();
    }
    return Observable.of(null);
  }
}
- این سرویس ویژه باید اینترفیس PreloadingStrategy را پیاده سازی کند. سپس باید متد اجباری preload آن‌را افزود و تکمیل نمود.
- پارامتر اول این متد، اطلاعاتی را در مورد مسیر جاری در اختیار ما قرار می‌دهد و دومین پارامتر آن متدی است که کار preloading را انجام می‌دهد.
- در اینجا است که تصمیم می‌گیریم ماژولی را preload کنیم یا خیر. برای نمونه در اینجا از خاصیت data مسیریابی استفاده شده‌است. این خاصیت نیز به یک مقدار ثابت اشاره می‌کند (قسمت «ارسال اطلاعات ثابت به مسیرهای مختلف برنامه» قسمت چهارم). برای مثال نام دلخواه آن‌را preload گذاشته‌ایم و اگر مقدار آن به true تنظیم شده بود، آنگاه این مسیر preload خواهد شد. فراخوانی متد load در اینجا به معنای preloading این مسیر است. در غیراینصورت null را بازگشت می‌دهیم.


در ادامه نیاز است در فایل src\app\app-routing.module.ts، بجای معرفی PreloadAllModules، این استراتژی سفارشی خود را معرفی کرد:
import { SelectiveStrategyService } from './selective-strategy.service';

@NgModule({
  imports: [RouterModule.forRoot(
    routes,
    { enableTracing: true, preloadingStrategy: SelectiveStrategyService
     /*, preloadingStrategy: PreloadAllModules*/  /*, useHash: true*/ }
  )],
و همچنین تعریف مسیریابی برنامه به این صورت تغییر می‌کند:
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    //canLoad: [AuthGuard] 
    canActivate: [AuthGuard],
    data: { preload: true }
  },
در اینجا نحوه‌ی مقدار دهی خاصیت data را به اطلاعات ثابت preload: true مشاهده می‌کنید. این اطلاعاتی است که در سرویس SelectiveStrategy سفارشی ما بررسی شده و بر اساس آن در مورد پیش بارگذاری این مسیر تصمیم‌گیری می‌شود.

برای آزمایش آن، برنامه را مجدا اجرا کرده و صفحه را refresh کنید. سپس برگه‌ی network ابزار developers را نیز باز نگه دارید. مشاهده خواهید کرد که علاوه بر فایل‌های js اصلی برنامه که در فایل index.html ارجاعی را دارند، فایل 5.chunk.js نیز پیش بارگذاری شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-09.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
نظرات مطالب
تبدیل PDF به تصویر با استفاده از API توکار Window 8.1 در برنامه‌های غیر مترو دات نت
یک نکته‌ی تکمیلی
اگر با استفاده از مطالب فوق قصد داشته باشید در WPF یک PDF Viewer درست کنید، می‌توان از متد ذیل استفاده کرد:
        private async Task<List<System.Windows.Media.Imaging.BitmapImage>> getPdfPageImages()
        {
            var results = new List<System.Windows.Media.Imaging.BitmapImage>();
            using (var randomAccessStream = File.Open("PieChartPdfReport.pdf", FileMode.Open).AsRandomAccessStream())
            {
                var pdfDocument = await PdfDocument.LoadFromStreamAsync(randomAccessStream);
                for (uint i = 0; i < pdfDocument.PageCount; i++)
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        using (var stream = memoryStream.AsRandomAccessStream())
                        {
                            using (var page = pdfDocument.GetPage(i))
                            {
                                // Set render options
                                var renderOptions = new PdfPageRenderOptions
                                {
                                    BackgroundColor = Colors.LightGray,
                                    DestinationHeight = (uint)(page.Size.Height * 10)
                                };

                                await page.RenderToStreamAsync(stream); //, renderOptions);
                                await stream.FlushAsync();

                                var bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
                                bitmapImage.BeginInit();
                                //Without this, BitmapImage uses lazy initialization by default and the stream will be closed by then.
                                bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
                                bitmapImage.StreamSource = memoryStream;
                                bitmapImage.EndInit();

                                results.Add(bitmapImage);
                            }
                        }
                    }
                }
            }
            return results;
        }
بعد برای استفاده از BitmapImageهای حاصل از  آن، برای مثال نمایش اولین صفحه در یک کنترل Image استاندارد، می‌توان نوشت:
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var images = await this.getPdfPageImages();
            ImagePdf.Source = images.First();
        }
مطالب
مقداردهی خودکار Created Date و Updated Date برای تمام رکوردهای موجودیت‌های یک پروژه توسط Entity Framework Core
فرض کنید می‌خواهید برای یک پروژه، امکانی را درنظر بگیرید که بتوان برای تمامی رکوردهای موجودیت‌های (Entity) آن پروژه، زمان ساخته شدن و به روزرسانی، به صورت خودکار ثبت شود.
کار با تعریف یک کلاس پایه به شکل زیر شروع می‌شود:
public class BaseEntity
    {
        public DateTimeOffset CreatedDate { get; set; }
        public DateTimeOffset UpdatedDate { get; set; }
    }
سپس برای اینکه کار مقداردهی، به صورت خودکار انجام گیرد، باید متدهای SaveChanges و SaveChangesAsync به شکل زیر در ApplicationDbContext پروژه override شوند:
//override because we need add created and updated date to some entities
        public override async Task<int> SaveChangesAsync(
            CancellationToken cancellationToken = default(CancellationToken))
        {
            AddCreatedUpdatedDate();
            return (await base.SaveChangesAsync(true, cancellationToken));
        }

        //override because we need add created and updated date to some entities
        public override int SaveChanges()
        {
            AddCreatedUpdatedDate();
            return base.SaveChanges();
        }
تابع AddCreatedUpdatedDate نیز به شکل زیر تعریف خواهد شد:
 /// <summary>
        /// Add created and updated date to any entities that
        /// inherit from BaseEntity class
        /// </summary>
        public void AddCreatedUpdatedDate()
        {
            var entries = ChangeTracker
                .Entries()
                .Where(e => e.Entity is BaseEntity && (
                    e.State == EntityState.Added
                    || e.State == EntityState.Modified));

            foreach (var entityEntry in entries)
            {
                ((BaseEntity)entityEntry.Entity).UpdatedDate = DateTimeOffset.UtcNow;

                if (entityEntry.State == EntityState.Added)
                {
                    ((BaseEntity)entityEntry.Entity).CreatedDate = DateTimeOffset.UtcNow;
                }
            }
        }
همانطور که ملاحظه می‌نمایید از ChangeTracker استفاده شده‌است که پیشتر مطلب کاملی در سایت در رابطه با آن منتشر شده‌است. در حقیقت لیستی از رکوردهای موجودیت‌هایی را که از BaseEntity ارث بری کرده باشند و در حال اضافه شدن یا ویرایش شدن هستند، در entries قرار می‌دهیم و سپس بررسی می‌کنیم که اگر این رکورد در حال اضافه شدن برای اولین بار است، آنگاه مقدار برابری را برای CreatedDate و UpdatedDate آن درنظر می‌گیریم؛ اما اگر این رکورد در حال ویرایش شدن باشد، آنگاه فقط مقدار UpdatedDate را به‌روزرسانی می‌کنیم.
حال برای اینکه موجودیتی دارای این قابلیت شود که برای هر رکورد آن، تاریخ ساخت و به روز رسانی به صورت خودکار ثبت شود، باید از کلاس پایه BaseEntity ارث بری نماید. برای مثال:
public class Student: BaseEntity
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
    public DateTimeOffset? DateOfBirth { get; set; }
    public decimal Height { get; set; }
    public float Weight { get; set; }
}
نظرات مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
سلام؛ هنگام حذف یک سطر با پیغام 400 یا همان BadRequest مواجه میشم و عجیب اینکه type رو بصورت xml نشون میده داخل delete به جای json تو بخش Network یعنی عکس دوم (یک لحظه اون سطر از داخل سطرها حذف میشه به محض رفرش کردن گرید دوباره نشون میده): 

اینم کدهای بنده داخل View متناظر:

destroy: {
                        url: function (blog) {
                            return "@Url.Action("DeleteBlog","Admin")/" + blog.id;
                        },
                        contentType: 'application/json; charset=utf-8',
                        type: "DELETE"
                    },

و این بخش : 

 $("#report-grid").kendoGrid({
                dataSource: blogDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                filterable: true,
                reorderable: true,
                selectable: true,
                editable: {
                    confirmation: "آیا مایل به حذف ردیف انتخابی هستید؟",
                    destroy: true, // whether or not to delete item when button is clicked
                    mode: "popup" // options are "incell", "inline", and "popup"

اینم کدهای اکشن متد Delete بنده :

  [HttpDelete]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> DeleteBlog(long id)
  {
          var blog = _blogRepository.Get(id);
          if (blog == null)
            return NotFound();
            blog.IsDeleted = true;
          _blogRepository.Update(blog);
           await _unitOfWork.SaveAsync();
           return Json(blog);
  }
مطالب
متد جدید Order در دات نت 7
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که مرتب سازی مجموعه‌های ساده را انجام می‌دهند.


روش متداول مرتب سازی مجموعه‌های ساده تا پیش از دات نت 7

فرض کنید لیستی از اعداد را داریم:
var numbers = new List<int> { -7, 1, 5, -6 };
تا پیش از دات نت 7 با استفاده از متدهای OrderBy و OrderByDescending موجود به همراه LINQ، امکان مرتب سازی صعودی و نزولی این لیست وجود دارد:
var sortedNumbers1 = numbers.OrderBy(n => n);
var sortedNumbers2 = numbers.OrderByDescending(n => n);
که در اینجا ذکر پارامتر keySelector ضروری است:
public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey>(
   [NotNull] this IEnumerable<TSource> source,
   [NotNull] Func<TSource,TKey> keySelector)
هرچند می‌شد طراحی آن ساده‌تر باشد و حداقل برای مجموعه‌های ساده، نیازی به ذکر آن نباشد.


روش جدید مرتب سازی مجموعه‌های ساده در دات نت 7

دات نت 7 به همراه دو متد جدید Order و OrderDescending است که دیگر نیازی به ذکر پارامتر keySelector ذکر شده را ندارند:
var sortedNumbers3 = numbers.Order();
var sortedNumbers4 = numbers.OrderDescending();
و امضای آن‌ها به صورت زیر است:
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source)
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source)
که در حقیقت دو متد الحاقی جدید قابل اعمال بر روی انواع و اقسام IEnumerableها هستند.


در مورد سایر مجموعه‌های پیچیده چطور؟

فرض کنید کلاس User را:
public class User
{
   public string Name { set; get; }
   public int Age { set; get; }
}
 به همراه لیستی از آن تعریف کرده‌ایم:
List<User> users = new()
                           {
                               new User { Name = "User 1", Age = 34 },
                               new User { Name = "User 2", Age = 24 },
                           };
سؤال: آیا اگر متد Order را بر روی این لیست فراخوانی کنیم:
var orderedUsers = users.Order();
برای مثال این مجموعه بر اساس نام و سن مرتب خواهد شد؟ که پاسخ آن خیر است و همچنین استثنائی را صادر می‌کند بر این مبنا که باید کلاس User، اینترفیس IComparable را پیاده سازی کند تا بتوان آن‌ها را مقایسه کرد؛ برای مثال چیزی شبیه به تغییرات زیر:
public class User : IComparable<User>
{
    public string Name { set; get; }
    public int Age { set; get; }

    public int CompareTo(User? other)
    {
        if (ReferenceEquals(this, other))
        {
            return 0;
        }

        if (ReferenceEquals(null, other))
        {
            return 1;
        }

        var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal);
        if (nameComparison != 0)
        {
            return nameComparison;
        }

        return Age.CompareTo(other.Age);
    }
}
که در یک چنین مواردی شاید بهتر باشد از همان متد OrderBy پیشین استفاده کرد که الزامی به پیاده سازی اینترفیس IComparable را ندارد:
var orderedUsers2 = users.OrderBy(user => user.Name).ThenBy(user => user.Age);
اشتراک‌ها
Async Main در C# 7.1
In C# 7.1, the language extends the valid signatures of an entrypoint to allow these async overloads of the Main method to be valid.

public static void Main();
public static int Main();
public static void Main(string[] args);
public static int Main(string[] args);
public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);
Async Main در C# 7.1
مطالب
سفارشی سازی ASP.NET Core Identity - قسمت چهارم - User Claims
از نگارش‌های پیشین ASP.NET، هنوز هم اطلاعات شیء User مانند User.Identity.Name در ASP.NET Core نیز در دسترس هستند. به این ترتیب زمانیکه کاربری به سیستم وارد شد، دیگر نیازی نیست تا جهت یافتن Name او، از بانک اطلاعاتی کوئری گرفت. خاصیت Name یاد شده به صورت خودکار از کوکی رمزنگاری شده‌ی او دریافت شده و در اختیار برنامه قرار می‌گیرد. این Name در ASP.NET Core Identity، اصطلاحا یک User Claim پیش‌فرض نام دارد و به صورت خودکار ایجاد و مقدار دهی می‌شود. اکنون این سؤال مطرح می‌شود که آیا می‌توان خاصیت دیگری را به شیء User.Identity اضافه کرد؟


جدول AppUserClaims


جدول AppUserClaims، جزو جداول اصلی ASP.NET Core Identity است و هدف آن ذخیره‌ی اطلاعات ویژه‌ی کاربران و بازیابی ساده‌تر آن‌ها از طریق کوکی‌های آن‌ها است (همانند User.Identity.Name). زمانیکه کاربری به سیستم وارد می‌شود، بر اساس UserId او، تمام رکوردهای User Claims متعلق به او از این جدول واکشی شده و به صورت خودکار به کوکی او اضافه می‌شوند.

در پروژه‌ی DNT Identity از این جدول استفاده نمی‌شود. چون اطلاعات User Claims مورد نیاز آن، هم اکنون در جدول AppUsers موجود هستند. به همین جهت افزودن این نوع User Claimها به جدول AppUserClaims، به ازای هر کاربر، کاری بیهوده است. سناریویی که استفاده‌ی از این جدول را با مفهوم می‌کند، ذخیره سازی تنظیمات ویژه‌ی هرکاربر است (خارج از فیلدهای جدول کاربران). برای مثال اگر سایتی را چندزبانه طراحی کردید، می‌توانید یک User Claim سفارشی جدید را برای این منظور ایجاد و زبان انتخابی کاربر را به عنوان یک رکورد جدید مخصوص آن در این جدول ویژه ثبت کنید. مزیت آن این است که واکشی و افزوده شدن اطلاعات آن به کوکی شخص، به صورت خودکار توسط فریم ورک صورت گرفته و در حین مرور صفحات توسط کاربر، دیگر نیازی نیست تا اطلاعات زبان انتخابی او را از بانک اطلاعاتی واکشی کرد.
بنابراین برای ذخیره سازی تنظیمات با کارآیی بالای ویژه‌ی هرکاربر، جدول جدیدی را ایجاد نکنید. جدول User Claim برای همین منظور درنظر گرفته شده‌است و پردازش اطلاعات آن توسط فریم ورک صورت می‌گیرد.


ASP.NET Core Identity چگونه اطلاعات جدول AppUserClaims را پردازش می‌کند؟

ASP.NET Identity Core در حین لاگین کاربر به سیستم، از سرویس SignInManager خودش استفاده می‌کند که با نحوه‌ی سفارشی سازی آن پیشتر در قسمت دوم این سری آشنا شدیم. سرویس SignInManager پس از لاگین شخص، از یک سرویس توکار دیگر این فریم ورک به نام UserClaimsPrincipalFactory جهت واکشی اطلاعات User Claims و همچنین Role Claims و افزودن آن‌ها به کوکی رمزنگاری شده‌ی شخص، استفاده می‌کند.
بنابراین اگر قصد افزودن User Claim سفارشی دیگری را داشته باشیم، می‌توان همین سرویس توکار UserClaimsPrincipalFactory را سفارشی سازی کرد (بجای اینکه الزاما رکوردی را به جدول AppUserClaims اضافه کنیم).

اطلاعات جالبی را هم می‌توان از پیاده سازی متد CreateAsync آن استخراج کرد:
  public virtual async Task<ClaimsPrincipal> CreateAsync(TUser user)
1) userId شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserIdClaimType به کوکی او اضافه می‌شود.
2) userName شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserNameClaimType به کوکی او اضافه می‌شود.
3) security stamp او (آخرین بار تغییر اطلاعات اکانت کاربر) نیز یک Claim پیش‌فرض است.
4) اگر نقش‌هایی به کاربر انتساب داده شده باشند، تمام این نقش‌ها واکشی شده و به عنوان یک Claim جدید به کوکی او اضافه می‌شوند.
5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه می‌شوند. در ASP.NET Identity Core نقش‌ها نیز می‌توانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).

بنابراین حداقل مدیریت Claims این 5 مورد خودکار است و اگر برای مثال نیاز به Id کاربر لاگین شده را داشتید، نیازی نیست تا آن‌را از بانک اطلاعاتی واکشی کنید. چون این اطلاعات هم اکنون در کوکی او موجود هستند.


سفارشی سازی کلاس UserClaimsPrincipalFactory جهت افزودن User Claims سفارشی

تا اینجا دریافتیم که کلاس UserClaimsPrincipalFactory کار مدیریت Claims پیش‌فرض این فریم ورک را برعهده دارد. در ادامه از این کلاس ارث بری کرده و متد CreateAsync آن‌را جهت افزودن Claims سفارشی خود بازنویسی می‌کنیم. این پیاده سازی سفارشی را در کلاس ApplicationClaimsPrincipalFactory می‌توانید مشاهده کنید:
        public override async Task<ClaimsPrincipal> CreateAsync(User user)
        {
            var principal = await base.CreateAsync(user).ConfigureAwait(false); 
            addCustomClaims(user, principal);
            return principal;
        }

        private static void addCustomClaims(User user, IPrincipal principal)
        {
            ((ClaimsIdentity) principal.Identity).AddClaims(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.Integer),
                new Claim(ClaimTypes.GivenName, user.FirstName ?? string.Empty),
                new Claim(ClaimTypes.Surname, user.LastName ?? string.Empty),
                new Claim(PhotoFileName, user.PhotoFileName ?? string.Empty, ClaimValueTypes.String),
            });
        }
در حین بازنویسی متد CreateAsync، ابتدا base.CreateAsync را فراخوانی کرده‌ایم، تا اخلالی در عملکرد این فریم ورک رخ ندهد و هنوز هم همان مواردی که در قسمت قبل توضیح داده شد، به صورت پیش فرض به کوکی شخص اضافه شوند. سپس در متد addCustomClaims، تعدادی Claim سفارشی خاص خودمان را اضافه کرده‌ایم.
برای مثال نام، نام خانوادگی و نام تصویر شخص به صورت Claimهایی جدید به کوکی او اضافه می‌شوند. در این حالت دیگر نیازی نیست تا به ازای هر کاربر، جدول AppUserClaims را ویرایش کرد و اطلاعات جدیدی را افزود و یا تغییر داد. همینقدر که کاربر به سیستم لاگین کند، شیء User او به متد Create کلاس UserClaimsPrincipalFactory ارسال می‌شود. به این ترتیب می‌توان به تمام خواص این کاربر دسترسی یافت و در صورت نیاز آن‌ها را به صورت Claimهایی به کوکی او افزود.

پس از تدارک کلاس ApplicationClaimsPrincipalFactory‌، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
services.AddScoped<IUserClaimsPrincipalFactory<User>, ApplicationClaimsPrincipalFactory>();
services.AddScoped<UserClaimsPrincipalFactory<User, Role>, ApplicationClaimsPrincipalFactory>();
یکبار ApplicationClaimsPrincipalFactory را به عنوان پیاده سازی کننده‌ی IUserClaimsPrincipalFactory معرفی کرده‌ایم. همچنین یکبار هم سرویس توکار UserClaimsPrincipalFactory را به سرویس سفارشی خودمان هدایت کرده‌ایم. به این ترتیب مطمئن خواهیم شد که همواره از ApplicationClaimsPrincipalFactory ما استفاده خواهد شد (حتی اگر UserClaimsPrincipalFactory اصلی از سیستم تزریق وابستگی‌ها درخواست شود).
 

چگونه به اطلاعات User Claims در سرویس‌های برنامه دسترسی پیدا کنیم؟

برای دسترسی به اطلاعات User Claims نیاز به دسترسی به HttpContext جاری را داریم. در این مورد و تزریق سرویس IHttpContextAccessor جهت تامین آن، در مطلب «بررسی روش دسترسی به HttpContext در ASP.NET Core» پیشتر بحث شده‌است.
به علاوه در کلاس IdentityServicesRegistry، تزریق وابستگی‌های سفارشی‌تری نیز صورت گرفته‌است:
services.AddScoped<IPrincipal>(provider =>
    provider.GetService<IHttpContextAccessor>()?.HttpContext?.User ?? ClaimsPrincipal.Current);
در اینجا اگر نیاز به اطلاعات Claims شیء User را داشتید، می‌توانید اینترفیس IPrincipal را هم بجای IHttpContextAccessor، به سازنده‌ی کلاس مدنظر خود تزریق کنید.


چگونه اطلاعات User Claims سفارشی را دریافت کنیم؟

برای کار ساده‌تر با Claims یک کلاس کمکی به نام IdentityExtensions به پروژه اضافه شده‌است و متدهایی مانند دو متد ذیل را می‌توانید در آن مشاهده کنید:
        public static string FindFirstValue(this ClaimsIdentity identity, string claimType)
        {
            return identity?.FindFirst(claimType)?.Value;
        }

        public static string GetUserClaimValue(this IIdentity identity, string claimType)
        {
            var identity1 = identity as ClaimsIdentity;
            return identity1?.FindFirstValue(claimType);
        }
در اینجا نحوه‌ی استخراج اطلاعات را از شیء User و یا User.Identity مشاهده می‌کنید. تنها کافی است claimType ایی را درخواست کرده و سپس مقدار آن‌را از کوکی شخص به نحو فوق واکشی کنیم.
برای نمونه متد GetUserDisplayName این کلاس کمکی، از همان Claims سفارشی که در کلاس ApplicationClaimsPrincipalFactory تعریف کردیم، اطلاعات خود را استخراج می‌کند و اگر در View ایی خواستید این اطلاعات را نمایش دهید، می‌توانید بنویسید:
 @User.Identity.GetUserDisplayName()


چگونه پس از ویرایش اطلاعات کاربر، اطلاعات کوکی او را نیز به روز کنیم؟

در پروژه قسمتی وجود دارد جهت ویرایش اطلاعات کاربران (UserProfileController). اگر کاربری برای مثال نام و نام خانوادگی خود را ویرایش کرد، می‌خواهیم بلافاصله متد GetUserDisplayName اطلاعات صحیح و به روزی را از کوکی او دریافت کند. برای اینکار یا می‌توان او را وادار به لاگین مجدد کرد (تا پروسه‌ی رسیدن به متد CreateAsync کلاس ApplicationClaimsPrincipalFactory طی شود) و یا روش بهتری نیز وجود دارد:
 // reflect the changes, in the current user's Identity cookie
await _signInManager.RefreshSignInAsync(user).ConfigureAwait(false);
در اینجا تنها کافی است متد RefreshSignInAsync را مجددا بر اساس اطلاعات ویرایش شده‌ی کاربر، فراخوانی کنیم تا کوکی او را بلافاصله به روز کند و این روش نیازی به اجبار به لاگین مجدد کاربر را ندارد.


کدهای کامل این سری را در مخزن کد DNT Identity می‌توانید ملاحظه کنید.