مطالب
شروع به کار با DNTFrameworkCore - قسمت 4 - پیاده‌سازی CRUD API موجودیت‌ها
پس از معرفی DNTFrameworkCore، طراحی موجودیت‌های سیستم و پیاده‌سازی DTOها، اعتبارسنج‌ها و سرویس‌های متناظر آنها، در این مطلب روش پیاده سازی CRUD API یکسری موجودیت فرضی را با استفاده از امکانات این زیرساخت بررسی خواهیم کرد.

برای شروع لازم است بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web

همچنین برای اعمال خودکار مهاجرت‌های بانک اطلاعاتی، بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web.EntityFramework

سپس با استفاده از متد الحاقی MigrateDbContext به شکل زیر می‌توان فرآیند مذکور را خودکار کرد:
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build()
            .MigrateDbContext<ProjectDbContext>()
            .Run();
    }

//...
}

مثال اول: پیاده سازی CRUD API یک موجودیت ساده
[Route("api/[controller]")]
public class BlogsController : CrudController<IBlogService, int, BlogModel>
{
    public BlogsController(IBlogService service) : base(service)
    {
    }

    protected override string CreatePermissionName => PermissionNames.Blogs_Create;
    protected override string EditPermissionName => PermissionNames.Blogs_Edit;
    protected override string ViewPermissionName => PermissionNames.Blogs_View;
    protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
}
کار با ارث‌بری از CrudController جنریک شروع می‌شود؛ سپس نیاز است نوع سرویس، نوع مدل و همچنین نوع شناسه مرتبط با موجودیت مورد‎‌نظر را از طریق Type Parameter مشخص کنید. این کنترلر پایه تعاریف مختلفی دارد که برحسب نیاز خود می‌توانید از آنها استفاده کنید. در ادامه نیز برای اعمال دسترسی خاصی برای عملیات CRUD، نیاز است نام دسترسی‌ها را مشخص کنید. کار تمام است؛ برای استفاده از آن می‌توانید با اجرای پروژه DNTFrameworkCore.TestAPI به شکل زیر عمل کنید:
ابتدا نیاز است با استفاده از افزونه‌ی Postman یک Environment جدید ایجاد کنید.

در اینجا دو متغیر endpoint و token ‌برای سرعت بخشیدن به فرآیند تست API تولیدی ایجاد شده‌اند. مقدار endpoint آن از ابتدا مشخص می‌باشد؛ ولی برای مقداردهی token، از ترفند زیر می‌توان استفاده کرد:

در برگه Tests آن می‌توان متغیر تعریف شده در Environment ایجاد شده را با قطعه کد زیر مقداردهی کرد:
 var data = JSON.parse(responseBody)
pm.environment.set("token", data.token);
و برای استفاده از این متغیر به شکل زیر عمل کنید:

حال برای ارسال درخواست‌های HTTP به BlogsController به شکل زیر عمل کنید:

پشت صحنه اکشن GET مربوط به BlogsController از متد سرویس ReadPagedListAsync استفاده می‌شود که خروجی آن در صورت مشخص نکردن TReadModel، برای یک موجودیت ساده مانند واحد سنجش، از همان TModel استفاده خواهد شد. در تصویر بالا لیست صفحه بندی شده موجودیت Blog را مشاهده می‌کنید. برای درخواست صفحه دیگر و جستجوی پویا می‌توان به شکل زیر عمل کرد:
query={"page":1,"pageSize":100,"filter":{"logic":"and","filters":[{"field":"title","value":"Blog1","operator":"startswith"}]}}
همانطور که در مطالب گذشته اشاره شد، ورودی متدهای Read موجود در سرویس‌ها از نوع IFilteredPagedQueryModel می‌باشد. یک ModelBinder سفارشی هم برای بایند خودکار این کوئری استرینک با محتوای یک شیء JSON، در زیرساخت طراحی شده است.

در پشت صحنه اکشن POST از متد CreateAsync سرویس مرتبط استفاده می‌شود و همانطور که در قسمت قبلی عنوان شد، Id و RowVersion مدل ارسالی، مقداردهی خواهد شد. 

در پشت صحنه اکشن ‎‎‎‎‎‎GET/{id}‎ از متد FindAsync سرویس مرتبط استفاده می‌شود و خروجی آن از نوع TModel می‌باشد. 

در پشت صحنه اکشن PUT از متد EditAsync سرویس مرتبط استفاده می‌شود که ورودی آن نوع TModel می‌باشد. همانطور که قبلا اشاره شده بود و در خروجی حاصل از درخواست ویرایش بالا مشخص می‎‌باشد، مکانیزم مدیریت استثناهای حاصل از مباحث همزمانی نیز به درستی انجام شده است.
برای حذف یک Blog می‌توان با ارسال درخواست DELETE به آدرس زیر به این هدف رسید:
{{endpoint}}/blogs/10
در پشت صحنه این اکشن نیز از متد DeleteAsync سرویس مرتبط استفاده می‌شود.
‌‌‌
مثال دوم: پیاده سازی و استفاده از CRUD API در سناریوهای Master-Detail
[Route("api/[controller]")]
public class UsersController : CrudController<IUserService, long, UserReadModel, UserModel>
{
    private readonly ILookupService _lookupService;

    public UsersController(IUserService service, ILookupService lookupService) : base(service)
    {
        _lookupService = lookupService ?? throw new ArgumentNullException(nameof(lookupService));
    }

    protected override string CreatePermissionName => PermissionNames.Users_Create;
    protected override string EditPermissionName => PermissionNames.Users_Edit;
    protected override string ViewPermissionName => PermissionNames.Users_View;
    protected override string DeletePermissionName => PermissionNames.Users_Delete;

    [HttpGet("[action]")]
    [PermissionAuthorize(PermissionNames.Users_Create, PermissionNames.Users_Edit)]
    public async Task<IActionResult> RoleList()
    {
        var result = await _lookupService.ReadRolesAsync();
        return Ok(result);
    }
}
‌‌
موجودیت User از جمله موجودیت‌هایی می‌باشد که نیاز است ReadModel مجزایی برای آن درنظر گرفت؛ چرا که در زمان نمایش لیستی کاربران، نیاز به واکشی گروه‌های کاربری متصل و دسترسی‌های خاص آن، نمی‌باشد. همچنین اکشن متد RoleList برای دریافت لیست گروه‌های کاربری موجود در سیستم نیز پیاده‌سازی شده است. باتوجه به اینکه نیاز است از این اکشن متد در عملیات ثبت و ویرایش استفاده کرد، بر روی آن با استفاده از فیلتر سفارشی PermissionAuthorize، بررسی شده است که کاربر جاری یکی از دسترسی‌های Users_Create یا Users_Edit را داشته باشد.
‎‎‎‎‎
درخواست‌های GET و DELETE مشابه مثال اول می‌باشد؛ برای درخواست‌های POST و PUT آن می‌توان به شکل زیر عمل کرد:

