مطالب
بررسی تغییرات Blazor 8x - قسمت دوم - بررسی حالت رندر سمت سرور
در قسمت قبل، حالت‌های مختلف رندر کامپوننت‌ها را در Blazor 8x معرفی کردیم. در این قسمت می‌خواهیم نحوه‌ی کارکرد دو حالت InteractiveServer و StreamRendering را به همراه چند مثال بررسی کنیم.


معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8

پس از نصب SDK دات نت 8، دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست! در اینجا در ابتدا باید مشخص کرد که سطح تعاملی برنامه در چه حدی است. در ادامه 4 روش شروع پروژه‌های Blazor 8x را مشاهده می‌کنید که توسط پرچم interactivity--، نوع رندر برنامه در آن‌ها مشخص شده‌است:

اجرای قسمت‌های تعاملی برنامه بر روی سرور:
dotnet new blazor --interactivity Server

اجرای قسمت‌های تعاملی برنامه در مرورگر، توسط فناوری وب‌اسمبلی:
dotnet new blazor --interactivity WebAssembly

برای اجرای قسمت‌های تعاملی برنامه، ابتدا حالت Server فعالسازی می‌شود تا فایل‌های WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده می‌کند:
dotnet new blazor --interactivity Auto

فقط از حالت SSR یا همان static server rendering استفاده می‌شود (این نوع برنامه‌ها تعاملی نیستند):
dotnet new blazor --interactivity None

سایر گزینه‌ها را با اجرای دستور dotnet new blazor --help می‌توانید مشاهده کنید.


نکته‌ی مهم! در قالب‌های آماده‌ی Blazor 8x، حالت SSR، پیش‌فرض است.

هرچند در تمام پروژه‌های فوق، انتخاب حالت‌های مختلف رندر را مشاهده می‌کنید، اما این انتخاب‌ها صرفا دو مقصود مهم را دنبال می‌کنند:
الف) تنظیم فایل Program.cs برنامه جهت افزودن وابستگی‌های مورد نیاز، به صورت خودکار.
ب) ایجاد پروژه‌ی کلاینت (علاوه بر پروژه‌ی سرور)، در صورت نیاز. برای مثال حالت‌های وب‌اسمبلی و Auto، هر دو به همراه یک پروژه‌ی کلاینت وب‌اسمبلی هم هستند؛ اما حالت‌های Server و None، خیر.

در تمام این پروژه‌ها هر صفحه و یا کامپوننتی که ایجاد می‌شود، به صورت پیش‌فرض بر اساس SSR رندر و نمایش داده خواهد شد؛ مگر اینکه به صورت صریحی این نحوه‌ی رندر را بازنویسی کنیم. برای مثال مشخص کنیم که قرار است بر اساس Blazor Server اجرا شود و یا وب‌اسمبلی و یا حالت Auto.

اما ... اگر علاقمند بودیم تا به حالت‌های پیش‌از دات نت 8 رجوع کنیم و تمام برنامه را به صورت یک‌دست تعاملی کنیم و حالت SSR پیش‌فرض نباشد، می‌توان از پرچم all-interactive-- که به انتهای دستورات فوق قابل افزوده شدن است، کمک گرفت. این پرچم، فایل App.Razor را جهت تنظیم سراسری حالت‌های رندر، ویرایش می‌کند. این مورد را در ادامه‌ی این مطلب، در قسمت «روشی ساده برای تعاملی کردن کل برنامه» بیشتر بررسی می‌کنیم.


بررسی حالت Server side rendering

برای بررسی این حالت یک پوشه‌ی جدید را ایجاد کرده و توسط خط فرمان، دستور dotnet new blazor --interactivity Server را در ریشه‌ی آن اجرا می‌کنیم. پس از ایجاد ساختار ابتدایی پروژه بر اساس این قالب انتخابی، فایل Program.cs جدید آن، چنین شکلی را دارد:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents().AddInteractiveServerComponents();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

app.Run();
مهم‌ترین قسمت‌های آن، متدهای AddInteractiveServerComponents و AddInteractiveServerRenderMode هستند که server-side rendering را به همراه امکان داشتن کامپوننت‌های تعاملی، میسر می‌کنند.
server-side rendering به این معنا است که برنامه‌ی سمت سرور، کل DOM و HTML نهایی را تولید کرده و به مرورگر کاربر ارائه می‌کند. مرورگر هم این DOM را نمایش می‌دهد. فقط همین! در اینجا هیچ خبری از اتصال دائم SignalR نیست و محتوای ارائه شده، یک محتوای استاتیک است. این حالت رندر، برای ارائه‌ی محتواهای فقط خواندنی غیرتعاملی، فوق العاده‌است؛ امکان از لحظه‌ای که نیاز به کلیک بر روی دکمه‌ای باشد، دیگر پاسخگو نیست. به همین جهت در اینجا امکان تعاملی کردن تعدادی از کامپوننت‌های ویژه و مدنظر نیز پیش‌بینی شده‌اند تا بتوان به ترکیبی از server-side rendering و client-side rendering رسید.
حالت پیش‌فرض در اینجا، ارائه‌ی محتوای استاتیک است. بنابراین هر کامپوننتی در اینجا ابتدا بر روی سرور رندر شده (HTML ابتدایی آن آماده شده) و به سمت مرورگر کاربر ارسال می‌شود. اگر کامپوننتی نیاز به امکانات تعاملی داشت باید آن‌را دقیقا توسط ویژگی InteractiveXYZ مشخص کند؛ مانند مثال زیر:
@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
همانطور که مشاهده می‌کنید، این کامپوننت از روش رندر InteractiveServer استفاده می‌کند. برای درک نحوه‌ی کارکرد آن، همین سطر را حذف، یا کامنت کنید و سپس برنامه را اجرا کنید. در حین مشاهده‌ی این صفحه، همه چیز به خوبی نمایش داده می‌شود و حتی دکمه‌ی Click me هم مشخص است. اما ... با کلیک بر روی آن اتفاقی رخ نمی‌دهد! علت اینجا است که اکنون این صفحه، یک صفحه‌ی کاملا استاتیک است و دیگر تعاملی نیست.
در ادامه، مجددا سطر کامنت شده را به حالت عادی برگردانید و سپس برنامه را اجرا کنید. پیش از باز کردن صفحه‌ی Counter، ابتدا developer tools مرورگر خود را گشوده و برگه‌ی network آن‌را انتخاب و سپس صفحه‌ی Counter را باز کنید. در این لحظه‌است که مشاهده می‌کنید یک اتصال وب‌سوکت برقرار شد. این اتصال است که قابلیت‌های تعاملی صفحه را برقرار کرده و مدیریت می‌کند (این اتصال دائم SignalR است که این صفحه را همانند برنامه‌های Blazor Web Server پیشین مدیریت می‌کند).

یک نکته: در برنامه‌های Blazor Server سنتی، امکان فعالسازی قابلیتی به نام prerender نیز وجود دارد. یعنی سرور، ابتدا صفحه را رندر کرده و محتوای استاتیک آن‌را به سمت مرورگر کاربر ارسال می‌کند و سپس اتصال SignalR برقرار می‌شود. در دات نت 8، این حالت، حالت پیش‌فرض است. اگر آن‌را نمی‌خواهید باید به نحو زیر غیرفعالش کنید:
@rendermode InteractiveServerRenderModeWithoutPrerendering

@code{
  static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = 
        new InteractiveServerRenderMode(false);
}


روشی ساده برای تعاملی کردن کل برنامه

اگر می‌خواهید رفتار برنامه را همانند Blazor Server سابق کنید و نمی‌خواهید به ازای هر کامپوننت، نحوه‌ی رندر آن‌را به صورت سفارشی انتخاب کنید، فقط کافی است فایل App.razor را باز کرده:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="MyApp.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>
و قسمت‌های HeadOutlet و مسیریابی آن‌را به صورت زیر تغییر دهید تا به کل برنامه اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" />
...
<Routes @rendermode="@InteractiveServer" />
این مورد دقیقا همان کاری است که پرچم all-interactive-- در هنگام ایجاد پروژه‌های جدید Blazor 8x به کمک NET CLI.، انجام می‌دهد. یک چنین گزینه‌ای در ویژوال استودیو نیز هنگام ایجاد پروژه‌ها‌ی جدید Blazor وجود دارد و به شما این امکان را می‌دهد که بین حالت‌های تعاملی Per page/component و Global، یکی را انتخاب کنید. در حین استفاده‌ی از CLI، نیازی به ذکر حالت تعاملی Per page/component نیست؛ چون حالت پیش‌فرض، یا همان SSR است. حالت Global هم فقط فایل App.Razor را به صورت فوق، ویرایش و تنظیم می‌کند.


در این حالت دیگر نیازی نیست تا به ازای هر کامپوننت و صفحه، نحوه‌ی رندر را مشخص کنیم؛ چون این نحوه، از بالاترین سطح، به تمام زیرکامپوننت‌ها به ارث می‌رسد (درباره‌ی این نکته در قسمت قبل، توضیحاتی ارائه شد).


بررسی حالت Streaming Rendering

در اینجا مثال پیش‌فرض Weather.razor قالب پیش‌فرض مورد استفاده‌ی جاری را کمی تغییر داده‌ایم که کدهای نهایی آن به صورت زیر است (2 قسمت forecasts.AddRange_ را اضافه‌تر دارد):

@page "/weather"
@attribute [StreamRendering(prerender: true)]

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (_forecasts == null)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    <table class="table">
        <thead>
        <tr>
            <th>Date</th>
            <th>Temp. (C)</th>
            <th>Temp. (F)</th>
            <th>Summary</th>
        </tr>
        </thead>
        <tbody>
        @foreach (var forecast in _forecasts)
        {
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
        </tbody>
    </table>
}