باتوجه به اینکه UserRole به عنوان یکی از وابستگی‌های موجودیت User محسوب می‌شود، در پاسخ درخواست GET مرتبط با کاربری با شناسه 2، roles آن، لیستی از UserRoleModel هستند که به عنوان یک DetailModel طراحی شده است. به عنوان مثال برای حذف اتصال یک گروه کاربری باید درخواست PUT را به شکل زیر ارسال کنید:

این‌بار اگر برای درخواست GET کاربر با شناسه 2 اقدام کنیم، به خروجی زیر خواهم رسید:

{
    "userName": "rabbal",
    "displayName": "غلامرضا ربال",
    "password": null,
    "isActive": false,
    "roles": [],
    "permissions": [],
    "ignoredPermissions": [],
    "rowVersion": "AAAAAAACGxI=",
    "id": 2
}


برای بررسی بیشتر، پیشنهاد می‌کنم پروژه  DNTFrameworkCore.TestAPI  موجود در مخزن این زیرساخت را بازبینی کنید.  
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت ششم - کامپوننت‌های تو در تو
گاهی از اوقات جهت refactoring یک template بزرگ، بهتر است آن‌را به چند template کوچک خرد کرد و سپس از جمع آن‌ها به صورت یک template اصلی استفاده نمود. در این حالت نیاز است بین این زیر کامپوننت‌ها و کامپوننت‌های دربرگیرنده‌ی آن‌ها ارتباطات لازم را برقرار کرد.
تا اینجا در قسمت سوم، نحوه‌ی قراردادن یک کامپوننت را در کامپوننتی دیگر، توسط مقدار دهی خاصیت directives مزین کننده‌ی Component بررسی کردیم. همینقدر که یک کامپوننت دارای selector باشد، قابلیت قرارگرفتن در یک کامپوننت دیگر را دارد. اما چگونه باید بین این کامپوننت‌ها ارتباط برقرار کرد؟


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

مثال نمایش لیست محصولات سری جاری، دارای ستون «5Star Rating» است. در این قسمت می‌خواهیم بجای نمایش عددی این امتیازها، کامپوننتی را طراحی کنیم که نماش ستاره‌ای آن‌ها را سبب شود. این کامپوننت باید بتواند یک مقدار ورودی، یا همان عدد امتیاز محصول را از کامپوننت دربرگیرنده‌ی آن دریافت کند. همچنین می‌خواهیم اگر کاربر بر روی این ستاره‌ها کلیک کرد، کامپوننت در برگیرنده را نیز مطلع سازیم.
در این مثال در فایل product-list.component.html چنین سطری تعریف شده‌است:
 <td>{{ product.starRating }}</td>
البته می‌توان در همینجا کدهای نمایش ستاره‌ای را بجای درج مقدار عددی آن قرار داد، اما ... قالب جاری را بیش از اندازه شلوغ خواهد کرد. به همین دلیل بهتر است نمایش آن‌را تبدیل به یک کامپوننت مجزا کرد. به علاوه در این حالت، قابلیت استفاده‌ی مجدد از آن در سایر کامپوننت‌ها نیز وجود خواهد داشت.
با توجه به اینکه کامپوننت نمایش ستاره‌ای امتیازها، قابلیت استفاده‌ی مجدد را دارد و الزامی ندارد که حتما در لیست محصولات، بکار گرفته شود، بهتر است محل تعریف آن‌را به خارج از پوشه‌ی products فعلی منتقل کنیم. برای مثال می‌توان پوشه‌ی app\shared را برای آن و تمامی کامپوننت‌های با قابلیت استفاده‌ی مجدد ایجاد کرد.


برای شروع، فایل جدید App\shared\star.component.ts را اضافه کنید؛ با کدهای کامل ذیل:
import { Component, OnChanges, Input, Output, EventEmitter } from 'angular2/core';
 
@Component({
    selector: 'ai-star',
    templateUrl: 'app/shared/star.component.html',
    styleUrls: ['app/shared/star.component.css']
})
export class StarComponent implements OnChanges {
    @Input() rating: number;
    starWidth: number;
    @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
 
    ngOnChanges(): void {
        this.starWidth = this.rating * 86 / 5;
    }
 
    onClick() {
        this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
    }
}
روش ساخت این کامپوننت نیز همانند سایر کامپوننت‌ها است و اصول کلی آن تفاوتی نمی‌کند. در اینجا نیز کلاسی وجود دارد که export شده و همچنین به Component مزین است. مقدار selector آن نیز به ai-star تنظیم شده‌است.
سپس مسیر template و مسیر فایل css ویژه‌ی آن، در تزئین کننده‌ی Component مشخص شده‌اند. محتوای کامل این دو فایل را در ذیل مشاهده می‌کنید:
الف) محتوای فایل App\shared\star.component.html
<div class="crop"
     [style.width.px]="starWidth"
     [title]="rating"
     (click)='onClick()'>
    <div style="width: 86px">
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
    </div>
</div>
ب) محتوای فایل App\shared\star.component.css
.crop {
    overflow: hidden;
}
 
div {
    cursor: pointer;
}
قالب star.component.html کار نمایش پنج ستاره را انجام می‌دهد. عرض کلی آن بر اساس مقدار خاصیت starWidth مشخص می‌شود و بر همین اساس، تعداد نمایان ستاره‌ها، مشخص خواهند شد. خاصیت starWidth به width این div بر حسب px، متصل شده‌است (property binding). همچنین خاصیت title این div نیز به مقدار rating متصل شده‌است و اگر بر روی آن کلیک شود، متد onClick را در کلاس متناظر با کامپوننت خود، فراخوانی خواهد کرد (event binding).

معرفی مقدماتی life cycle hooks در قسمت قبل صورت گرفت. در اینجا چون نیاز است به ازای هر بار رندر شدن این کامپوننت، عرض آن متفاوت باشد، بنابراین نیاز است راهی را پیدا کنیم تا بتوان مقدار خاصیت starWidth را متغیر کرد. به همین منظور از hook مخصوص این تغییرات یا همان OnChanges استفاده می‌شود. بنابراین باید کلاس این کامپوننت، اینترفیس OnChanges را پیاده سازی کند. پس از آن، importهای لازم جهت تعریف OnChanges به ابتدای فایل اضافه شده و همچنین متد ngOnChanges نیز جهت تکمیل کار پیاده سازی اینترفیس OnChanges، به کلاس جاری اضافه می‌شود.
کار متد ngOnChanges، تبدیل عدد امتیاز یک محصول، به عرض div نمایش ستاره‌ها است.