@code {
    private List<WeatherForecast>? _forecasts;

    protected override async Task OnInitializedAsync()
    {
    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(500);

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[]
                        {
                            "Freezing",
                            "Bracing",
                            "Chilly",
                            "Cool",
                            "Mild",
                            "Warm",
                            "Balmy",
                            "Hot",
                            "Sweltering",
                            "Scorching",
                        };
        _forecasts = GetWeatherForecasts(startDate, summaries).ToList();
        StateHasChanged();

    // Simulate asynchronous loading to demonstrate streaming rendering
        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
        StateHasChanged();

        await Task.Delay(1000);
        _forecasts.AddRange(GetWeatherForecasts(startDate, summaries));
    }

    private static IEnumerable<WeatherForecast> GetWeatherForecasts(DateOnly startDate, string[] summaries)
    {
        return Enumerable.Range(1, 5)
                         .Select(index => new WeatherForecast
                                          {
                                              Date = startDate.AddDays(index),
                                              TemperatureC = Random.Shared.Next(-20, 55),
                                              Summary = summaries[Random.Shared.Next(summaries.Length)],
                                          });
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }

}
برای بررسی این مثال، ابتدا سطر ویژگی StreamRendering را حذف و یا کامنت کرده و سپس برنامه را اجرا کنید. پس از اجرای برنامه، با انتخاب مشاهده‌ی صفحه‌ی Weather، هیچگاه قسمت loading که در حالت forecasts == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در این حالت امکانات تعاملی Blazor server وجود خارجی ندارند.
در ادامه مجددا سطر ویژگی StreamRendering را به حالت قبلی برگردانید و برنامه را اجرا کنید. در این حالت ابتدا قسمت loading ظاهر می‌شود و سپس در طی چند مرحله با توجه به Task.Delay‌های قرار داده شده، صفحه رندر شده و تکمیل می‌شود.
اتفاقی که در اینجا رخ می‌دهد، استفاده از فناوری  HTML Streaming است که مختص به مایکروسافت هم نیست. در حالت Streaming، هربار قطعه‌ای از HTML ای که قرار است به کاربر ارائه شود، به صورت جریانی به سمت مرورگر ارسال می‌شود و مرورگر این قطعه‌ی جدید را بلافاصله نمایش می‌دهد. نکته‌ی جالب این روش، عدم نیاز به اتصال SignalR و یا اجرای WASM درون مرورگر است.

Streaming rendering حالت بینابین رندر کامل در سمت سرور و رندر کامل در سمت کلاینت است. در حالت رندر سمت سرور، کل HTML صفحه ابتدا توسط سرور تهیه و بازگشت داده می‌شود و کاربر باید تا پایان عملیات تهیه‌ی این HTML نهایی، منتظر باقی بماند و در این بین چیزی را مشاهده نخواهد کرد. در حالت Streaming rendering، هنوز هم همان حالت تهیه‌ی HTML استاتیک سمت سرور برقرار است؛ به همراه تعدادی محل جایگذاری اطلاعات جدید. به محض پایان یک عمل async سمت سرور که سه نمونه‌ی آن را در مثال فوق مشاهده می‌کنید، برنامه، جریان قطعه‌ای از اطلاعات استاتیک را به سمت مرورگر کاربر ارسال می‌کند تا در مکان‌هایی از پیش تعیین شده، درج شوند.
در حالت SSR، فقط یکبار شانس ارسال کل اطلاعات به سمت مرورگر کاربر وجود دارد؛ اما در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد. در این حالت نمایش گزارشی از اطلاعاتی که ممکن است با تاخیر در سمت سرور تهیه شوند، ساده‌تر می‌شود. یعنی می‌توان هربار قسمتی را که تهیه شده، برای نمایش بازگشت داد و کاربر تا مدت زیادی منتظر نمایش کل صفحه باقی نخواهد ماند.


روش نهایی معرفی نحوه‌ی رندر صفحات

بجای استفاده از ویژگی‌های RenderModeXyz جهت معرفی نحوه‌ی رندر کامپوننت‌ها و صفحات (که تا پیش از نگارش RTM معرفی شده بودند و چندبار هم تغییر کردند)، می‌توان از دایرکتیو جدیدی به نام rendermode@ با سه مقدار InteractiveServer، InteractiveWebAssembly و InteractiveAuto استفاده کرد. برای سهولت تعریف این موارد باید سطر ذیل را به فایل Imports.razor_ اضافه نمود:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
در این حالت تعریف قدیمی سطر ذیل:
@attribute [RenderModeInteractiveServer]
به صورت زیر:
@rendermode InteractiveServer
تبدیل می‌شود.

اگر هم قصد سفارشی سازی آن‌ها را دارید، برای مثال می‌خواهید prerender را در آن‌ها false کنید، روش کار به صورت زیر است:
@rendermode renderMode

@code {
    static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}
مطالب
به روز رسانی ساده‌تر اجزاء ارتباطات در EF Code first به کمک GraphDiff
دو نوع حالت کلی کارکردن با EF وجود دارند: متصل و منقطع.
در حالت متصل مانند برنامه‌های متداول دسکتاپ، Context مورد استفاده در طول عمر صفحه‌ی جاری زنده نگه داشته می‌شود. در این حالت اگر شیءایی اضافه شود، حذف شود یا تغییر کند، توسط EF ردیابی شده و تنها با فراخوانی متد SaveChanges، تمام این تغییرات به صورت یکجا به بانک اطلاعاتی اعمال می‌شوند.
در حالت غیرمتصل مانند برنامه‌های وب، طول عمر Context در حد طول عمر یک درخواست است. پس از آن از بین خواهد رفت و دیگر فرصت ردیابی تغییرات سمت کاربر را نخواهد یافت. در این حالت به روز رسانی کلیه تغییرات انجام شده در خواص و همچنین ارتباطات اشیاء موجود، کاری مشکل و زمانبر خواهد بود.
برای حل این مشکل، کتابخانه‌ای به نام GraphDiff طراحی شده‌است که صرفا با فراخوانی متد UpdateGraph آن، به صورت خودکار، محاسبات تغییرات صورت گرفته در اشیاء منقطع و اعمال آن‌ها به بانک اطلاعاتی صورت خواهد گرفت. البته ذکر متد SaveChanges پس از آن نباید فراموش شود.


اصطلاحات بکار رفته در GraphDiff

برای کار با GraphDiff نیاز است با یک سری اصطلاح آشنا بود:

Aggregate root
گرافی است از اشیاء به هم وابسته که مرجع تغییرات داده‌ها به شمار می‌رود. برای مثال یک سفارش و آیتم‌های آن‌را درنظر بگیرید. بارگذاری آیتم‌های سفارش، بدون سفارش معنایی ندارند. بنابراین در اینجا سفارش aggregate root است.

AssociatedCollection/AssociatedEntity
حالت‌های Associated به GraphDiff اعلام می‌کنند که اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root نباید به روز رسانی شوند. در این حالت تنها ارجاعات به روز رسانی خواهند شد.
اگر خاصیت راهبری از نوع ICollection است، حالت AssociatedCollection و اگر صرفا یک شیء ساده است، از AssociatedEntity استفاده خواهد شد.

OwnedCollection/OwnedEntity
حالت‌های Owned به GraphDiff اعلام می‌کنند که جزئیات و همچنین ارجاعات اینگونه خواص راهبری تعریف شده، در حین به روز رسانی aggregate root باید به روز رسانی شوند.


دریافت و نصب GraphDiff

برای نصب خودکار کتابخانه‌ی GraphDiff می‌توان از دستور نیوگت ذیل استفاده کرد:
 PM> Install-Package RefactorThis.GraphDiff


بررسی GraphDiff در طی یک مثال

مدل‌های برنامه آزمایشی، از سه کلاس ذیل که روابط many-to-many و one-to-many با یکدیگر دارند، تشکیل شده‌است:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace GraphDiffTests.Models
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }

        public BlogPost()
        {
            Tags = new List<Tag>();
        }
    }

    public class Tag
    {
        public int Id { set; get; }

        [StringLength(maximumLength: 450), Required]
        public string Name { set; get; }

        public virtual ICollection<BlogPost> BlogPosts { set; get; } // many-to-many

        public Tag()
        {
            BlogPosts = new List<BlogPost>();
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; } // one-to-many
    }
}
- یک مطلب می‌تواند چندین برچسب داشته باشد و هر برچسب می‌تواند به چندین مطلب انتساب داده شود.
- هر کاربر می‌تواند چندین مطلب ارسال کند.

در این حالت، Context برنامه چنین شکلی را خواهد یافت:
using System;
using System.Data.Entity;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<BlogPost> BlogPosts { get; set; }
        public DbSet<Tag> Tags { get; set; }


        public MyContext()
            : base("Connection1")
        {
            this.Database.Log = sql => Console.Write(sql);
        }
    }
}
به همراه تنظیمات به روز رسانی ساختار بانک اطلاعاتی به صورت خودکار:
using System.Data.Entity.Migrations;
using System.Linq;
using GraphDiffTests.Models;

namespace GraphDiffTests.Config
{
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            if(context.Users.Any())
                return;

            var user1 = new User {Name = "User 1"};
            context.Users.Add(user1);

            var tag1 = new Tag { Name = "Tag1" };
            context.Tags.Add(tag1);

            var post1 = new BlogPost { Title = "Title...1", Content = "Content...1", User = user1};
            context.BlogPosts.Add(post1);

            post1.Tags.Add(tag1);

            base.Seed(context);
        }
    }
}
در متد Seed آن یک سری اطلاعات ابتدایی ثبت شده‌اند؛ یک کاربر، یک برچسب و یک مطلب.




در این تصاویر به Id هر کدام از رکوردها دقت کنید. از آن‌ها در ادامه استفاده خواهیم کرد.
در اینجا نمونه‌ای از نحوه‌ی استفاده از GraphDiff را جهت به روز رسانی یک Aggregate root ملاحظه می‌کنید:
            using (var context = new MyContext())
            {
                var user1 = new User { Id = 1, Name = "User 1_1_1" };
                var post1 = new BlogPost { Id = 1, Title = "Title...1_1", Content = "Body...1_1",
                    User = user1, UserId = user1.Id };
                var tags = new List<Tag>
                {
                    new Tag {Id = 1, Name = "Tag1_1"},
                    new Tag {Id=12, Name = "Tag2_1"},
                    new Tag {Name = "Tag3"},
                    new Tag {Name = "Tag4"},
                };
                tags.ForEach(tag => post1.Tags.Add(tag));

                context.UpdateGraph(post1, map => map
                    .OwnedEntity(p => p.User)
                    .OwnedCollection(p => p.Tags)
                    );

                context.SaveChanges();
            }
پارامتر اول UpdateGraph، گرافی از اشیاء است که قرار است به روز رسانی شوند.
پارامتر دوم آن، همان مباحث Owned و Associated بحث شده در ابتدای مطلب را مشخص می‌کنند. در اینجا چون می‌خواهیم هم برچسب‌ها و هم اطلاعات کاربر مطلب اول به روز شوند، نوع رابطه را Owned تعریف کرده‌ایم.
در حین کار با متد UpdateGraph، ذکر Idهای اشیاء منقطع از Context بسیار مهم هستند. اگر دستورات فوق را اجرا کنیم به خروجی ذیل خواهیم رسید:




- همانطور که مشخص است، چون id کاربر ذکر شده و همچنین این Id در post1 نیز درج گردیده است، صرفا نام او ویرایش گردیده است. اگر یکی از موارد ذکر شده رعایت نشوند، ابتدا کاربر جدیدی ثبت شده و سپس رابطه‌ی مطلب و کاربر به روز رسانی خواهد شد (userId آن به userId آخرین کاربر ثبت شده تنظیم می‌شود).
- در حین ثبت برچسب‌ها، چون Id=1 از پیش در بانک اطلاعاتی موجود بوده، تنها نام آن ویرایش شده‌است. در سایر موارد، برچسب‌های تعریف شده صرفا اضافه شده‌اند (چون Id مشخصی ندارند یا Id=12 در بانک اطلاعاتی وجود خارجی ندارد).
- چون Id مطلب مشخص شده‌است، فیلدهای عنوان و محتوای آن نیز به صورت خودکار ویرایش شده‌اند.