مکانیزم کار رخداد ngOnChanges و دریافت اطلاعات از والد

متد ngOnChanges، تنها به خواص ویژه‌ای به نام «input properties» واکنش نشان می‌دهد. اگر یک کامپوننت تو در توی قرار گرفته‌ی در یک کامپوننت دیگر، بخواهد اطلاعاتی را از والد خود دریافت کند، باید خاصیتی را در معرض دید آن دربرگیرنده قرار دهد. این کار توسط decorator ویژه‌ای به نام ()Input@ انجام می‌شود.
به همین جهت است که پیش از خاصیت rating در کلاس  StarComponent، شاهد درج مزین کننده‌ی ویژه‌ی ()Input@ هستیم:
export class StarComponent implements OnChanges {
      @Input() rating: number;
مزین کننده‌ی ()Input@ را به هر خاصیتی با هر نوعی، می‌توان انتساب داد. در اینجا نیاز است کامپوننت نمایش ستاره‌ای امتیازها، عدد امتیاز را از والد خود دریافت کنند. به همین جهت خاصیت امتیاز را از نوع «خاصیت ورودی» مشخص کرده‌ایم.
پس از آن، کامپوننت دربرگیرنده یا والد، این خاصیت ورودی ویژه را از طریق روش property binding متداول، مقدار دهی می‌کند:
 [rating]='product.starRating'

بدیهی است در اینجا چون خاصیت starWidth از نوع ورودی تعریف نشده‌است، قابلیت property binging فوق را در کامپوننت والد، ندارد.

اکنون به ازای هر بار نمایش این کامپوننت فرزند، خاصیت rating ورودی آن مقدار دهی شده و مقدار آن در رخداد ngOnChanges قابل دسترسی و استفاده خواهد بود. اینجا است که می‌توان از این مقدار تغییر یافته، جهت ترجمه‌ی آن به عرض div نمایش ستاره‌ها، استفاده کرد.


ارسال داده‌ها از کامپوننت فرزند به کامپوننت والد

تا اینجا با استفاده از «خواص ورودی» امکان دسترسی به مقادیر ارسالی از طرف والد را در کامپوننت فرزند، پیدا کردیم. عکس آن نیز امکان پذیر است؛ اما توسط رخدادها.
کامپوننت فرزند، با استفاده از decorator ویژه‌ی دیگری به نام ()Output@ امکان ارسال رخدادها را به کامپوننت والد پیدا می‌کند:
export class StarComponent implements OnChanges {
    @Input() rating: number;
    starWidth: number;
    @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
در اینجا خاصیت ratingClicked را که با ()Output@ مزین شده‌است، مشاهده می‌کنید. نوع این خاصیت باید از نوع رخدادها باشد که در AngularJS 2.0 توسط شیء EventEmitter جنریک، تعریف شده‌است. در این مثال، نوع رخداد و مقداری که توسط آن ارسال می‌شود، رشته‌ای درنظر گرفته شده‌است. اگر نیاز به ارسال چندین مقدار بود، می‌توان یک شیء را در اینجا مشخص کرد.
در مثال جاری اگر کاربر بر روی div ستاره‌های نمایش داده شده کلیک کند، اتصال به آن از طریق event binging متداول انجام می‌شود (متد جدید onClick به رخداد click متصل شده‌است):
<div class="crop"
     [style.width.px]="starWidth"
     [title]="rating"
     (click)='onClick()'>
و سپس این رخداد کلیک، در کلاس StarComponent به نحو ذیل به والد خود منتقل خواهد شد:
onClick() {
     this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
 }
در اینجا متد emit است که اطلاعات را به کامپوننت دربرگیرنده، ارسال می‌کند. نحوه‌ی تعریف این رشته هم توسط back tick مربوط به ES 6 صورت گرفته‌است.

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

اکنون در کامپوننت والد، باید این مراحل برای دریافت اطلاعات از کامپوننت فرزند خود، طی شوند:
الف) ابتدا نام خاصیت مزین شده‌ی با Output، به عنوان مقصد event binding مشخص می‌شود و سپس متدی در کلاس کامپوننت والد، به آن متصل می‌گردد:
 (ratingClicked)='onRatingClicked($event)'
event$ مقدار ارسالی از کامپوننت فرزند را به کامپوننت والد منتقل می‌کند.
ب) در ادامه، تعریف این متد جدید متصل شده را به کلاس ProductListComponent اضافه می‌کنیم:
onRatingClicked(message: string): void {
    this.pageTitle = 'Product List: ' + message;
}
نوع پارامتر ورودی این متد را با توجه به نوع رشته‌ای <EventEmitter<string در کلاس StarComponent  تعریف کردیم.
به این ترتیب با کلیک بر روی div هر کامپوننت نمایش ستاره‌ای امتیازها، خاصیت pageTitle درج شده‌ی در صفحه تغییر می‌کند.


استفاده از کامپوننت نمایش ستاره‌ای امتیازها

نکات کلی افزودن این کامپوننت جدید، تفاوتی با مطالب عنوان شده‌ی در قسمت سوم، در حین بررسی مراحل افزودن دایرکتیو نمایش لیست محصولات، به کامپوننت ریشه‌ی سایت ندارد و یکی هستند.
برای افزودن و استفاده از این کامپوننت جدید، ابتدا قالب product-list.component.html را گشوده و سپس سطر نمایش عددی امتیاز یک محصول را به نحو ذیل تغییر می‌دهیم:
<td>
    <ai-star [rating]='product.starRating'
             (ratingClicked)='onRatingClicked($event)'>
    </ai-star>
</td>
همانطور که ملاحظه می‌کنید، ابتدا selector این کامپوننت جدید، به صورت یک المان جدید و سفارشی HTML، در قالب کامپوننت لیست محصولات درج می‌شود. همچنین خاصیت rating ورودی آن به عدد امتیاز محصول جاری در حال رندر، متصل شده‌است. به علاوه توسط event binding به خاصیت ratingClicked که از نوع ()Output@ تعریف شده‌است، متد onRatingClicked متصل گردیده‌است.