و ... تمام این کارها صرفا با فراخوانی متدهای UpdateGraph و سپس SaveChanges رخ داده‌است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
GraphDiffTests.zip
نظرات مطالب
پردازش داده‌های جغرافیایی به کمک SQL Server و Entity framework
این‌ها بیشتر مسایل نمایشی است و توانمندی ابزار نمایش دهنده‌ی اطلاعات نقشه. نیازی نیست در اصل دیتابیس و اطلاعات، تغییری حاصل شود؛ چون اندازه‌ی نمایشی حتی اگر 10 برابر هم شود، در فاصله‌ی بین تهران و شیراز نهایتا تغییری حاصل نخواهد شد و طول و عرض جغرافیایی مکان‌ها ثابت خواهند ماند. برای نمونه اگر مثال پیوست شده را اجرا کنید، خود management studio امکان تغییر اندازه‌ی نمایشی را دارد:


در برنامه‌های دات نت هم برای مثال از SharpMap می‌شود برای نمایش این نوع اطلاعات به همراه تغییر اندازه و ابعاد خودکار نقشه استفاده کرد. برای برنامه‌های وب هم jVectorMap چنین قابلیت‌هایی را دارد.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 1 - NET Core. چیست؟
NET Core. چیست؟

برای اغلب توسعه دهنده‌های دات نت (برنامه‌های وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شده‌است که در تصویر ذیل قابل مشاهده‌اند:


مهم‌ترین قسمت‌های این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام می‌دهد، BCL که کلاس‌های پایه‌ای را جهت کار با  IO، Text و غیره، فراهم می‌کنند، هستند؛ به علاوه کتابخانه‌هایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار می‌کنند.
هرچند تعدادی از توسعه دهنده‌های دات نت تنها با Full framework کار می‌کنند، اما در طی سال‌های اخیر انشعابات بسیار دیگری از آن به وجود آمده‌اند؛ مانند دات نت‌های ویژه‌ی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعه‌ی برنامه‌های iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورک‌ها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرم‌های دیگر قابل انتقال نیستند.

با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرم‌های یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:


هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرم‌های مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائه‌ی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانه‌ی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرم‌های مختلف هستید.

برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی می‌کند).


هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرم‌هایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده می‌کنند و نحوه‌ی توزیع آن‌ها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت می‌کنید که از طریق نیوگت سفارش داده‌اید.
به این ترتیب نه تنها کار توزیع برنامه‌های مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامه‌ها نخواهد داشت و مشکلات جانبی را به وجود نمی‌آورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانی‌ها را دریافت کنید. اینبار به روز رسانی بسته‌های نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارش‌های قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی می‌شود.

البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائه‌ی آن، PCLها منسوخ شده درنظر گرفته می‌شوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرم‌های دیگر، به صورت یکجا انتخاب می‌شوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانه‌ی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامه‌ای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامه‌های NETStandard همان برنامه‌های مبتنی بر NET Core. هستند.


ASP.NET Core چیست؟

در زمان نگارش این مطلب، دو گزینه‌ی برنامه‌های وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفاده‌ی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایده‌ی کاملا ماژولار بودن، تهیه شده‌است و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیء‌گرا مانند تزریق وابستگی‌ها، به صورت توکار و از ابتدا پشتیبانی می‌شوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمی‌کند. البته در این بین عدم پشتیبانی از «وب فرم‌ها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائه‌ی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.


اکنون وضعیت  ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟

ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه می‌دهد و دیگر خبری از ارائه‌ی مجزای این‌ها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شده‌است. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی این‌ها را پوشش می‌دهد.

در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شده‌است و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافته‌اند.


آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوری‌های «قدیمی» خودش را بر روی CodePlex نگهداری می‌کند و تمام فناوری‌های «جدید» را به GitHub منتقل کرده‌است. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامه‌ی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگ‌ها و مشکلات گزارش شده باقی می‌مانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex می‌توانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شد‌ه‌است که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ داده‌است. نگارش‌های 5 و 2 آن‌ها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه می‌یابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژه‌ی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.


اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند

برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحله‌ی نهایی تکمیل قابلیت‌های آن نرسیده‌است. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشده‌است.



همانطور که ملاحظه می‌کنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.


فناورهای منسوخ شده‌ی در NET Core.

یکسری از فناوری‌ها و کتابخانه‌ها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوری‌های خاتمه یافته‌ی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا می‌شود و تولید برنامه‌های native و static linking آن‌ها مانند برنامه‌های ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمی‌شوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شده‌است که استفاده‌ی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز می‌گرداند و نه اطلاعات بیشتری را مانند  GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی می‌کند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامه‌ها توصیه شده‌است که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامه‌ها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفاده‌ای از آن نمی‌شود.
- binary serialization: البته کتابخانه‌هایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.


فناور‌هایی که به زودی به NET Core. منتقل می‌شوند

یکسری از فناوری‌ها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml


مراحل نصب ASP.NET Core 1.0

پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارش‌های آزمایشی قبلی را حذف و یا بازنویسی نمی‌کند و همین مساله ممکن است سبب بروز تداخل‌هایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارش‌های RC یا بتا را پیشتر نصب کرده‌اید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بسته‌های قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.


بررسی شماره نگارش 1.0 RTM

پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
 C:\Users\Vahid>dotnet --version
1.0.0-preview2-003121
همانطور که مشاهده می‌کنید، نگارش ذکر شده هنوز در مرحله‌ی preview است و صرفا مرتبط است به tooling و یا ابزارهای مرتبط با آن.
اگر یک پروژه‌ی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
 .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template


در اینجا فایل جدیدی را به نام global.json مشاهده می‌کنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشه‌های اصلی پروژه:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003121"
  }
}
مطالب
OpenCVSharp #2
کتابخانه‌ی اصلی OpenCV، دارای دو نوع اینترفیس C و ++C است. اینترفیس C آن مرتبط است به نگارش‌های 1x آن و اینترفیس ++C آن به همراه نگارش‌های 2x آن ارائه شده‌اند. کتابخانه‌ی OpenCVSharp هر دو نوع اینترفیس یاد شده را پشتیبانی می‌کند. در این قسمت نگاهی خواهیم داشت به نحوه‌ی بارگذاری و نمایش تصاویر در OpenCV به کمک متدهای اینترفیس C آن، مانند cvLoadImage، cvShowImage، cvReleaseImage.


بارگذاری و نمایش تصاویر به کمک OpenCVSharp

متدهای اینترفیس C مربوط به OpenCV، در OpenCVSharp با ذکر کلاس Cv آن قابل دسترسی هستند. برای نمونه متدهای C یاد شده‌ی در ابتدای بحث، چنین معادلی را در OpenCVSharp دارند:
using OpenCvSharp;
 
namespace OpenCVSharpSample02
{
  class Program
  {
   static void Main(string[] args)
   {
    var img = Cv.LoadImage(@"..\..\images\ocv02.jpg");
 
    Cv.NamedWindow("window");
    Cv.ShowImage("window", img);
 
    Cv.WaitKey();
 
    Cv.DestroyWindow("window");
 
    Cv.ReleaseImage(img);
   }
  }
}
متد cvLoadImage اینترفیس C، به Cv.LoadImage تبدیل شده‌است و مابقی نیز به همین ترتیب.
در اینجا با استفاده از متد LoadImage، تصویری را از مسیر مشخصی، بارگذاری می‌کنیم. سپس یک پنجره‌ی OpenCV ایجاد و این تصویر در آن نمایش داده می‌شود. متد WaitKey منتظر فشرده شدن یک کلید بر روی پنجره‌ی OpenCV می‌شود. پس از آن این پنجره تخریب و همچنین منابع native این تصویر آزاد می‌شوند.


متد LoadImage، پارامتر دومی را نیز می‌پذیرد:
 var img = Cv.LoadImage(@"..\..\images\ocv02.jpg", LoadMode.GrayScale);
برای مثال در اینجا می‌توان به کمک مقدار LoadMode.GrayScale، تصویر را به صورت سیاه و سفید بارگذاری کرد.
Enum تعریف شده‌ی در اینجا قابلیت or یا جمع منطقی را نیز دارد. برای مثال می‌توان مقدار  LoadMode.AnyColor | LoadMode.AnyDepth را نیز مشخص کرد؛ جهت بارگذاری تصویر اصلی با مشخصات کامل آن که حالت پیش فرض است.


کلاس‌های پشت صحنه‌ی اینترفیس C در OpenCVSharp

علت وجود کلاس Cv در OpenCVSharp، سهولت برگرداندن مثال‌های C کتابخانه‌ی OpenCV به نمونه‌ها‌ی دات نتی است. اما اگر قصد داشته باشید از کلاس‌های پشت صحنه‌ی این اینترفیس در OpenCVSharp استفاده کنید، می‌توان کدهای فوق را به نحو ذیل نیز بازنویسی کرد:
using (var img = new IplImage(@"..\..\images\ocv02.jpg", LoadMode.Unchanged))
{
  using (var window = new CvWindow("window"))
  {
   window.Image = img;
   Cv.WaitKey();
  }
}
خروجی متد LoadImage از نوع کلاس IplImage است. در اینجا می‌توان همین کلاس را وهله سازی کرد و مورد استفاده قرار داد. به علاوه اینبار این کلاس تهیه شده، اینترفیس IDisposable را نیز پیاده سازی می‌کند. بنابراین می‌توان با استفاده از عبارت using کار آزاد سازی منابع آن‌را خودکار کرد.
همچنین پنجره‌ی OpenCV نیز در اینجا با کلاس CvWindow پیاده سازی می‌شود که این کلاس نیز اینترفیس IDisposable را پیاده سازی می‌کند.


یک نکته‌ی تکمیلی