سپس باید به کلاس کامپوننت لیست محصولات (کامپوننت در برگیرنده) اعلام کرد که این کامپوننت جدید را باید از کجا پیدا کند. برای این منظور فایل product-list.component.ts را گشوده و خاصیت directives این کامپوننت را مقدار دهی می‌کنیم:
import { Component, OnInit } from 'angular2/core';
import { IProduct } from './product';
import { ProductFilterPipe } from './product-filter.pipe';
import { StarComponent } from '../shared/star.component';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css'],
    pipes: [ProductFilterPipe],
    directives: [StarComponent]
})
در اینجا دو کار جدید انجام شده‌است. ابتدا خاصیت directives، به نام کلاس این کامپوننت جدید (StarComponent)، تنظیم شده‌است. سپس تعریف import ماژول این کامپوننت، به ابتدای فایل جاری اضافه شده‌است.

نمونه‌ای از اجرای برنامه را در تصویر ذیل مشاهده می‌کنید:

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


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



خلاصه‌ی بحث

در اینجا نحوه‌ی طراحی API عمومی یک کامپوننت را بررسی کردیم. تا زمانیکه خواص کلاس یک کامپوننت به نحو متداولی تعریف می‌شوند، میدان دید آن‌ها محدود است به قالب تعریف شده‌ی متناظر با آن‌ها. اگر نیاز است خاصیتی خارج از این قالب و به صورت عمومی در کامپوننت دربرگیرنده‌ی دیگری در دسترس قرار گیرد، آن‌را با مزین کننده‌ی ()Input@ مشخص می‌کنیم و اگر قرار است این کامپوننت فرزند، اطلاعاتی را به کامپوننت والد ارسال کند، این‌کار را توسط رخدادها و با تعریف ویژگی ()Output@ و EventEmitter انجام می‌دهد. نوع آرگومان جنریک EventEmitter، تعیین کننده‌ی نوع اطلاعاتی است که قرار است به کامپوننت دربرگیرنده ارسال شوند.
پس از تعریف کامپوننت فرزند، برای تعریف آن در کامپوننت والد، از نام selector آن به عنوان یک المان جدید HTML استفاده می‌شود و سپس با استفاده از property binding، اطلاعات لازم، به خاصیت از نوع ()Input@ کامپوننت فرزند ارسال می‌گردد. از event binding برای دریافت رخدادها از کامپوننت فرزند استفاده می‌شود. در اینجا هر رخدادی که توسط مزین کننده‌ی ()Output@ تعریف شده باشد، می‌تواند به عنوان مقصد event binding تعریف شود و اگر نیاز است به رخدادهای property binding از والد به فرزند، گوش فرا داد، می‌توان اینترفیس OnChanges را در کلاس کامپوننت فرزند پیاده سازی کرد.
مطالب
Performance در AngularJS 1.x قدم ششم
موضوع این مقاله استفاده مستقیم از توابع و عملیات محاسباتی برای Binding در View می‌باشد که در پروژه‌های بزرگ که حجم المنت‌ها در صفحه زیاد است عملکردی منفی در Performance دارد که قابل چشم پوشی نیست. برای اینکه این مورد ملموس باشد بنده مثالی را آماده کرده‌ام که هدف آن بیشتر درک درست شما از این موضوع است.
کد زیر را مشاهده کنید:
<input type="text" ng-model="newItemTitle">
<button type="button" ng-click="add()">افزودن</button>
<ul>
     <li ng-repeat="item in items">{{item.title}}</li>
</ul>
<div ng-show="showMSG()">شما بیش از ۱۰ استان ثبت کرده اید</div>
و قسمت Controller:
$scope.newItemTitle='';           

$scope.add=function(){
     $scope.items.push({
          title:$scope.newItemTitle
     });
}

$scope.items=[
     {title:'اردبیل'},
     {title:'تهران'},
     {title:'اصفهان'},
     {title:'شیراز'},
     {title:'مشهد'},
];  

$scope.showMSG=function(){
     return $scope.items.length>10;
}
 همانطور که مشاهده میکنید این کد یک مثال ساده است که شامل لیست استانها و قسمتی برای افزودن استان جدید و در قسمت پایینتر در صورتی که بیش از ۱۰ استان ثبت شده باشد به کاربر پیغامی نمایش می‌دهیم.

سوال اول، مشکل کجاست؟
این کد کاملا صحیح است، اما بهینه نیست. مشکل اصلی در View است که مستقیما تابع showMSG را صدا میزند. صدا زدن مستقیم توابع از View در قسمت‌هایی که Bind شده‌اند، در Performance نتیجه منفی دارند. منظور از صدا زدن مستقیم توابع در قسمت‌های Bind روش‌های زیر است:
ng-show="showMSG()"
ng-if="showMSG()"
ng-hide="showMSG()==false"
ng-class="{'red',getResult()}"
ng-style="{'width':getWidth()}"
سوال دوم، چرا Performance را کاهش میدهد؟
AngularJS برای اینکه بتواند تکلیف ng-show هایی را که در div نوشته شده‌است، مشخص کند، مجبور است تابع showMSG را صدا بزند. تا این قسمت هیچ مشکلی نیست. اما وقتی که AngularJS می‌خواهد در زمان‌های بعدی هم متوجه تغییرات بشود مجبود هست دوباره تابع showMSG را صدا بزند و این کار تکرار می‌شود و در مواقعی که تغییرات انجام شده هیچ ارتباطی با این Binding ندارند باز هم اجرا می‌شود و این تداوم در اجرا که اکثرا لازم نیستند باعث کاهش Performance می‌شود. حالا فرض کنید در پروژه‌ای بزرگ در بیشتر قسمت‌های صفحه از این روش استفاده کرده اید و پروژه با داده‌های حجیم کار میکند.

سوال سوم، راهکار چیست؟ 
راهکار این موضوع خیلی ساده است؛ اما در بهبود کارآیی پروژه، خیلی تاثیر مثبتی دارد. به طور کلی سعی کنید در ‌Binding‌های View از متغییر استفاده کنید و هیچ تابعی و یا عملیات محاسباتی را در Binding قرار ندهید . منظور از عملیات محاسباتی در Binding روش زیر است:
<div ng-show="items.length>10">شما بیش از ۱۰ استان ثبت کرده اید</div>
حتی این روش هم مناسب نیست. روشی که می‌توان برای حل این مشکل در نظر گرفت به شرح زیر است:
$scope.maxItem=false';           

$scope.add=function(){
     $scope.items.push({
          title:$scope.newItemTitle
     });
     $scope.maxItem=$scope.items.length>10;
}

<div ng-show="maxItem">شما بیش از ۱۰ استان ثبت کرده اید</div>
این مشکل با متفیر maxItem و انتقال منطق به تابع add حل می‌شود.
نتیجه گیری کلی، در پروژه‌هایی که با داده‌های حجیم کار میکنند، هیچ وقت از توابع و عملیات محاسباتی در View استفاده نکنید. 
مطالب دوره‌ها
لیست ها و آرایه ها در #F
برای تعریف لیست در #F فقط کافیست از [] و برای جداسازی آیتم‌های موجود در لیست از عملگر :: (بخوانید cons) استفاده کنید. #F از لیست‌های خالی نیز پشتیبانی می‌کند. به مثال هایی از این دست توجه کنید

#1 let emptyList = []
#2 let oneItem = "one " :: []
#3 let twoItem = "one " :: "two " :: []
#1 تعریف یک لیست خالی
#2 تعریف یک لیست به همراه یک آیتم
#3 تعریف یک لیست به همراه دو آیتم
قبول دارم که دستورالعمل بالا برای مقدار دهی اولیه به لیست کمی طولانی و سخت است. برای همین می‌تونید از روش زیر هم استفاده کنید.
let shortHand = ["apples "; "pears"]
*کد بالا یک لیست با دو آیتم که از نوع رشته ای هستند تولید خواهد کرد.
می‌تونید از عملگر @ برای پیوستن دو لیست به هم نیز استفاده کنید.
let twoLists = ["one, "; "two, "] @ ["buckle "; "my "; "shoe "]

نکته : تمام آیتم‌های موجود در لیست باید از یک نوع باشند. بعنی امکان تعریف لیستی که دارای آیتم هایی با datatype‌های متفاوت باشد باعث تولید خطای کامپایلری می‌شود. اما اگر نیاز به لیستی دارید که باید چند datatype رو هم پوشش دهد می‌تونید از object‌ها استفاده کنید.
let objList = [box 1; box 2.0; box "three"]
در بالا یک لیست از object‌ها رو تعریف کرده ایم. فقط دقت کنید برای اینکه آیتم‌های موجود در لیست رو تبدیل به object کنیم از دستور box قبل از هر آیتم استفاده کردیم.

 در هنگام استفاده از عملگر‌ها @ و :: مقدار لیست تغییر نمی‌کند بلکه یک لیست جدید تولید خواهد شد.
#1 let one = ["one "]
#2 let two = "two " :: one
#3 let three = "three " :: two
#4 let rightWayRound = List.rev three

#5 let main() =
printfn "%A" one
printfn "%A" two
printfn "%A" three
printfn "%A" rightWayRound
#1 تعریف لیستی که دارای یک آیتم است.
#2 تعریف لیستی که دارای دو آیتم است(آیتم دوم لیست خود از نوع لیست است)
#3 تعریف لیستی که دارای سه آیتم است(ایتم دوم لیست خود از نوع لیستی است که دارای دو آیتم است)
# از تابع List.rev برای معکوس کردن آیتم‌های لیست three استفاده کردیم و مقادیر در لیستی به نام  rightWayRound قرار گرفت.
#5 تابع main برای چاپ اطلاعات لیست ها
بعد از اجرا خروجی زیر مشاهده می‌شود.
["one "]
["two "; "one "]
["three "; "two "; "one "]
["one "; "two "; "three "]
تفاوت بین لیست‌ها در #F و لیست و آرایه در دات نت(System.Collection.Generic)

 F#List
Net Array
 Net List
 #1 امکان تغییر در عناصر لیست
 NoYes
 Yes
 #2 امکان اضافه کردن عنصر جدید
 NoNo
 Yes
#3 جستجو
On
O1
O1
#1 در #F بعد از ساختن یک لیست امکان تغییر در مقادیر عناصر آن وجود ندارد.
#2 در #F بعد از ساختن یک لیست دیگه نمی‌تونید یک عنصر جدید به لیست اضافه کنید.
#3 جستجوی در لیست‌های #F به نسبت لیست‌ها و آرایه‌های در دات نت کند‌تر عمل می‌کند.

استفاده از عبارات در لیست ها
برای تعریف محدوده در لیست می‌تونیم به راحتی از روش زیر استفاده کنیم
let rangeList = [1..99]
برای ساخت لیست‌ها به صورت داینامیک استفاده از حلقه‌های تکرار در لیست مجاز است.
let dynamicList = [for x in 1..99 -> x*x]
کد بالا معادل کد زیر در #C  است.
for(int x=0;x<99 ; x++)
{
   myList.Add(x*x);
}
لیست‌ها و الگوی Matching
روش عادی برای کار با لیست‌ها در #F استفاده از الگوی Matching و توابع بازگشتی است.
let listOfList = [[2; 3; 5]; [7; 11; 13]; [17; 19; 23; 29]]

let rec concatList l =
match l with
| head :: tail -> head @ (concatList tail)
| [] -> []

let primes = concatList listOfList

printfn "%A" primes
در مثال بالا ابتدا یک لیست تعریف کردیم که دارای 3 آیتم است و هر آیتم آن خود یک لیست با سه آیتم است.(تمام آیتم‌ها از نوع داده عددی هستند). یک تابع بازگشتی برای پیمایش تمام آیتم‌های لیست نوشتم که در اون از الگوی Matching استفاده کردیم.
خروجی :
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29]
ماژول لیست
در جدول زیر تعدادی از توابع ماژول لیست رو مشاهده می‌کنید.

 نام تابع
 توضیحات
 List.length  تابعی که طول لیست را برمی گرداند
 List.head  تابعی برای برگشت عنصر اول لیست
 List.tail  تمام عناصر لیست را بر میگرداند به جز عنصر اول
 List.init  یک لیست با توجه به تعداد آیتم ایجاد می‌کند و یم تابع را بر روی تک تک عناصر لیست ایجاد می‌کند.
 List.append  یک لیست را به عنوان ورودی دریافت می‌کند و به لیست مورد نظر اضافه می‌کند و مجموع دو لیست را برگشت می‌دهد
 List.filter  فقط عناصری را برگشت می‌دهد که شرط  مورد نظر بر روی آن‌ها مقدار true را برگشت دهد
 List.map  یک تابع مورد نظر را بر روی تک تک عناصر لیست اجرا می‌کند و لیست جدید را برگشت می‌دهد
 List.iter  یک تابع مورد نظر را بر روی تک تک عناصر لیست اجرا می‌کند  
 List.zip 
 مقادیر دو لیست را با هم تجمیع می‌کند و لیست جدید را برگشت می‌دهد. اگر طول 2 لیست ورودی یکی نباشد خطا رخ خواهدداد 
 List.unzip درست برعکس تابع بالا عمل می‌کند
 List.toArray  لیست را تبدیل به آرایه می‌کند
 List.ofArray آرایه را تبدیل به لیست می‌کند