اگر متد LoadImage کتابخانه‌ی OpenCV قادر به بارگذاری تصویر شما نبود، متد دیگری به نام IplImage.FromFile نیز پیش بینی شده‌است. این متد از امکانات System.Drawing.Bitmap دات نت برای بارگذاری تصویر و تبدیل آن به فرمت OpenCV استفاده می‌کند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
این Prerendering است که امکان رندر یک کامپوننت تعاملی را در سمت سرور میسر می‌کند تا کاربر بتواند پیش از فعال شدن قابلیت‌های پیشرفته‌ی یک کامپوننت، یک حداقل خروجی را از آن مشاهده کند و همچنین وجود آن برای موتورهای جستجو و بهبود SEO بسیار مفید است. اما ... در این بین مشکلی رخ می‌دهد که نمونه‌ی آن‌را در قسمت قبل مشاهده کردیم: آغاز آن دوبار صورت می‌گیرد؛ یکبار در سمت سرور برای تهیه‌ی یک خروجی SSR و یکبار هم پس از فعال شدن قابلیت‌های تعاملی آن در سمت کلاینت. این آغاز دوباره، برای هر دو حالت کامپوننت‌های تعاملی Blazor Server و Blazor WASM برقرار است. راه‌حل‌هایی از نحوه‌ی مواجه شدن با یک چنین مشکلی را در قسمت قبل بررسی کردیم. راه‌حل دیگری که در این بین ارائه شده و توسط خود مایکروسافت هم در مثال‌های آن مورد استفاده قرار می‌گیرد، استفاده از سرویس PersistentComponentState است که جزئیات آن‌را در این قسمت بررسی خواهیم کرد.


بررسی نحوه‌ی عملکرد سرویس PersistentComponentState

سرویس PersistentComponentState، در دات‌نت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که به‌نظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامه‌های Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
مشکلی که در این حالت بروز می‌کرد این بود که متد OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شد؛ یکبار در زمان pre-rendering در سمت سرور، تا HTML استاتیکی برای ارائه‌ی به مرورگر کاربر تولید شود و بار دیگر در زمان فعال شدن اتصال SignalR کامپوننت و ارائه‌ی نهایی تعاملی آن. به همین جهت، کار سنگین آغازین یک کامپوننت، دوبار انجام می‌شد که تجربه‌ی کاربری ناخوشایندی را هم به همراه داشت. برای رفع این مشکل، tag helper ویژه‌ای را به نام persist-component-state تهیه کردند که به صورت زیر به فایل host.cshtml_ اضافه می‌شد:
<body>
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> 
    <persist-component-state />
</body>
این tag-helper فوق است که کار درج رمزنگاری شده‌ی اطلاعات کش شده‌ی pre-rendering سمت سرور را در انتهای فایل HTML صفحه انجام می‌دهد و برای نمونه، نحوه‌ی استفاده‌ی از آن به صورت زیر است:
@page "/"

@implements IDisposable
@inject PersistentComponentState applicationState
@inject IUserRepository userRepository

@foreach(var user in users)
{
    <ShowUserInformation user="@item"></ShowUserInformation>
}

@code {
    private const string cachingKey = "something_unique";
    private List<User> users = new();
    private PersistingComponentStateSubscription persistingSubscription;
    
    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = applicationState.RegisterOnPersisting(PersistData);

        if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored))
        {
            users = restored;
        }
        else 
        {
            users = await userRepository.GetAllUsers();
        }
    }

    public void Dispose()
    {
        persistingSubscription.Dispose();
    }

    private Task PersistData()
    {
        applicationState.PersistAsJson(cachingKey, users);
        return Task.CompletedTask;
    }

}
توضیحات:
- ابتدا تزریق سرویس PersistentComponentState را مشاهده می‌کنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت می‌کند.
- سپس فراخوانی متد RegisterOnPersisting انجام شده‌است. متدی که توسط آن ثبت می‌شود، در حین عملیات pre-rendering فراخوانی می‌شود تا اطلاعاتی را کش کند. نحوه‌ی این کش شدن را در ادامه‌ی مطلب بررسی می‌کنیم.
- سپس فراخوانی متد TryTakeFromJson رخ‌داده‌است. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعه‌ی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده می‌کنید تا اگر کاربر به صفحه‌ی دیگری مراجعه کرد، متد ثبت شده‌ی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.

اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ داده‌ها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام می‌شود و رندر دومی که متعاقبا در سمت کلاینت رخ می‌دهد.

یک نکته: به یک چنین قابلیتی در فریم‌ورک‌های دیگر «hydration» هم گفته می‌شود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آن‌را در زمان فعال شدن سمت کلاینت کامپوننت، برای استفاده‌ی مجدد مهیا می‌کند.


سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره می‌شوند؟

همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره می‌کند. اما ... این اطلاعات دقیقا کجا ذخیره می‌شوند؟
برای مشاهده‌ی آن‌ها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شده‌ی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz->
<!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
فرمت این اطلاعات، JsonSerializer.SerializeToUtf8Bytes رمزنگاری شده‌ی توسط IDataProtectionProvider است. این هم یک روش نگهداری اطلاعات، بجای استفاده از کش مرورگر است؛ بدون نیاز به استفاده از جاوااسکریپت و کار با local storage و امثال آن.

مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام داده‌است! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال می‌شوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده می‌شوند و هدف آن‌ها ارسال اطلاعاتی به سمت سرور نیست.

یک نکته: همانطور که عنوان شد، در نگارش‌های قبلی Blazor، از تگ‌هلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده می‌کردند. استفاده از این تگ‌هلپر در Blazor 8x منسوخ شده و به صورت خودکار داده‌های تمام سرویس‌های PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره می‌کنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج می‌کنند.


روش خاموش کردن Pre-rendering

برای خاموش کردن pre-rendering نیاز است پارامتر هم‌نامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode

@code 
{
    private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}


بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState

در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام می‌شود؛ یکبار در زمان pre-rendering و با مراجعه‌ی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آن‌را از بانک اطلاعاتی دریافت نکنیم، راه‌حل سوم آن، استفاده از سرویس PersistentComponentState است.

در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده می‌کنید که کمی پیچیده‌تر شده‌است. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده می‌شوند و نه پس از کلیک بر روی دکمه‌ای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیش‌رندر سمت سرور اجرا می‌شود و بار دیگر زمانیکه وب‌اسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال می‌شود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شده‌ی در حین pre-rendering ابتدایی رخ‌داده‌ی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وب‌اسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده می‌شوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.

@implements IDisposable

@inject IProductStore ProductStore
@inject PersistentComponentState PersistentState

<h3>Related products</h3>

@if (_relatedProducts == null)
{
    <p>Loading...</p>
}
else
{
    <div class="mt-3">
        @foreach (var item in _relatedProducts)
        {
            <a href="/ProductDetails/@item.Id">
                <div class="col-sm">
                    <h5 class="mt-0">@item.Title (@item.Price)</h5>
                </div>
            </a>
        }
    </div>
}

@code{

    private const string StateCachingKey = "products";
    private IList<Product>? _relatedProducts;
    private PersistingComponentStateSubscription _persistingSubscription;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnInitializedAsync()
    {
        _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);

        if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored))
        {
            _relatedProducts = restored;
        }
        else
        {
            await Task.Delay(500); // Simulates asynchronous loading
            _relatedProducts = await ProductStore.GetRelatedProducts(ProductId);
        }
    }

    private Task OnPersistingAsync()
    {
        PersistentState.PersistAsJson(StateCachingKey, _relatedProducts);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _persistingSubscription.Dispose();
    }

}
در این پیاده سازی هنوز هم از سرویس IProductStore استفاده می‌شود که دو نگارش سمت سرور و سمت کلاینت آن‌را در قسمت قبل تهیه کردیم. برای مثال باتوجه به اینکه کدهای فوق در حین pre-rendering در سمت سرور اجرا می‌شوند، به صورت خودکار از نگارش سمت سرور IProductStore استفاده خواهد شد.

نکته‌ی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیه‌ی کامپوننت درست کار می‌کنند. یعنی اگر نگارش وب‌اسمبلی کامپوننت پس از وقوع پیش‌رندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعه‌ی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمی‌کنند و در این حالت کش شدنی رخ نمی‌دهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفاده‌ی از PersistentComponentState را زیر سؤال می‌برد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمی‌شود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمی‌خواند). اطلاعات بیشتر 
مطالب دوره‌ها
ارتباطات بلادرنگ و SignalR
زمانیکه صحبت از برنامه‌های بلادرنگ می‌شود با کاربرانی سر و کار داریم که نیاز دارند تا اطلاعات مورد نیاز خود را همواره و بلافاصله در آخرین وضعیت به روز آن مشاهده کنند. در این بین، کلاینت می‌خواهد یک برنامه وب باشد یا سیلورلایت و یا یک برنامه نوشته شده با WPF. حتی برنامه‌های موبایل را نیز باید به این لیست اضافه کرد.
در اینجا کلمه بلادرنگ به معنای ارسال اطلاعات از طرف سرور به کلاینت‌ها با فاصله زمانی بسیار کوتاهی از به روز رسانی اطلاعات صورت گرفته در سمت سرور است.
نمونه‌ای از این برنامه‌ها شامل موارد ذیل هستند:
- اطلاع رسانی همزمان به گروهی از کاربران
- جستجوهای زنده و به روز رسانی‌هایی از این دست
- نمایش بلادرنگ قیمت‌ها و وضعیت تجاری محصولات و سهام‌ها
- بازی‌های تعاملی
- برنامه‌های گروهی و تعاملی (مانند برنامه‌های Chat)
- برنامه‌های شبکه‌های اجتماعی (برای مثال پیام جدیدی دارید؛ شخص خاصی آنلاین یا آفلاین شد و امثال آن)

بنابراین به صورت خلاصه قصد داریم به ارائه بازخوردها و اطلاع رسانی‌های بلادرنگ یا نسبتا سریع و به روز از سمت سرور به کلاینت‌ها برسیم.
برای مثال یک دیتاگرید را درنظر بگیرید. دو کاربر در شبکه صفحه یکسانی را گشوده‌اند و یکی از آن‌ها مشغول به ویرایش و یا حذف اطلاعات است. در ارتباطات بلادرنگ کاربر یا کاربران دیگر نیز باید (یا بهتر است) در زمانیکه گرید یکسانی را گشوده‌اند، بلافاصله آخرین تغییرات را ملاحظه کنند. یا حتی حالتی را درنظر بگیرید که شخصی SQL Server management studio را گشوده و در آنجا مشغول به تغییر اطلاعات گردیده است. در این حالت نیز بهتر است آخرین تغییرات بلافاصله به اطلاع کاربران رسانده شوند.

معرفی الگوی Push service

البته باید دقت داشت که الگوی push service یک الگوی رسمی ذکر شده در گروه‌های مرسوم الگوهای طراحی نیست، اما مفهوم آن سرویسی است که چندین کار ذیل را انجام می‌دهد:
الف) پذیرش اتصالات از چندین مصرف کننده. مصرف کننده‌ها در اینجا الزاما محدود به کلاینت‌های وب یا دسکتاپ نیستند؛ می‌توانند حتی یک سرور یا سرویس دیگر نیز باشند.
ب) قادر است اطلاعات را به مصرف کننده‌های خود ارسال کند. این سرویس می‌تواند یک برنامه ASP.NET باشد یا حتی یک سرویس متداول ویندوز.
ج) در اینجا چندین منبع خارجی مانند یک بانک اطلاعاتی یا تغییرات رخ داده توسط یک سخت افزار که می‌توانند سبب بروز رخدادهایی در push service گردند نیز می‌تواند وجود داشته باشند. هر زمان که تغییری در این منابع خارجی رخ دهد، مایل هستیم تا مصرف کننده‌ها را مطلع سازیم.