مثال هایی از توابع بالا
 List.head [5; 4; 3]

List.tail [5; 4; 3]

List.map (fun x -> x*x) [1; 2; 3]

List.filter (fun x -> x % 3 = 0) [2; 3; 5; 7; 9]

Sequence Collection

seq در #F یک توالی از عناصری است که هم نوع باشند. عموما از sequence‌ها زمانی استفاده میکنیم که یک مجموعه از داده‌ها با تعداد زیاد و مرتب شده داشته باشیم ولی نیاز به استفاده از تمام عناصر آن نیست. کارایی sequence  در مجموعه‌های با تعداد زیاد از list‌ها به مراتب بهتر است. sequence‌‌ها را با تابع  seq می‌شناسند که معادل IEnumerable در دات نت است. بنابر این هر مجمو عه ای که IEnumerable رو در دات نت پیاده سازی کرده باشد در #F با seq قابل استفاده است.

مثال هایی از نحوه استفاده seq

#1 seq بامحدوده 1 تا 100 و توالی 10 
seq { 0 .. 10 .. 100 }
#2 استفاده از حلقه‌های تکرار برای تعریف محدوده و توالی در seq
seq { for i in 1 .. 10 do yield i * i }
#3 استفاده از <- به جای yield
seq { for i in 1 .. 10 -> i * i }
#4 استفاده از حلقه for به همراه شرط برای فیلتر کردن
let isprime n =
    let rec check i =
        i > n/2 || (n % i <> 0 && check (i + 1))
    check 2

let aSequence = seq { for n in 1..100 do if isprime n then yield n }
چگونگی استفاده از توابع seq
در این بخش به ارائه مثال هایی کاربردی‌تر از چگونگی استفاده از seq در #F می‌پردازیم. برای شروع نحوه ساخت یک seq خالی یا empty رو خواهم گفت.
let seqEmpty = Seq.empty
روش ساخت یک seq که فقط یک عنصر را برگشت می‌دهد.
let seqOne = Seq.singleton 10
برای ساختن یک seq همانند لیست‌ها می‌تونیم از seq.init استفاده کنیم. عدد 5 که بلافاصله بعد از تابع seq.init آمده است نشان دهنده تعداد آیتم‌ها موجود در seq خواهد بود. seq.iter هم یک تابع مورد نظر رو بر روی تک تک عناصر seq اجرا خواهد کرد.(همانند list.iter)
let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10)
Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10
خروجی مثال بالا
0 10 20 30 40
با استفاده از توابع seq.ofArray , seq.ofList می‌تونیم seq مورد نظر خود را از لیست یا آرایه مورد نظر بسازیم.
let seqFromArray2 = [| 1 .. 10 |] |> Seq.ofArray 
البته این نکته رو هم یادآور بشم که به کمک عملیات تبدیل نوع(type casting) هم می‌تونیم آرایه رو به seq تبدیل کنیم. به صورت زیر
let seqFromArray1 = [| 1 .. 10 |] :> seq<int>
برای مشخص کردن اینکه آیا یک آیتم در seq موجود است یا نه می‌تونیم از seq.exists به صورت زیر استفاده کنیم.
let containsNumber number seq1 = Seq.exists (fun elem -> elem = number) seq1
let seq0to3 = seq {0 .. 3}
printfn "For sequence %A, contains zero is %b" seq0to3 (containsNumber 0 seq0to3)
اگر seq پاس داده شده به تابع exists خالی باشد یا یک ArgumentNullException متوقف خواهید شد.

برای جستجو و پیدا کردن یک آیتم در seq می‌تونیم از seq.find استفاده کنیم.
let isDivisibleBy number elem = elem % number = 0
let result = Seq.find (isDivisibleBy 5) [ 1 .. 100 ]
printfn "%d " result
دقت کنید که اگر هیچ آیتمی در sequence با  predicate مورد نظر پیدا نشود یک KeyNotFoundException رخ خواهد داد. در صورتی که مایل نباشید که استثنا رخ دهد می‌توانید از تابع seq.tryFind استفاده کنید. هم چنین خالی بودن sequence ورودی باعث ArgumentNullExceptionخواهد شد.

استفاده از lambda expression در توابع
lamdaExpressoion از توانایی‌ها مورد علاقه برنامه نویسان دات نت است و کمتر کسی است حاضر به استفاده از آن در کوئری‌های linq نباشد. در #F نیز می‌توانید از lambda Expression استفاده کنید. در ادامه به بررسی مثال هایی از این دست خواهیم پرداخت.

تابع skipWhile
همانند skipWhile در linq عمل می‌کند. یعنی یک predicate مورد نظر را بر روی تک تک عناصر یک لیست اجرا می‌کند و آیتم هایی که شرط برای آن‌ها true باشد نادیده گرفته میشوند و مابقی آیتم‌ها برگشت داده می‌شوند.
let mySeq = seq { for i in 1 .. 10 -> i*i }
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn "" 
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
می‌بینید که predicate مورد نظر برای تابع skipWhile به صورت lambda expression است که با رنگ متفاوت نمایش داده شده است.(استفاده از کلمه fun).
 خروجی به صورت زیر است:
16 25 36 49 64 81 100
 برای بازگرداندن یک تعداد مشخص از آیتم‌های seq می‌تونید از توابع seq.take یا seq.truncate استفاده کنید. ابتدا باید تعداد مورد نظر و بعد لیست مورد نظر را به عنوان پارامتر مقدار دهی کنید.
مثال:
let mySeq = seq { for i in 1 .. 10 -> i*i }
let truncatedSeq = Seq.truncate 5 mySeq
let takenSeq = Seq.take 5 mySeq

let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn "" 
#1 truncatedSeq |> printSeq #3 takenSeq |> printSeq
خروجی
1 4 9 16 25 //truncate
1 4 9 16 25 //take

Tuples
tuples در #F به گروهی از مقادیر بی نام ولی مرتب شده که می‌توانند انواع متفاوت هم داشته باشند گفته می‌شود. ساختار کلی آن به صورت ( element , ... , element )  است که هر element خود می‌تواند یک عبارت نیز باشد.(مشابه کلاس Tuple در #C که به صورت generic استفاده می‌کنیم)
// Tuple of two integers.
( 1, 2 ) 

// Triple of strings.
( "one", "two", "three" ) 

// Tuple of unknown types.
( a, b ) 

// Tuple that has mixed types.
( "one", 1, 2.0 ) 

// Tuple of integer expressions.
( a + 1, b + 1)
نکات استفاده از tuple
#1 می‌تونیم از الگوی Matching برای دسترسی به عناصر tuple استفاده کنیم.
let print tuple1 =
   match tuple1 with
    | (a, b) -> printfn "Pair %A %A" a b
#2 میتونیم از let  برای تعربف الگوی tuple استفاده کنیم.
let (a, b) = (1, 2)
#3 توابع fst و snd مقادیر اول و دوم هر tuple رو بازگشت می‌دهند
let c = fst (1, 2) // return 1
let d = snd (1, 2)// return 2
#4 تابعی برای بازگشت عنصر سوم یک tuple وجود ندارد ولی این تابع رو با هم می‌نویسیم:
let third (_, _, c) = c
کاربرد tuple در کجاست
زمانی که یک تابع باید بیش از یک مقدار را بازگشت دهد از tuple‌ها استفاده می‌کنیم. برای مثال
let divRem a b = 
   let x = a / b
   let y = a % b
   (x, y)
خروجی تابع divRem از نوع tuple که دارای 2 مقدار است می‌باشد.
نظرات اشتراک‌ها
تبدیلگر ایران سیستم به یونیکد
در متد get_Unicode_To_IranSystem  نوع لیست اشتباه است، من به جای آن List<byte> نوشتم. این لیست رو چطور میشه به string تبدیل کرد؟
نظرات مطالب
افزودن ASP.NET Identity به یک پروژه Web Forms
سلام.
من اگر بخوام لیست کاربرایی که یک نقش خاص مثلا "Admin" رو دارن لیست کنم چطور میتونم دسترسی داشته باشم؟
مطالب
کار با اسناد در RavenDb 4، ثبت و ویرایش
اگر تا بحال با بانک‌های NoSql کار کرده و لذت برده‌اید، به شما پیشنهاد میکنم حتما RavenDb را هم امتحان کنید، تا لذت استفاده از NoSql را چندین برابر حس کنید! RavenDb یک بانک اطلاعاتی NoSql از نوع DocumentStore است که به‌صورت متن باز توسعه داده می‌شود و مخزن کد آن در Github موجود است. از ویژگی‌های بارز RavenDb نسبت به سایر DocumentStoreها، Transactional بودن میباشد و در نسخه‌ی 4 بصورت کامل از Net Core. پشتیبانی میکند. برای آشنایی بیشتر با NoSql میتوانید از مقالات موجود در گروه NoSql استفاده کنید و برای آشنایی با RavenDb از دوره ای که در سایت وجود دارد استفاده نمایید(دوره مربوط به نسخه‌ی 3.5 می‌باشد).
از بارز‌ترین ویژگی‌های NoSqlها توانایی آن‌ها در ذخیره‌ی اطلاعات، بدون توجه به اسکیمای آن هاست؛ پس هر نوع مدلی که ما برای ذخیره اطلاعات نرم افزار تعریف میکنیم، فقط برای درک بهتر ما هست و بس!

با این مقدمه مدل‌های زیر را برای شروع کار داریم:
Public Class User
{
        public string Id { get; set; }
        public string PhoneNumber { get; set; }
        public Dictionary<string, App> Apps { get; set; }
}
public class App
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public List<string> Roles { get; set; }
        public List<String> Messages { get; set; }
        public String AdressId { get; set; }
        public bool IsActive { get; set; } = true;
        [JsonIgnore]
        public string DisplayName => $"{FirstName} {LastName}";
}

در این مدل، هر کاربر با یک شماره تماس میتواند در چندین برنامه ثبت شود و اطلاعات او در هر برنامه هم میتواند متفاوت باشد.
برای اتصال به RavenDb، به DocumentStore و برای ارسال درخواست‌ها به سمت سرور، به DocumentSession نیاز داریم. نمونه سازی DocumentStore هزینه‌بر بوده و باید در طی اجرای نرم افزار فقط یکبار(Singleton) نمونه سازی شود. DocumentSession بسیار سبک بوده و باید به ازای هر درخواست که به سمت سرور RavenDb ارسال میگردد یک بار نمونه سازی شده و بعد از آن نابود شود. پس برای استفاده در ASP.NET Core به این پیاده سازی در Startup میرسیم:
services.AddSingleton<IDocumentStore>(serviceProvider =>
{
      var store = new DocumentStore()
      {
            Urls = new[] { "http://192.168.1.10:8080" },
            Database = "AccountingSystem",
      }.Initialize();
      return store;
});

services.AddScoped<IAsyncDocumentSession>(serviceProvider =>
{
      var store = serviceProvider.GetRequiredService<IDocumentStore>();
      return store.OpenAsyncSession();
});

حال در تمام بخش‌های نرم افزار می‌توانیم DocumentSession استفاده کنیم.
برای ذخیره سازی مدل در RavenDb از کد زیر استفاده می‌کنیم:
var user = new User
{
      PhoneNumber = user.PhoneNumber
};
user.Apps.Add(appCode, new ActiveApp
{
       FirstName = "عبدالصالح",
       LastName = "کاشانی",
       UserName = abdossaleh,
       IsActive = true,
       RolesId = new List<string>{"Admin"}
});
await _documentSession.StoreAsync(user);
await _documentSession.SaveChangesAsync()

این ساده‌ترین کاری هست که میتوانیم انجام دهیم. بلافاصله بعد از استفاده از متد StoreAsync و بدون رفت و برگشتی به سرور، ویژگی Id برای user مقداردهی می‌شود و توضیح این رفتار هم پیشتر گفته شده است. با فراخوانی متد SaveChangesAsync تغییرات اتفاق افتاده در DocumentSession برای ذخیره سازی به سمت سرور ارسال می‌شوند. بله! الگوی Repository و UnitOfWork.
حال برای دریافت همین مدل، در صورتیکه Id آن را در اختیار داشته باشیم، از متد LoadAsync استفاده میکنیم.
var user = await _documentSession.LoadAsync<User>("Users/131-A");
با لود شدن کاربر، این Entity تحت نظر قرار میگیرد و اگر تغییری در هر کدام از ویژگی‌های آن صورت گیرد و متد SaveChangesAsync فراخوانی شود، کل مدل برای به‌روزرسانی به سمت سرور ارسال میشود. کل مدل و این به معنای بار اضافی در شبکه هست. البته در مدل‌های کوچک بهتر است که همین کار را انجام دهیم. ولی در اینجا به عمد مدلی را انتخاب کرده‌ایم که اطلاعات زیادی را در خود نگهداری میکند و ارسال تمام آن به ازای یک تغییر کوچک به صرفه نیست! خوشبختانه RavenDb برای حل این مشکل امکانات جالبی را در اختیار ما قرار داده که در ادامه آن‌ها را بررسی می‌کنیم.