پروتکل HTTP و ارتباطات بلادرنگ

پروتکلی که در ارتباطات بلادرنگ مبتنی بر SignalR مورد استفاده قرار می‌گیرد، HTTP است و از قابلیت‌های Request و Response آن در اینجا بیشترین بهره برده می‌شود. پیاده سازی Push عموما بر مبنای یکی از روش‌های متداول زیر است:
1) Periodic polling
به این معنا که مثلا هر 10 ثانیه یکبار، کاری را انجام بده؛ مانند ارسال متناوب: آیا تغییری رخ داده؟ آیا تغییری رخ داده؟ و .... به همین ترتیب. این روش اصلا بهینه نبوده و منابع زیادی را خصوصا در سمت سرور مصرف خواهد کرد. برای مثال:
function getInfo() {
         $.ajax("url", function ( newInfo){
                  if ( newInfo != null) {
                      // do something with the data
                  }
         });
    // poll again after 20 seconds
    setTimeout(getInfo,20000);
}
// start the polling loop
getInfo();

2) Long polling
به آن HTTP Streaming یا Comet هم گفته می‌شود. این روش نسبتا هوشمند بوده و کلاینت اتصالی را به سرور برقرار خواهد کرد. سرور در این حالت تا زمانیکه اطلاعاتی را در دسترس نداشته باشد، پاسخی نخواهد داد. برای نمونه:
function getNewInfo(){
  $.ajax("url", function (newinfo) {
      // do something with the data
  // start the new request
      getNewINfo();
  });
}
// start the polling loop
getNewInfo();

این روش نسبت به حالت Periodic polling بهینه‌تر است اما نیاز به اتصالات زیادی داشته و همچنین تردهای بسیاری را در سمت سرور به خود مشغول خواهد کرد.

3) Forever frame
فقط در IE پشتیبانی می‌شود. در این روش یک Iframe مخفی توسط مرورگر تشکیل شده و از طریق آن درخواستی به سرور ارسال می‌شود. سپس سرور متناوبا با تزریق اسکریپت‌هایی به این Iframe سبب فراخوانی مجدد وضعیت خود می‌گردد. در این روش نیز به ازای هر درخواست و پاسخ، ارتباطات گشوده و بسته خواهند شد.

4) Server Sent Events یا SSE
این مورد جزو استاندارد HTML5 است. در اینجا اتصالی برقرار شده و داده‌ها از طریق اتصالات HTTP منتقل می‌شوند.
var eventSrc = new EventSource("url");
    // register event handler for the message
    eventSrc.addEventListener( "message",function (evt) {
    //process the data
});
این روش نیز بسیار شبیه به حالت long polling است. سرور تا زمانیکه اطلاعاتی را برای پاسخ دهی فراهم نداشته باشد، اتصال را باز نگه می‌دارد. به این ترتیب از لحاظ مقیاس پذیری گزینه بهتری است (نسبت به حالتیکه مدام اتصال برقرار و قطع می‌شود). اکثر مرورگرها منهای نگارش‌های قدیمی IE از این روش پشتیبانی می‌کنند.
تنها تفاوت آن با حالت long polling در این است که پس از ارائه پاسخ به کلاینت، اتصال را قطع نمی‌کند. Long polling نیز اتصال را باز نگه می‌دارد، اما این اتصال را بلافاصله پس از ارائه پاسخ، می‌بندد.

5) Web sockets
Web sockets در سکوی کاری ویندوز، تنها در ویندوز‌های 8، ویندوز سرور 2012 و دات نت 4 و نیم پشتیبانی می‌شود. هرچند این روش در حال حاضر به عنوان بهترین روش Push مطرح است اما به دلیل محدودیتی که یاد شد، مدتی طول خواهد کشید تا استفاده گسترده‌ای پیدا کند.
var socket = new WebSocket("url");
socket.onmessage = function (msg) {
var newInfo = msg.data;
// do something with the data
}
// client can also send request to server
socket.send(.... )
با این اوصاف آیا راه حل بهتر و میانه‌تری وجود دارد؟
بلی. اگر به وضعیت فعلی سکوی کاری ASP.NET نگاه کنیم:

SignalR را می‌توان مشاهده کرد که در گروه ساخت سرویس‌های آن قرار گرفته است. همانطور که ملاحظه می‌کنید، این سرویس جدید آنچنان وابستگی به سایر اجزای آن نداشته و می‌تواند خارج از ASP.NET نیز مورد استفاده قرار گیرد.

SignalR چیست؟

SignalR راه حلی است سمت سرور برای نوشتن push services. همچنین به همراه کتابخانه‌های سمت کاربری است که ارتباطات push services را در انواع و اقسام سکوهای کاری میسر می‌سازد. SignalR سورس باز بوده و برای اعمال غیرهمزمان (asynchronous) بهینه سازی شده است.
SignalR بر اساس مدل ذهنی اتصالات ماندگار (persistent connections) طراحی شده است. اتصالات ماندگار را باید به عنوان اتصالاتی سریع و غیرطولانی درنظر گرفت. در اینجا Signal یک اتصال است که اطلاعاتی به آن ارسال می‌گردد و هدف، انتقال قطعات کوچکی از اطلاعات است و هدف، ارسال حجم عظیمی از اطلاعات نیست. برای مثال اطلاع رسانی سریعی صورت گیرد که تغییراتی رخ داده است و سپس ادامه کار و دریافت اطلاعات واقعی توسط فرآیندهای متداول مثلا HTTP GET انجام شود. البته باید دقت داشت SignalR نیز نهایتا از یکی از 5 روش push بررسی شده در این قسمت استفاده می‌کند. اما بر اساس توانایی‌های کلاینت و سرور، به صورت هوشمند بهترین و بهینه‌ترین انتخاب را به کاربر ارائه می‌دهد.
اتصالات ماندگار قسمت سطح پایین SignalR را تشکیل می‌دهند. سطح بالاتر آن که این مفاهیم را به شکلی کپسوله شده ارائه می‌دهد، Hubs نام دارد که پایه اصلی دوره جاری را تشکیل خواهد داد.



همانطور که عنوان شد، SignalR سورس باز بوده و دارای مخزن کدی عمومی در GitHub است. همچنین بسته‌های تشکیل دهنده‌ی آن از طریق NuGet نیز قابل دریافت هستند. این بسته‌ها شامل هسته SignalR و کلاینت‌های آن مانند کلاینت‌های WinRT، سیلورلایت، jQuery، ویندوز فون8 و امثال آن هستند.

شروع کار با SignalR

تیم SignalR مثالی مقدماتی از نحوه کار با SignalR را به صورت یک بسته NuGet ارائه داده‌اند که از طریق آدرس و فرمان ذیل قابل دریافت است:
 PM> Install-Package Microsoft.AspNet.SignalR.Sample
قبل از اینکه این مثال را دریافت کنید نیاز است ابتدا یک برنامه ASP.NET جدید را آغاز نمائید (تفاوتی نمی‌کند که MVC باشد یا Web forms). سپس دستور فوق را فراخوانی کنید.

پس از دریافت مثال، یکبار پروژه را کامپایل کرده و سپس بر روی فایل StockTicker.html آن کلیک راست نموده و گزینه مشاهده در مرورگر را انتخاب کنید. همچنین برای اینکه این مثال را بهتر مشاهده کنید، بهتر است دو وهله از مرورگر را باز کرده و آدرس باز شده را در آن بررسی کنید تا اعمال تغییرات همزمان به کلاینت‌های متفاوت را بهتر بتوان بررسی و مشاهده کرد.

مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت چهارم - data binding
در قسمت قبل، نگاهی مقدماتی داشتیم به مبحث data binding. در ادامه، این مبحث را به همراه pipes، جهت اعمال تغییرات بر روی اطلاعات، پیگیری خواهیم کرد.


انقیاد به خواص یا property binding

قابلیت property binding این امکان را فراهم می‌کند که یکی از خواص المان‌های HTML را به مقادیر دریافتی از کلاس کامپوننت، متصل کنیم:
 <img [src]='producr.imageUrl'>
در این مثال، خاصیت src المان تصویر، به آدرس تصویر یک محصول متصل شده‌است.
در حین تعریف property binding، مقصد اتصال، داخل براکت‌ها قرار می‌گیرد و خاصیت مدنظر المان را مشخص می‌کند. منبع اتصال همیشه داخل "" در سمت راست علامت مساوی قرار می‌گیرد.
اگر اینکار را بخواهیم با interpolation معرفی شده‌ی در قسمت قبل انجام دهیم، به کد ذیل خواهیم رسید:
 <img src={{producr.imageUrl}}>
در اینجا نه از []، برای معرفی مقصد اتصال استفاده شده‌است و نه از "" برای مشخص سازی منبع اتصال. این نوع اتصال یک طرفه است (از منبع به مقصد).

خوب، در یک چنین مواردی property binding بهتر است یا interpolation؟
توصیه‌ی کلی ترجیح property binding به interpolation است. اما اگر در اینجا نیاز به انجام محاسباتی بر روی عبارت منبع وجود داشت، باید از interpolation استفاده کرد؛ مانند:
 <img src='http://www.mysite.com/images/{{producr.imageUrl}}'>


تکمیل قالب کامپوننت لیست محصولات

اگر از قسمت قبل به خاطر داشته باشید، در فایل product-list.component.html، لیست پردازش شده‌ی توسط ngFor*، فاقد ستون نمایش تصاویر محصولات است. به همین جهت فایل یاد شده را گشوده و سپس با استفاده از property binding، دو خاصیت src و title تصویر را به منبع داده‌ی آن متصل می‌کنیم:
<tbody>
    <tr *ngFor='#product of products'>
        <td>
            <img [src]='product.imageUrl'
                 [title]='product.productName'>
        </td>
        <td>{{ product.productName }}</td>
        <td>{{ product.productCode }}</td>
        <td>{{ product.releaseDate }}</td>
        <td>{{ product.price }}</td>
        <td>{{ product.starRating }}</td>
    </tr>
</tbody>
در این حالت اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:


هرچند اینبار تصاویر محصولات نمایش داده شده‌اند، اما اندکی بزرگ هستند. بنابراین در ادامه با استفاده از property binding، خواص style آن‌را تنظیم خواهیم کرد. برای این منظور فایل product-list.component.ts را گشوده و به کلاس ProductListComponent، دو خاصیت imageWidth و imageMargin را اضافه می‌کنیم:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    products: any[] = [
        // as before...
    ];
}
البته همانطور که پیشتر نیز ذکر شد، چون مقادیر پیش فرض این خواص عددی هستند، نیازی به ذکر صریح نوع number در اینجا وجود ندارد (type inference).
پس از تعریف این خواص، امکان دسترسی به آن‌ها در قالب کامپوننت وجود خواهد داشت:
<tbody>
    <tr *ngFor='#product of products'>
        <td>
            <img [src]='product.imageUrl'
                 [title]='product.productName'
                 [style.width.px]='imageWidth'
                 [style.margin.px]='imageMargin'>
        </td>
همانطور که مشاهده می‌کنید، چون خاصیت‌های جدید تعریف شده، جزئی از خواص اصلی کلاس هستند و نه خواص اشیاء لیست محصولات، دیگر به همراه .product ذکر نشده‌اند.
همچنین در اینجا نحوه‌ی style binding را نیز مشاهده می‌کنید. مقصد اتصال همیشه با [] مشخص می‌شود و سپس کار با ذکر .style شروع شده و پس از آن نام خاصیت مدنظر عنوان خواهد شد. اگر نیاز به ذکر واحدی وجود داشت، پس از درج نام خاصیت، قید خواهد شد. برای مثال [style.fontSize.em] و یا [%.style.fontSize]


یک نکته:
اگر مثال را قدم به قدم دنبال کرده باشید، با افزودن style binding و بارگذاری مجدد صفحه، احتمالا تغییراتی را مشاهده نخواهید کرد. این مورد به علت کش شدن قالب قبلی و یا فایل جاوا اسکریپتی متناظر با آن است (فایلی که خواص عرض و حاشیه‌ی تصویر به آن اضافه شده‌اند).
یک روش ساده‌ی حذف کش آن، بازکردن آدرس http://localhost:2222/app/products/product-list.component.js در مرورگر به صورت مجزا و سپس فشردن دکمه‌های ctrl+f5 بر روی آن است.


پاسخ دادن به رخدادها و یا event binding

تا اینجا تمام data binding‌های تعریف شده‌ی ما یک طرفه بودند؛ از خواص کلاس کامپوننت به اجزای قالب متناظر با آن. اما گاهی از اوقات نیاز است تا با کلیک کاربر بر روی دکمه‌ای، عملی خاص صورت گیرد و در این حالت، جهت ارسال اطلاعات، از قالب کامپوننت، به متدها و خواص کلاس متناظر با آن خواهند بود. کامپوننت به اعمال کاربر از طریق event binding گوش فرا می‌دهد:
 <button (click)='toggleImage()'>
syntax آن بسیار شبیه است به حالت property binding و در اینجا بجای [] از () جهت مشخص سازی رخدادی خاص از المان مدنظر استفاده می‌شود. سمت راست این انتساب، متدی است که داخل "" قرار می‌گیرد و این متد متناظر است با متدی مشخص در کلاس متناظر با کامپوننت جاری.
در این حالت اگر کاربر روی دکمه‌ی تعریف شده کلیک کند، متد toggleImage موجود در کلاس متناظر، فراخوانی خواهد شد.
چه رخدادهایی را در اینجا می‌توان ذکر کرد؟ پاسخ آن‌را در آدرس ذیل می‌توانید مشاهده کنید:
https://developer.mozilla.org/en-US/docs/Web/Events

این syntax جدید AngularJS 2.0 سطح API آن‌را کاهش داده است. دیگر در اینجا نیازی نیست تا به ازای هر رخداد ویژه‌ای، یک دایرکتیو و یا syntax خاص آن‌را در مستندات آن
جستجو کرد. فقط کافی است syntax جدید (نام رخداد) را مدنظر داشته باشید.


تکمیل مثال نمایش لیست محصولات با فعال سازی دکمه‌ی Show Image آن

در اینجا قصد داریم با کلیک بر روی دکمه‌ی Show image، تصاویر موجود در ستون تصاویر، مخفی و یا نمایان شوند. برای این منظور خاصیت جدیدی را به نام showImage به کلاس ProductListComponent اضافه می‌کنیم. بنابراین فایل product-list.component.ts را گشوده و تغییر ذیل را به آن اعمال کنید:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    showImage: boolean = false;
در اینجا خاصیت Boolean جدیدی به نام showImage با مقدار اولیه‌ی false تعریف شده‌است. به این ترتیب تصاویر، در زمان اولین بارگذاری صفحه نمایش داده نخواهند شد.
سپس به انتهای کلاس، پس از تعاریف خواص، متد جدید toggleImage را اضافه می‌کنیم:
export class ProductListComponent {
    // as before ...
 
    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}
در کلاس‌های TypeScript نیازی به ذکر صریح واژه‌ی کلیدی function برای تعریف متدی وجود ندارد. این متد، خروجی هم ندارد، بنابراین نوع خروجی آن void مشخص شده‌است. در بدنه‌ی این متد، وضعیت خاصیت نمایش تصویر معکوس می‌شود.
پس از این تغییرات، اکنون می‌توان به قالب این کامپوننت یا فایل product-list.component.html مراجعه و event binding را تنظیم کرد:
<button class='btn btn-primary'
        (click)='toggleImage()'>
    Show Image
</button>
در اینجا click به عنوان رخداد مقصد، مشخص شده‌است. سپس آن‌را به متد toggleImage کلاس ProductListComponent متصل می‌کنیم.
خوب، تا اینجا اگر کاربر بر روی دکمه‌ی show image کلیک کند، مقدار خاصیت showImage کلاس ProductListComponent با توجه به کدهای متد toggleImage، معکوس خواهد شد.
مرحله‌ی بعد، استفاده از مقدار این خاصیت، جهت مخفی و یا نمایان ساختن المان تصویر جدول نمایش داده شده‌است. اگر از قسمت قبل به خاطر داشته باشید، کار ngIf*، حذف و یا افزودن المان‌های DOM است. بنابراین ngIf* را به المان تصویر جدول اضافه می‌کنیم:
<tr *ngFor='#product of products'>
    <td>
        <img *ngIf='showImage'
             [src]='product.imageUrl'
             [title]='product.productName'
             [style.width.px]='imageWidth'
             [style.margin.px]='imageMargin'>
    </td>
با توجه به ngIf* تعریف شده، المان تصویر تنها زمانی به DOM اضافه خواهد شد که مقدار خاصیت showImage مساوی true باشد.

اکنون برنامه را اجرا کنید. در اولین بار اجرای صفحه، تصاویر ستون اول جدول، نمایش داده نمی‌شود. پس از کلیک بر روی دکمه‌ی Show image، این تصاویر نمایان شده و اگر بار دیگر بر روی این دکمه کلیک شود، این تصاویر مخفی خواهند شد.

یک مشکل! در هر دو حالت نمایش و مخفی سازی تصاویر، برچسب این دکمه Show image است. بهتر است زمانیکه قرار است تصاویر مخفی شوند، برچسب hide image نمایش داده شود و برعکس. برای حل این مساله از interpolation به نحو ذیل استفاده خواهیم کرد:
<button class='btn btn-primary'
        (click)='toggleImage()'>
    {{showImage ? 'Hide' : 'Show'}} Image
</button>
در اینجا اگر مقدار خاصیت showImage مساوی true باشد، مقدار رشته‌ای Hide و اگر false باشد، مقدار رشته‌ای show بجای {{}} درج خواهد شد.



بررسی انقیاد دو طرفه یا two-way binding

تا اینجا، اتصال مقدار یک خاصیت عمومی کلاس متناظر با قالبی، به اجزای مختلف آن، یک طرفه بودند. اما در ادامه نیاز است تا بتوان برای مثال در textbox قسمت filter by مثال جاری بتوان اطلاعاتی را وارد کرد و سپس بر اساس آن ردیف‌های جدول نمایش داده شده را فیلتر نمود. این عملیات نیاز به انقیاد دو طرفه یا two-way data binding دارد.
برای تعریف انقیاد دو طرفه در AngularJS 2.0 از دایرکتیو توکاری به نام ngModel استفاده می‌شود:
 <input [(ngModel)]='listFilter' >
ابتدا [] ذکر می‌شود تا مشخص شود که این عملیات در اصل یک property binding است؛ از خاصیت عمومی به نام listFilter به المان textbox تعریف شده.
سپس () تعریف شده‌است تا event binding را نیز گوشزد کند. کار آن انتقال تعاملات کاربر، با المان رابط کاربری جاری، به خاصیت عمومی کلاس یا همان listFilter است.

در اینجا ممکن است که فراموش کنید [()] صحیح است یا ([]) . به همین جهت به این syntax، نام banana in the box را داده‌اند یا «موز درون جعبه»! موز همان event binding است که داخل جعبه‌ی property binding قرار می‌گیرد!

خوب، برای اعمال انقیاد دو طرفه، به مثال جاری، فایل product-list.component.ts را گشوده و خاصیت رشته‌ای listFilter را به آن اضافه می‌کنیم:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    showImage: boolean = false;
    listFilter: string = 'cart';
سپس فایل قالب product-list.component.html را گشوده و انقیاد دو طرفه را به آن اعمال می‌کنیم:
<div class='panel-body'>
    <div class='row'>
        <div class='col-md-2'>Filter by:</div>
        <div class='col-md-4'>
            <input type='text'
                   [(ngModel)]='listFilter' />
        </div>
    </div>
    <div class='row'>
        <div class='col-md-6'>
            <h3>Filtered by: {{listFilter}}</h3>
        </div>
    </div>
در اینجا انقیاد دو طرفه، توسط ngModel، به خاصیت listFilter کلاس، در المان input تعریف شده، صورت گرفته است‌. سپس توسط interpolation مقدار این تغییر را در قسمت Filtered by به صورت یک برچسب نمایش می‌دهیم.


پس از اجرای برنامه، تکست باکس تعریف شده، مقدار اولیه‌ی cart را خواهد داشت و اگر آن‌را تغییر دهیم، بلافاصله این مقدار تغییر یافته را در برچسب Filtered by می‌توان مشاهده کرد. به این رخداد two-way binding می‌گویند.
البته هنوز کار فیلتر لیست محصولات در اینجا انجام نمی‌شود که آن‌را در قسمت بعد تکمیل خواهیم کرد.


فرمت کردن اطلاعات نمایش داده شده‌ی در جدول با استفاده از Pipes

تا اینجا لیست محصولات نمایش داده شد، اما نیاز است برای مثال فرمت ستون نمایش قیمت آن بهبود یابد. برای این منظور، از ویژگی دیگری به نام pipes استفاده می‌شود و کار آن‌ها تغییر داده‌ها، پیش از نمایش آن‌ها است. AngularJS 2.0 به همراه تعدادی pipe توکار برای فرمت مقادیر است؛ مانند date، number، decimal، percent و غیره. همچنین امکان ساخت custom pipes نیز پیش بینی شده‌است.
در اینجا یک مثال ساده‌ی pipes را مشاهده می‌کنید:
 {{ product.productCode | lowercase }}