Patching
به معنای تغییر دادن قسمتی از سند که شامل تغییر مقادیر، اضافه یا حذف یک ویژگی، ایجاد تغییرات در لیست و ... می‌باشد. با استفاده از متدهای Patch سند، میتوانیم بدون نیاز به لود سند و تغییر و ذخیره آن، قسمتی از سند را ویرایش کنیم. عملیات Patch، سمت سرور اجرا می‌شوند. برای مثال برای تغییر شماره تماس، از متد زیر استفاده می‌کنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.PhoneNumber
      , "09131110000");
که مدلی را که میخواهیم تغییر دهیم، به همراه نوع ویژگی مورد نظر برای تغییر، دریافت میکند و بعد از آن، به ترتیب Id سند مورد نظر، ویژگی مورد نظر برای اعمال تغییر و مقدار را میگیرد و با فراخوانی SaveChangesAsync این تغییرات اعمال می‌شوند. نکته‌ای که باید توجه کنید این است که اگر مدلی را لود کردید و در فیلدهای آن تغییری ایجاد نموده‌اید، دیگر نمیتوانید از Patch یا Defer (توضیح داده میشود) استفاده کنید. به عبارت دیگر در هر درخواست یا باید از سیستم Tracking خود RavenDb استفاده کنید و یا از Patching!
برای اضافه کردن یک آیتم به لیست،  از Patch بصورت زیر استفاده میکنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.Apps["59"].RolesId
      , r => r.Add("Admin"));

برای اضافه کردن مقداری به یک مقدار عددی در RavenDb، از متد Increment بصورت زیر استفاده میکنیم:
 _documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
متد Patch برای کارهای ساده‌ی اینچنین بسیار کاربردی می‌باشد؛ ولی برای کارهای پیشرفته‌تر کارآیی ندارد. به همین دلیل متد Defer در کنار آن معرفی شده‌است که فوق العاده کاربردی ولی اصطلاحا non-typed است و تحت نظارت Compiler نیست. برای مثال اضافه کردن یک مقدار به Dictionary ما، از طریق Patch امکان ندارد. اما اینکار با استفاده از متد Defer و کدهای JavaScript به‌سادگی زیر می‌باشد:
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null,
                              new PatchRequest()
                              {
                                    Script = $@"this.Apps[args.appCode] = args.app",
                                    Values =
                                         {
                                              {"appCode", appCode},
                                              {"app", new ActiveApp
                                                   {
                                                        FirstName = "عبدالصالح",
                                                        LastName = "کاشانی",
                                                        UserName = abdossaleh,
                                                        RolesId = new List<string>{"Admin"}
                                                    }
                                              }
                                          }
                              }, null));
متد Defer شناسه‌ی سند مورد نظر را گرفته و اسکریپت ما را با آرگومان‌های ارسالی، بر روی سند اعمال میکند. Defer دسترسی کاملی را به ما برای تغییر در سند میدهد. برای نمونه میتوانیم آیتمی را به مکان خاصی از لیست اضافه کنیم (برای کوتاه‌تر شدن اسکریپت‌ها فقط بخش Script و Value را ذکر میکنم):
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)",
Values =
        {
            {
                "index": 1 // مکانی که میخواهیم عملیات انجام شود
                "app", 59
                "role", "User"
            }
        }
this در اینجا به سند جاری اشاره میکند.
از همین روش میتوانیم برای ویرایش کردن یک آیتم هم استفاده کنیم. برای مثال اگر مقدار 0 را در متد splice به یک تغییر دهیم، عملیات ویرایش صورت می‌گیرد (در واقع حذف آیتم در مکان index و درج آیتم جدید در همان مکان):
splice(args.index,1,args.role)
و برای حذف تمام آیتم‌های لیست جز یک آیتم خاص، از کد زیر استفاده میکنیم:
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);",
        Values =
        {
            {"app", 59}
            {"role", "User"}
        }
همانطور که مشاهده می‌کنید به راحتی می‌توانیم کدهای جاوا اسکریپتی خود را در Defer استفاده کنیم. اما این قدرت زیاد، امکان اشتباه در کدهای ما را زیاد میکند چرا که تحت کنترل کامپایلر نیست.
مطالب
آموزش فایرباگ - #9 - Script Panel - Side Panels
سه پنل Watch، Stack و Breakpoints پنل‌های جانبی پنل Script هستند که امکانات و اطلاعات مفیدی در ارتباط با قطعه کدی که در حال دیباگ آن هستیم ارائه می‌کنند.


Watch
این پنل متغییرها و تغییرات مقادیر آن‌ها در هنگام دیباگ را نمایش می‌دهد. بصورت پیشفرض متغییرهایی که در بلاک فعلی (که دیباگر در آن توقف کرده) ایجاد شده اند در این لیست قرار دارند. اما می‌توانید متغییرها و عبارات مورد نظر را در این قسمت وارد کنید. برای مثال می‌توانید مساوی بودن یک متغییر با یک مقدار را در این لیست وارد کنید و نتیجه را در هر اجرا مشاهده کنید. یا حتی یک متد را در این لیست وارد کنید تا در هر اجرا ، اجرا شود و نتیجه نمایش داده شود.
برای اضافه کردن یک عبارت جدید به این لیست، بروی عبارت New watch expression... در بالای پنل کلیک کرده و عبارت مورد نظر را وارد کنید و در نهایت کلید Enter را بزنید.
برای حذف عبارتی که وارد کردید، بروی علامت X کلیک کنید.
توجه کنید که هر عبارتی که در این قسمت وارد می‌کنید در اجرای هر خط از برنامه در حالت دیباگ اجرا می‌شود و اگر عبارتی که وارد کرده اید مقادیر داخل برنامه را تغییر دهند، برنامه‌ی شما متفاوت از حالت عادی عمل خواهد کرد.


Options Menu و Context Menu در این پنل همان موارد پنل DOM هستند و از آنجایی که همه‌ی این آیتم‌ها در این پنل به درستی عمل نمی‌کنند، توضیح آن را در مقاله‌ی مربوط به پنل DOM ارائه می‌کنیم.


Stack

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




Breakpoints
در این پنل لیست Break Point‌های اضافه شده نمایش داده می‌شود و می‌توانید آن هارا فعال، غیرفعال یا حذف نمایید.
برای فعال، غیرفعال و حذف کردن یک یا همه‌ی Break Point‌ها بروی قسمت از پنل راست کلیک کرده و یکی از گزینه‌های مورد نظر را انتخاب کنید.