پس از قید نام خاصیتی که قرار است نمایش داده شود، حرف pipe یا | قرار گرفته و سپس نوع pipe ذکر می‌شود. به این ترتیب کد محصول، پیش از نمایش، ابتدا به حروف کوچک تبدیل شده و سپس نمایش داده می‌شود.

از pipes در property binding هم می‌توان استفاده کرد:
 [title]='product.productName | uppercase'
در اینجا برای مثال عنوان تصویر با حروف بزرگ نمایش داده خواهد شد.

و یا می‌توان pipes را به صورت زنجیره‌ای نیز تعریف کرد:
 {{ product.price | currency | lowercase }}
در اینجا pipe توکار currency سبب نمایش سه حرف اول واحد پولی، با حروف بزرگ می‌شود. اگر علاقمند بودیم که آن‌را با حروف کوچک نمایش دهیم می‌توان یک pipe دیگر را در انتهای این زنجیره قید کرد.

بعضی از pipes، پارامتر هم قبول می‌کنند:
 {{ product.price | currency:'USD':true:'1.2-2' }}
در اینجا هر پارامتر با یک : مشخص می‌شود. برای مثال pipe واحد پولی، سه پارامتر را دریافت می‌کند: یک کد دلخواه، نمایش یا عدم نمایش علامت پولی، بجای کد دلخواه و مشخصات ارقام نمایش داده شده. برای مثال 2-1.2، یعنی حداقل یک عدد پیش از اعشار، حداقل دو عدد پس از اعشار و حداکثر دو عدد پس از اعشار باید ذکر شوند (یعنی در نهایت دو رقم اعشار مجاز است).
مبحث ایجاد custom pipes را در قسمت بعدی دنبال خواهیم کرد.

در ادامه برای ویرایش مثال جاری، فایل قالب product-list.component.html را گشوده و سطرهای جدول را به نحو ذیل تغییر دهید:
<td>{{ product.productName }}</td>
<td>{{ product.productCode | lowercase }}</td>
<td>{{ product.releaseDate }}</td>
<td>{{ product.price | currency:'USD':true:'1.2-2'}}</td>
<td>{{ product.starRating }}</td>
در اینجا با استفاده از pipes، شماره محصول با حروف کوچک و قیمت آن تا حداکثر دو رقم اعشار، فرمت خواهند شد.
اینبار اگر برنامه را اجرا کنید، یک چنین خروجی را مشاهده خواهید کرد:


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


خلاصه‌ی بحث

data binding سبب سهولت نمایش مقادیر خواص کلاس یک کامپوننت، در قالب آن می‌شود. در AngularJS 2.0، چهار نوع binding وجود دارند:


interpolation، عبارت رشته‌ای محاسبه شده را در بین المان‌های DOM درج می‌کند و یا می‌تواند خاصیت المانی را مقدار دهی نماید.
property binding سبب اتصال مقدار خاصیتی، به یکی از خواص المانی مشخص در DOM می‌شود.
event binding به رخ‌دادها گوش فرا داده و سبب اجرای متدی در کلاس کامپوننت، در صورت بروز رخداد متناظری می‌شود.
حالت two-way binding، کار دریافت اطلاعات از کلاس و همچنین بازگشت مقادیر تغییر یافته‌ی توسط کاربر را به کلاس انجام می‌دهد.
اطلاعات نمایش داده شده‌ی توسط binding عموما فرمت مناسبی را ندارد. برای رفع این مشکل از pipes استفاده می‌شود.
اشتراک‌ها
نمایش لیست کشورها به همراه پرچم آنها به صورت dropdown با استفاده از select2
بعد از دانلود و افزودن  select2 ، برای پرچم‌ها سراغ semantic-ui  می‌رویم و بعد از دانلود آن، فایل flag.min.css در آدرس Semantic-Ui/components/flag.min.css را به پروژه اضافه می‌کنیم.
<div>
    <div>
        <select ></select>
    </div>
</div>

 $(document).ready(function () {

            $('.js-example-basic-single').select2({
                data: [
                    { id: "al", text: "Albania" },
                    { id: "dz", text: "Algeria" },
                    { id: "as", text: "American Samoa" },
                    { id: "ad", text: "Andorra" },
                    { id: "ao", text: "Angola" },
                    { id: "ai", text: "Anguilla" },
                    { id: "ag", text: "Antigua" },
                    { id: "ar", text: "Argentina" },
                    { id: "am", text: "Armenia" },
                    { id: "aw", text: "Aruba" },
                    { id: "au", text: "Australia" },
                    { id: "at", text: "Austria" },
                    { id: "az", text: "Azerbaijan" },
                    { id: "bs", text: "Bahamas" },
                    { id: "bh", text: "Bahrain" },
                    { id: "bd", text: "Bangladesh" },
                    { id: "bb", text: "Barbados" },
                    { id: "by", text: "Belarus" },
                    { id: "be", text: "Belgium" },
                    { id: "bz", text: "Belize" },
                    { id: "bj", text: "Benin" },
                    { id: "bm", text: "Bermuda" },
                    { id: "bt", text: "Bhutan" },
                    { id: "bo", text: "Bolivia" },
                    { id: "ba", text: "Bosnia" },
                    { id: "bw", text: "Botswana" },
                    { id: "bv", text: "Bouvet Island" },
                    { id: "br", text: "Brazil" },
                    { id: "vg", text: "British Virgin Islands" },
                    { id: "bn", text: "Brunei" },
                    { id: "bg", text: "Bulgaria" },
                    { id: "bf", text: "Burkina Faso" },
                    { id: "mm", text: "Burma" },
                    { id: "bi", text: "Burundi" },
                    { id: "tc", text: "Caicos Islands" },
                    { id: "kh", text: "Cambodia" },
                    { id: "cm", text: "Cameroon" },
                    { id: "ca", text: "Canada" },
                    { id: "cv", text: "Cape Verde" },
                    { id: "ky", text: "Cayman Islands" },
                    { id: "cf", text: "Central African Republic" },
                    { id: "td", text: "Chad" },
                    { id: "cl", text: "Chile" },
                    { id: "cn", text: "China" },
                    { id: "cx", text: "Christmas Island" },
                    { id: "cc", text: "Cocos Islands" },
                    { id: "co", text: "Colombia" },
                    { id: "km", text: "Comoros" },
                    { id: "cg", text: "Congo Brazzaville" },
                    { id: "cd", text: "Congo" },
                    { id: "ck", text: "Cook Islands" },
                    { id: "cr", text: "Costa Rica" },
                    { id: "ci", text: "Cote Divoire" },
                    { id: "hr", text: "Croatia" },
                    { id: "cu", text: "Cuba" },
                    { id: "cy", text: "Cyprus" },
                    { id: "cz", text: "Czech Republic" },
                    { id: "dk", text: "Denmark" },
                    { id: "dj", text: "Djibouti" },
                    { id: "dm", text: "Dominica" },
                    { id: "do", text: "Dominican Republic" },
                    { id: "ec", text: "Ecuador" },
                    { id: "eg", text: "Egypt" },
                    { id: "sv", text: "El Salvador" },
                    { id: "gb", text: "England" },
                    { id: "gq", text: "Equatorial Guinea" },
                    { id: "er", text: "Eritrea" },
                    { id: "ee", text: "Estonia" },
                    { id: "et", text: "Ethiopia" },
                    { id: "eu", text: "European Union" },
                    { id: "fk", text: "Falkland Islands" },
                    { id: "fo", text: "Faroe Islands" },
                    { id: "fj", text: "Fiji" },
                    { id: "fi", text: "Finland" },
                    { id: "fr", text: "France" },
                    { id: "gf", text: "French Guiana" },
                    { id: "pf", text: "French Polynesia" },
                    { id: "tf", text: "French Territories" },
                    { id: "ga", text: "Gabon" },
                    { id: "gm", text: "Gambia" },
                    { id: "ge", text: "Georgia" },
                    { id: "de", text: "Germany" },
                    { id: "gh", text: "Ghana" },
                    { id: "gi", text: "Gibraltar" },
                    { id: "gr", text: "Greece" },
                    { id: "gl", text: "Greenland" },
                    { id: "gd", text: "Grenada" },
                    { id: "gp", text: "Guadeloupe" },
                    { id: "gu", text: "Guam" },
                    { id: "gt", text: "Guatemala" },
                    { id: "gw", text: "Guinea-Bissau" },
                    { id: "gn", text: "Guinea" },
                    { id: "gy", text: "Guyana" },
                    { id: "ht", text: "Haiti" },
                    { id: "hm", text: "Heard Island" },
                    { id: "hn", text: "Honduras" },
                    { id: "hk", text: "Hong Kong" },
                    { id: "hu", text: "Hungary" },
                    { id: "is", text: "Iceland" },
                    { id: "in", text: "India" },
                    { id: "io", text: "Indian Ocean Territory" },
                    { id: "id", text: "Indonesia" },
                    { id: "ir", text: "Iran" },
                    { id: "iq", text: "Iraq" },
                    { id: "ie", text: "Ireland" },
                    { id: "il", text: "Israel" },
                    { id: "it", text: "Italy" },
                    { id: "jm", text: "Jamaica" },
                    { id: "jp", text: "Japan" },
                    { id: "jo", text: "Jordan" },
                    { id: "kz", text: "Kazakhstan" },
                    { id: "ke", text: "Kenya" },
                    { id: "ki", text: "Kiribati" },
                    { id: "kw", text: "Kuwait" },
                    { id: "kg", text: "Kyrgyzstan" },
                    { id: "la", text: "Laos" },
                    { id: "lv", text: "Latvia" },
                    { id: "lb", text: "Lebanon" },
                    { id: "ls", text: "Lesotho" },
                    { id: "lr", text: "Liberia" },
                    { id: "ly", text: "Libya" },
                    { id: "li", text: "Liechtenstein" },
                    { id: "lt", text: "Lithuania" },
                    { id: "lu", text: "Luxembourg" },
                    { id: "mo", text: "Macau" },
                    { id: "mk", text: "Macedonia" },
                    { id: "mg", text: "Madagascar" },
                    { id: "mw", text: "Malawi" },
                    { id: "my", text: "Malaysia" },
                    { id: "mv", text: "Maldives" },
                    { id: "ml", text: "Mali" },
                    { id: "mt", text: "Malta" },
                    { id: "mh", text: "Marshall Islands" },
                    { id: "mq", text: "Martinique" },
                    { id: "mr", text: "Mauritania" },
                    { id: "mu", text: "Mauritius" },
                    { id: "yt", text: "Mayotte" },
                    { id: "mx", text: "Mexico" },
                    { id: "fm", text: "Micronesia" },
                    { id: "md", text: "Moldova" },
                    { id: "mc", text: "Monaco" },
                    { id: "mn", text: "Mongolia" },
                    { id: "me", text: "Montenegro" },
                    { id: "ms", text: "Montserrat" },
                    { id: "ma", text: "Morocco" },
                    { id: "mz", text: "Mozambique" },
                    { id: "na", text: "Namibia" },
                    { id: "nr", text: "Nauru" },
                    { id: "np", text: "Nepal" },
                    { id: "an", text: "Netherlands Antilles" },
                    { id: "nl", text: "Netherlands" },
                    { id: "nc", text: "New Caledonia" },
                    { id: "pg", text: "New Guinea" },
                    { id: "nz", text: "New Zealand" },
                    { id: "ni", text: "Nicaragua" },
                    { id: "ne", text: "Niger" },
                    { id: "ng", text: "Nigeria" },
                    { id: "nu", text: "Niue" },
                    { id: "nf", text: "Norfolk Island" },
                    { id: "kp", text: "North Korea" },
                    { id: "mp", text: "Northern Mariana Islands" },
                    { id: "no", text: "Norway" },
                    { id: "om", text: "Oman" },
                    { id: "pk", text: "Pakistan" },
                    { id: "pw", text: "Palau" },
                    { id: "ps", text: "Palestine" },
                    { id: "pa", text: "Panama" },
                    { id: "py", text: "Paraguay" },
                    { id: "pe", text: "Peru" },
                    { id: "ph", text: "Philippines" },
                    { id: "pn", text: "Pitcairn Islands" },
                    { id: "pl", text: "Poland" },
                    { id: "pt", text: "Portugal" },
                    { id: "pr", text: "Puerto Rico" },
                    { id: "qa", text: "Qatar" },
                    { id: "re", text: "Reunion" },
                    { id: "ro", text: "Romania" },
                    { id: "ru", text: "Russia" },
                    { id: "rw", text: "Rwanda" },
                    { id: "sh", text: "Saint Helena" },
                    { id: "kn", text: "Saint Kitts and Nevis" },
                    { id: "lc", text: "Saint Lucia" },
                    { id: "pm", text: "Saint Pierre" },
                    { id: "vc", text: "Saint Vincent" },
                    { id: "ws", text: "Samoa" },
                    { id: "sm", text: "San Marino" },
                    { id: "gs", text: "Sandwich Islands" },
                    { id: "st", text: "Sao Tome" },
                    { id: "sa", text: "Saudi Arabia" },
                    { id: "sn", text: "Senegal" },
                    { id: "cs", text: "Serbia" },
                    { id: "rs", text: "Serbia" },
                    { id: "sc", text: "Seychelles" },
                    { id: "sl", text: "Sierra Leone" },
                    { id: "sg", text: "Singapore" },
                    { id: "sk", text: "Slovakia" },
                    { id: "si", text: "Slovenia" },
                    { id: "sb", text: "Solomon Islands" },
                    { id: "so", text: "Somalia" },
                    { id: "za", text: "South Africa" },
                    { id: "kr", text: "South Korea" },
                    { id: "es", text: "Spain" },
                    { id: "lk", text: "Sri Lanka" },
                    { id: "sd", text: "Sudan" },
                    { id: "sr", text: "Suriname" },
                    { id: "sj", text: "Svalbard" },
                    { id: "sz", text: "Swaziland" },
                    { id: "se", text: "Sweden" },
                    { id: "ch", text: "Switzerland" },
                    { id: "sy", text: "Syria" },
                    { id: "tw", text: "Taiwan" },
                    { id: "tj", text: "Tajikistan" },
                    { id: "tz", text: "Tanzania" },
                    { id: "th", text: "Thailand" },
                    { id: "tl", text: "Timorleste" },
                    { id: "tg", text: "Togo" },
                    { id: "tk", text: "Tokelau" },
                    { id: "to", text: "Tonga" },
                    { id: "tt", text: "Trinidad" },
                    { id: "tn", text: "Tunisia" },
                    { id: "tr", text: "Turkey" },
                    { id: "tm", text: "Turkmenistan" },
                    { id: "tv", text: "Tuvalu" },
                    { id: "ug", text: "Uganda" },
                    { id: "ua", text: "Ukraine" },
                    { id: "ae", text: "United Arab Emirates" },
                    { id: "us", text: "United States" },
                    { id: "uy", text: "Uruguay" },
                    { id: "um", text: "Us Minor Islands" },
                    { id: "vi", text: "Us Virgin Islands" },
                    { id: "uz", text: "Uzbekistan" },
                    { id: "vu", text: "Vanuatu" },
                    { id: "va", text: "Vatican City" },
                    { id: "ve", text: "Venezuela" },
                    { id: "vn", text: "Vietnam" },
                    { id: "wf", text: "Wallis and Futuna" },
                    { id: "eh", text: "Western Sahara" },
                    { id: "ye", text: "Yemen" },
                    { id: "zm", text: "Zambia" },
                    { id: "zw", text: "Zimbabwe" }
                ],
                placeholder: "کشور مورد نظرتان را انتخاب نمایید",
                language: "fa",
                theme: "bootstrap",
                dir: "rtl",
                tokenSeparators: [',', ' '],
                multiple: false,
                templateResult:format,
                templateSelection: format,
                escapeMarkup: function (m) { return m; }
            });

        });
    </script>
در اینجا ما اطلاعات را به صورت دستی وارد کرده ایم. شما می‌توانید این اطلاعات را از سمت سرور دریافت نمایید.
در templateResult و templateSelection شما می‌توانید ساختار مورد نظرتان را پیاده سازی کنید. اولی در لیستی از dropdown به عنوان یک option و دومی وقتی که انتخاب می‌شود.
تابع fotmat به صورت زیر می‌باشد.
 function format(state) {
            var $state = $(
                '<span>' + state.text + ' <i class="' + state.id + ' flag"> ' +
                '</i></span>'
            );
            return $state;
        };

لازم به ذکر است کلاس لازم برای ایجاد  پرچم کشوری مثل ایران با استفاده از Semantic-Ui به صورت زیر می‌باشد.
<i class="ir flag"></i>
و در نهایت خروجی آن به شکل زیر می‌باشد

نمایش لیست کشورها به همراه پرچم آنها به صورت dropdown با استفاده از select2
مطالب
آشنایی با mocking frameworks (چارچوب‌های تقلید) - قسمت اول

این مطلب در ادامه‌ی مطالب آزمو‌ن‌های واحد یا unit testing است.
نوشتن آزمون واحد برای کلاس‌هایی که با یک سری از الگوریتم‌ها ، مسایل ریاضی و امثال آن سر و کار دارند، ساده است. عموما این نوع کلاس‌ها وابستگی خارجی آنچنانی ندارند؛ اما در عمل کلاس‌های ما ممکن است وابستگی‌های خارجی بسیاری پیدا کنند؛ برای مثال کار با دیتابیس، اتصال به یک وب سرویس، دریافت فایل از اینترنت، خواندن اطلاعات از انواع فایل‌ها و غیره.
مطابق اصول آزمایشات واحد، یک آزمون واحد خوب باید ایزوله باشد. نباید به مرزهای سیستم‌های دیگر وارد شده و عملکرد سیستم‌های خارج از کلاس را بررسی کند.
این مثال ساده را در نظر بگیرید:
فرض کنید برنامه شما قرار است از یک وب سرویس لیستی از آدرس‌های IP یک کشور خاص را دریافت کند و در یک دیتابیس محلی آن‌ها را ذخیره نماید. به صورت متداول این کلاس باید اتصالی را به وب سرویس گشوده و اطلاعات را دریافت کند و همچنین آن‌ها را خارج از مرز کلاس در یک دیتابیس ثبت کند. نوشتن آزمون واحد برای این کلاس مطابق اصول مربوطه غیر ممکن است. اگر کلاس آزمون واحد آن‌را تهیه نمائید، این آزمون، integration test نام خواهد گرفت زیرا از مرزهای سیستم باید عبور نماید.

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

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

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

public static string GetComparisonText(IComparable a, IComparable b)
{
if (a.CompareTo(b) == 1)
return "a is bigger";
if (a.CompareTo(b) == -1)
return "b is bigger";
return "same";
}
در این مثال چون از IComparable استفاده شده، متد ما از هر نوع داده‌ای جهت مقایسه می‌تواند استفاده کند. تنها موردی که برای آن مهم خواهد بود این است که a راهی را برای مقایسه با b ارائه دهد.

اکنون با توجه به این توضیحات، برای ایزوله کردن ارتباط با دیتابیس و وب سرویس در مثال فوق، می‌توان اینترفیس‌های زیر را تدارک دید:
    public interface IEmailSource
{
IEnumerable<string> GetEmailAddresses();
}

public interface IEmailDataStore
{
void SaveEmailAddresses(IEnumerable<string> emailAddresses);

}
در اینجا استفاده و تعریف اینترفیس‌ها چندین خاصیت را به همراه خواهد داشت :
الف) به این صورت تنها مشخص می‌شود که چه کاری را قصد داریم انجام دهیم و کاری به پیاده سازی آن نداریم.
ب) ساخت کلاس بدون وجود یا دسترسی به یک دیتابیس میسر می‌شود. این مورد خصوصا در یک کار تیمی که قسمت‌های مختلف کار به صورت همزمان در حالت پیشرفت و تهیه است حائز اهمیت می‌شود.
ج) با توجه به اینکه در اینجا به پیاده سازی توجهی نداریم، می‌توان از این اینترفیس‌ها جهت تقلید دنیای واقعی استفاده کنیم. (که در اینجا mocking نام گرفته است)

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

public class MockEmailSource : IEmailSource
{
public IEnumerable<string> EmailAddressesToReturn { get; set; }
public IEnumerable<string> GetEmailAddresses()
{
return EmailAddressesToReturn;
}
}

public class MockEmailDataStore : IEmailDataStore
{
public IEnumerable<string> SavedEmailAddresses { get; set; }
public void SaveEmailAddresses(IEnumerable<string> emailAddresses)
{
SavedEmailAddresses = emailAddresses;
}

}
تا اینجا اولین قدم در مورد ایزوله سازی کلاس‌هایی که به مرز سیستم‌های دیگر وارد می‌شوند، برداشته شد. اما به مرور زمان مدیریت این اینترفیس‌ها و افزودن رفتارهای جدید به کلاس‌های مشتق شده از آن‌ها مشکل می‌شود. به همین جهت تا حد ممکن از پیاده سازی دستی آن‌ها خودداری شده و روش پیشنهادی استفاده از mocking frameworks است.

ادامه دارد ....