مطالب
بررسی تغییرات Blazor 8x - قسمت چهارم - معرفی فرم‌های جدید تعاملی
در قسمت قبل مشاهده کردیم که چگونه می‌توان کل برنامه را به صورت سراسری، تعاملی کرد تا بتوان توسط آن، Blazor Server سنتی را شبیه سازی نمود؛ اما ... آیا واقعا نیاز است چنین کاری را انجام دهیم؟! چون در این صورت از قابلیت‌‌های جدید SSR به همراه Blazor 8x محروم می‌شویم. اگر کل قابلیت‌های تعاملی مورد نیاز ما در حد یک فرم و ارسال اطلاعات آن به سمت سرور است، می‌توان در Blazor 8x هنوز هم در همان حالت SSR قرار گرفت و از فرم‌های جدید تعاملی آن استفاده کرد تا برای پردازش چنین مواردی، نیازی به برقراری اتصال دائم SignalR نباشد. جزئیات نحوه‌ی کار با اینگونه فرم‌ها را در ادامه بررسی می‌کنیم.


امکان تعریف HTML Forms استاندارد در Blazor 8x

فرم‌های استاندارد HTML، پیش از ظهور جاوااسکریپت و SPAها وجود داشتند (دقیقا همان زمانیکه که فقط مفهوم SSR وجود خارجی داشت) و هنوز هم جزء مهمی از اغلب برنامه‌های وب را تشکیل می‌دهند. با ارائه‌ی دات نت 8 و قابلیت server side rendering آن، کامپوننت‌های برنامه، فقط یکبار در سمت سرور رندر شده و HTML ساده‌ی آن‌ها به سمت مرورگر کاربر بازگشت داده می‌شود. در این حالت، فرم‌های استاندارد HTML، امکان دریافت ورودی‌های کاربر و ارسال داده‌های آن‌ها را به سمت سرور میسر می‌کنند (چون دیگر خبری از اتصال دائم SignalR نیست و باید اطلاعات را به همان نحو استاندارد پروتکل HTTP، به سمت سرور Post کرد). در دات نت 8، دو راه‌حل برای کار با فرم‌ها در برنامه‌های Blazor وجود دارد: استفاده از EditForm خود Blazor و یا استفاده از HTML forms استاندارد و ساده، به همان نحوی که بوده و هست.


روش کار با EditForm در برنامه‌های Blazor SSR

البته ما قصد استفاده از فرم‌های ساده‌ی HTML را در اینجا نداریم و ترجیح می‌دهیم که از همان EditForm استفاده کنیم. EditForms در Blazor بسیار مفید بوده و امکان بایند خواص یک مدل را به اجزای مختلف ورودی‌های تعریف شده‌ی در آن میسر می‌کند و همچنین قابلیت‌هایی مانند اعتبارسنجی و امثال آن‌را نیز به همراه دارد (اطلاعات بیشتر). اما چگونه می‌توان از این امکان در برنا‌مه‌های Blazor SSR نیز استفاده کرد؟
برای این منظور، ابتدا مثالی را به صورت زیر تکمیل می‌کنیم (که بر اساس قالب dotnet new blazor --interactivity Server تهیه شده) و سپس توضیحات آن ارائه خواهد شد:

الف) تهیه یک مدل برای تعریف محل‌های مرتبط با یک سفارش در فایل Models/OrderPlace.cs

using System.ComponentModel.DataAnnotations;

namespace Models;

public record OrderPlace
{
    public Address BillingAddress { get; set; } = new();
    public Address ShippingAddress { get; set; } = new();
}

public class Address
{
    [Required] public string Name { get; set; } = default!;
    public string? AddressLine1 { get; set; }
    public string? AddressLine2 { get; set; }
    public string? City { get; set; }
    [Required] public string PostCode { get; set; } = default!;
}

ب) تهیه‌ی یک کامپوننت Editor برای دریافت اطلاعات آدرس فوق در فایل Components\Pages\Chekout\AddressEntry.razor

@inherits Editor<Models.Address>

<div>
    <label>Name</label>
    <InputText @bind-Value="Value.Name"/>
</div>
<div>
    <label>Address 1</label>
    <InputText @bind-Value="Value.AddressLine1"/>
</div>
<div>
    <label>Address 2</label>
    <InputText @bind-Value="Value.AddressLine2"/>
</div>
<div>
    <label>City</label>
    <InputText @bind-Value="Value.City"/>
</div>
<div>
    <label>Post Code</label>
    <InputText @bind-Value="Value.PostCode"/>
</div>

ج) استفاده از مدل و ادیتور فوق در یک EditForm تغییر یافته برای کار با برنامه‌های Blazor SSR در فایل Components\Pages\Chekout\Checkout.razor

@page "/checkout"

@using Models
@if (!_submitted && PlaceModel != null)
{
    <EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
        <DataAnnotationsValidator/>

        <h4>Bill To:</h4>
        <AddressEntry @bind-Value="PlaceModel.BillingAddress"/>

        <h4>Ship To:</h4>
        <AddressEntry @bind-Value="PlaceModel.ShippingAddress"/>

        <button type="submit">Submit</button>
        <ValidationSummary/>
    </EditForm>
}

@if (_submitted && PlaceModel != null)
{
    <div>
        <h2>Order Summary</h2>

        <h3>Shipping To:</h3>
        <dl>
            <dt>Name</dt>
            <dd>@PlaceModel.BillingAddress.Name</dd>
            <dt>Address 1</dt>
            <dd>@PlaceModel.BillingAddress.AddressLine1</dd>
            <dt>Address 2</dt>
            <dd>@PlaceModel.BillingAddress.AddressLine2</dd>
            <dt>City</dt>
            <dd>@PlaceModel.BillingAddress.City</dd>
            <dt>Post Code</dt>
            <dd>@PlaceModel.BillingAddress.PostCode</dd>
        </dl>
    </div>
}

@code {
    bool _submitted;

    [SupplyParameterFromForm]
    public OrderPlace? PlaceModel { get; set; }

    protected override void OnInitialized()
    {
        PlaceModel ??= GetOrderPlace();
    }

    private void SubmitOrder()
    {
        _submitted = true;
    }

    private static OrderPlace GetOrderPlace() =>
        new()
        {
            BillingAddress = new Address
                             {
                                 PostCode = "12345",
                                 Name = "Test 1",
                             },
            ShippingAddress = new Address
                              {
                                  PostCode = "67890",
                                  Name = "Test 2",
                              },
        };

}
توضیحات:
باید بخاطر داشت که این فرم بر اساس حالت Server Side Rendering در اختیار مرورگر کاربر قرار می‌گیرد. یعنی برای بار اول، یک HTML خالص، در سمت سرور بر اساس اطلاعات آن تهیه شده و بازگشت داده می‌شود و زمانیکه به کاربر نمایش داده شد، دیگر برخلاف Blazor Server پیشین، اتصال SignalR ای وجود ندارد تا قابلیت‌های تعاملی آن‌را مدیریت کند. در این حالت اگر به view source صفحه‌ی جاری رجوع کنیم، چنین خروجی قابل مشاهده‌است:
<form method="post">
   <input type="hidden" name="_handler" value="checkout" />
   <input type="hidden" name="__RequestVerificationToken" value="CfDxxx" />
.
.
.
   <button type="submit">Submit</button>
</form>
یعنی زمانیکه این فرم به سمت سرور ارسال می‌شود، همان HTTP POST استاندارد رخ می‌دهد و برای اینکار، نیازی به اتصال وب‌سوکت SignalR ندارد.
این EditForm تعریف شده، دو قسمت اضافه‌تر را نسبت به EditFormهای نگارش‌های قبلی Blazor دارد:
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
در اینجا نوع HTTP Method ارسال فرم، مشخص شده و همچنین یک FormName نیز تعریف شده‌است. علت اینجا است که Blazor باید بتواند اطلاعات POST شده و دریافتی در سمت سرور را به کامپوننت متناظری نگاشت کند؛ به همین جهت این نامگذاری، ضروری است.
همانطور که در نحوه‌ی تعریف فرم HTML ای فوق مشخص است، فیلد مخفی handler_، کار متمایز ساختن این فرم را به عهده داشته و از مقدار آن در سمت سرور جهت یافتن کامپوننت متناظر، استفاده خواهد شد.

همچنین برای دریافت و پردازش این اطلاعات در سمت سرور، تنها کافی است خاصیت مرتبط با آن‌را با ویژگی SupplyParameterFromForm مزین کنیم:
[SupplyParameterFromForm]
public OrderPlace? PlaceModel { get; set; }

جریان کاری این فرم به صورت خلاصه به نحو زیر است (که در آن متد OnInitialized دوبار فراخوانی می‌شود و باید به آن دقت داشت):
- در بار اول نمایش این صفحه (با فراخوانی مسیر /checkout در مرورگر)، متد OnInitialized فراخوانی شده و در آن، مقدار شیء PlaceModel نال است.
- بنابراین به متد GetOrderPlace مراجعه کرده و اطلاعاتی را دریافت می‌کند؛ برای مثال، این اطلاعات را از سرویسی می‌خواند.
- پس از پایان هر روال رخدادگردانی در Blazor، در پشت صحنه به صورت خودکار، متد تغییر حالت جاری کامپوننت (متد StateHasChanged) هم فراخوانی می‌شود. این فراخوانی خودکار، باعث رندر مجدد UI آن بر اساس اطلاعات جدید خواهد شد. یعنی قسمت‌های نمایش فرم و نمایش اطلاعات ارسالی، یکبار ارزیابی شده و در صورت برقراری شرط‌ها، نمایش داده می‌شوند.
- در ادامه، کاربر فرم را پر کرده و به سمت سرور POST می‌کند.
- پیش از هر رخ‌دادی، خواص شیء PlaceModel به علت مزین بودن به ویژگی SupplyParameterFromForm، بر اساس اطلاعات ارسالی به سرور، مقدار دهی می‌شوند.
- سپس متد OnInitialized فراخوانی شده و چون اینبار مقدار PlaceModel نال نیست، به متد GetOrderPlace جهت دریافت مقادیر ابتدایی خود مراجعه نمی‌کند. سطر تعریف شده‌ی در متد OnInitialized فقط زمانی سبب مقدار دهی شیء PlaceModel می‌شود که مقدار این شیء، نال باشد (یعنی فقط در اولین بار نمایش صفحه)؛ اما اگر این مقدار توسط پارامتر مزین شده‌ی به SupplyParameterFromForm به علت ارسال داده‌های فرم به سرور، مقدار دهی شده باشد، دیگر به منبع داده‌ی ابتدایی رجوع نمی‌کند.
- چون متد رخ‌دادگردان OnInitialized فراخوانی شده، پس از پایان آن (و فراخوانی خودکار متد StateHasChanged در انتهای آن)، یکبار دیگر کار رندر UI فرم جاری بر اساس اطلاعات جدید، انجام خواهد شد.
- اکنون است که پس از طی این رخ‌دادها، متد رویدادگردان SubmitOrder فراخوانی می‌شود. یعنی زمانیکه این متد فراخوانی می‌شود، شیء PlaceModel بر اساس اطلاعات رسیده‌ی از طرف کاربر، مقدار دهی شده و آماده‌ی استفاده است (برای مثال آماده‌ی ذخیره سازی در بانک اطلاعاتی؛ با فراخوانی سرویسی در اینجا).
- پس از پایان فراخوانی متد رویدادگردان SubmitOrder، به علت تغییر حالت کامپوننت (و فراخوانی خودکار متد StateHasChanged در انتهای آن)، یکبار دیگر نیز کار رندر UI فرم جاری بر اساس اطلاعات جدید انجام خواهد شد. یعنی اینبار قسمت Order Summary نمایش داده می‌شود.


مدیریت تداخل نام‌های HTML Forms در Blazor 8x SSR

تمام فرم‌هایی که به این صورت در برنامه‌های Blazor SSR مدیریت می‌شوند، باید دارای نام منحصربفردی که توسط خاصیت FormName مشخص می‌شود، باشند. برای جلوگیری از این تداخل نام‌ها، کامپوننت جدیدی به نام FormMappingScope معرفی شده‌است که نمونه‌ای از آن‌را در فایل فرضی Components\Pages\Chekout\CheckoutForm.razor تعریف شده‌ی به صورت زیر مشاهده می‌کنید:

@page "/checkout"

<FormMappingScope Name="store-checkout">
    <CheckoutForm />
</FormMappingScope>
در اینجا ابتدا ویژگی page@ کامپوننت CheckoutForm را حذف کرده و آن‌را تبدیل به یک کامپوننت معمولی بدون قابلیت مسیریابی کرده‌ایم. سپس آن‌را توسط کامپوننت FormMappingScope در صفحه‌ای دیگر معرفی و محصور می‌کنیم.
اکنون اگر برنامه را اجرا کرده و خروجی HTML آن‌را بررسی کنیم، به فرم زیر خواهیم رسید:
<form method="post">
   <input type="hidden" name="_handler" value="[store-checkout]checkout" />
   <input type="hidden" name="__RequestVerificationToken" value="CfDxxxxx" />
.
.
.
   <button type="submit">Submit</button>
</form>
همانطور که ملاحظه می‌کنید، اینبار مقدار فیلد مخفی handler_ که کار متمایز ساختن این فرم را به عهده دارد و از آن در سمت سرور جهت یافتن کامپوننت متناظری استفاده می‌شود، با حالتی‌که از کامپوننت FormMappingScope استفاده نشده بود، متفاوت است و نام FormMappingScope را در ابتدای خود به همراه دارد تا به این نحو، از تداخل احتمالی نام‌های فرم‌ها جلوگیری شود.

یک نکته: اگر به تگ‌های فرم HTML ای فوق دقت کنید، به همراه یک anti-forgery token نیز هست که کار تولید و مدیریت آن، به صورت خودکار صورت می‌گیرد و میان‌افزاری نیز برای آن طراحی شده که در فایل Program.cs برنامه، به صورت app.UseAntiforgery بکارگرفته شده‌است.


یک نکته: در Blazor 8x SSR می‌توان بجای EditForm، از همان HTML form متداول هم استفاده کرد

اگر بخواهیم بجای استفاده از EditForm، از فرم‌های استاندارد HTML هم در حالت SSR استفاده کنیم، این کار میسر بوده و روش کار به صورت زیر است:
<form method="post" @onsubmit="SaveData" @formname="MyFormName">
    <AntiforgeryToken />

    <InputText @bind-Value="Name" />

    <button>Submit</button>
</form>
در اینجا ذکر دایرکتیوهای onsubmit@ و formname@ را (شبیه به خواص و رویدادگردان‌های مشابهی در EditForm) به همراه ذکر صریح کامپوننت AntiforgeryToken، مشاهده می‌کنید. در حین استفاده از EditForm، نیازی به درج این کامپوننت نیست و به صورت خودکار اضافه می‌شود.


پردازش فرم‌های GET در Blazor 8x

در حالتی‌که از فرم‌های استاندارد HTML ای استفاده می‌شود، ممکن است method فرم، بجای post، حالت get باشد که نتایج آن به صورت کوئری استرینگ در نوار آدرس مرورگر ظاهر می‌شوند؛ مانند جستجوی گوگل که اشخاص می‌توانند کوئری استرینگ و لینک نهایی را به اشتراک بگذارند. روش پردازش یک چنین فرم‌هایی به صورت زیر است:
@page "/"

<form method="GET">
    <input type="text" name="q"/>
    <button type="submit">Search</button>
</form>


@code {
    [SupplyParameterFromQuery(Name="q")]
    public string SearchTerm { get; set; }
    
    protected override async Task OnInitializedAsync()
    {
       // do something with the search term
    }
}
در اینجا از ویژگی SupplyParameterFromQuery برای دریافت کوئری استرینگ استفاده شده و چون نام پارامتر تعریف شده با نام input فرم یکی نیست، این نام به صورت صریحی توسط خاصیت Name آن مشخص شده‌است.


یک ابتکار! تعاملی کردن قسمتی از صفحه بدون فعالسازی کامل Blazor Server و یا Blazor WASM کامل

این دکمه‌ی قرار گرفته‌ی در یک صفحه‌ی SSR را ملاحظه کنید:
<button class="nav-link border-0" @onclick="BeginSignOut">Log out</button>
در اینجا می‌خواهیم، اگر کاربری بر روی آن کلیک کرد، روال رویدادگردان منتسب به onclick اجرا شود. اما ... اگر در این حالت برنامه را اجرا کرده و بر روی دکمه‌ی Log out کلیک کنیم، هیچ اتفاقی رخ نمی‌دهد! یعنی روال رویدادگران BeginSignOut اصلا اجرا نمی‌شود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیت‌های تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند. برای رفع این مشکل یا می‌توان این قسمت از صفحه را کاملا تعاملی کرد که روش انجام آن‌را در قسمت‌های بعدی با جزئیات کاملی بررسی می‌کنیم و یا ... می‌توان این دکمه را داخل یک فرم جدید تعاملی به صورت زیر محصور کرد:
<EditForm Context="ctx" FormName="LogoutForm" method="post" Model="@Foo" OnValidSubmit="BeginSignOut">
     <button type="submit" class="nav-link border-0">Log out</button>
</EditForm>

@code{
    [SupplyParameterFromForm(Name = "LogoutForm")]
    public string? Foo {  get; set; }

    protected override void OnInitialized() => Foo = "";

    async Task BeginSignOut()
    {
        // TODO: SignOutAsync();
        // TODO: NavigateTo("/authentication/logout");
    }
}
در این حالت چون این فرم، از نوع فرم‌های جدید تعاملی است، برای پردازش آن نیازی به اتصال دائم SignalR و یا فعالسازی یک وب‌اسمبلی نیست. پردازش آن بر اساس استاندارد HTTP Post و فرم‌های آن، صورت گرفته و به این ترتیب می‌توان عملکرد onclick@ کاملا تعاملی را با یک فرم تعاملی جدید، شبیه سازی کرد.


یک نکته: می‌توان حالت post-back مانند فرم‌های تعاملی Blazor 8x را تغییر داد.

به همراه ویژگی‌های جدید مرتبط با صفحات SSR، ویژگی هدایت بهبودیافته هم وجود دارد که جزئیات بیشتر آن‌را در قسمت‌های بعدی این سری بررسی می‌کنیم. برای نمونه اگر مثال این قسمت را اجرا کنید، فرم آن به همراه یک post-back مانند به سمت سرور است که کاملا قابل احساس است؛ این رفتار هرچند استاندارد است، اما بی‌شباهت به برنامه‌های MVC ، Razor pages و یا وب‌فرم‌ها نیست و با فرم‌های بی‌صدا و سریع نگارش‌های قبلی Blazor متفاوت است. در Blazor8x می‌توان این نوع ارسال اطلاعات را Ajax ای هم کرد که به آن enhanced navigation می‌گویند. برای اینکار فقط کافی است ویژگی Enhance را به تگ EditForm اضافه کرد و یا ویژگی جدید data-enhance را به تگ‌های فرم‌های استاندارد HTML ای افزود. پس از آن اگر برنامه را اجرا کنیم، دیگر یک post-back استاندارد وب‌فرم‌ها مشاهده نمی‌شود و رفتار این صفحه بسیار سریع، نرم و روان خواهد بود.
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout" Enhance>
در اینجا تنها تغییری که حاصل شده، اضافه شدن ویژگی Enhance به المان EditForm است. این ویژگی به صورت پیش‌فرض غیرفعال است که جزئیات بیشتر آن‌را در قسمت‌های بعدی بررسی خواهیم کرد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: Blazor8x-Server-Normal.zip
مطالب
فارسی کردن گرید تلریک در برنامه های ویندوزی

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

1- یک پروژه جدید ویندوزی در visual studio  ایجاد میکنیم

2- اضافه کردن یک radGridView به فرم و خاصیت Dock آن را به حالت Fill و خاصیت RightToLeft را Yes قرار میدهیم :

3- حال برای اینکه یک سری اطلاعاتی داخل این گرید نمایش بدهیم یک کلاس در همان فرم درست میکنیم مشابه کد ذیل :
   public List<MyCustomData> GetData() {

            List<MyCustomData> myList = new List<MyCustomData>();
            for (int i = 1; i < 11; i++)
            {
                myList.Add(new MyCustomData() {
                     ID = i,
                     Name = "Name Family " + i.ToString(),
                      Age = 29
                });
            }

            return myList;
        }

       public class MyCustomData
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public bool Sex { get; set; }
            
        }

4 - حال برای اینکه این اطلاعات را در گرید نمایش دهیم کد زیر را در بخش Load_Form1 مینویسیم :
 private void Form1_Load(object sender, EventArgs e)
        {
            radGridView1.DataSource = GetData();
        }
5 - در این صورت اگر برنامه را اجرا کنیم بدین صورت در گرید نمایش داده میشود که هیچ کدام از موارد فارسی نیستند:
 
6 - برای اینکه این موارد فارسی شوند نیاز به یک کلاس یا Provider داریم که این عمل ترجمه را انجام دهد که حتی در سایت خود تلریک در بخش مربوطه نیز ارائه شده است. بنده این کلاس را کپی کرده و تمامی ترجمه‌های آنها را نیز نوشتم ( اگر در ترجمه ایرادی بود به بزرگی خودتان بخشیده و تصحیح نمائید . ) که کد آن را در زیر میتوانید در اختیار داشته باشید:
class PersianRadGridLocalizationProvider : RadGridLocalizationProvider
    {
        public override string GetLocalizedString(string id)
        {
            switch (id)
            {
                case RadGridStringId.FilterFunctionBetween: return "بین"; //Between  
                case RadGridStringId.FilterOperatorBetween: return "بین";
                case RadGridStringId.FilterFunctionContains: return "حاوی";
                case RadGridStringId.FilterOperatorContains: return "حاوی";
                case RadGridStringId.FilterFunctionDoesNotContain: return "شامل نشود"; //Does not contain
                case RadGridStringId.FilterOperatorDoesNotContain: return "شامل نشود";
                case RadGridStringId.FilterFunctionEndsWith: return "پایان پذیرد با"; //Ends with 
                case RadGridStringId.FilterOperatorEndsWith: return "پایان پذیرد با";
                case RadGridStringId.FilterFunctionEqualTo: return "برابر با"; //Equals     
                case RadGridStringId.FilterOperatorEqualTo: return "برابر با";
                case RadGridStringId.FilterFunctionGreaterThan: return "بزرگتر از"; //Greater than
                case RadGridStringId.FilterOperatorGreaterThan: return "بزرگتر از";
                case RadGridStringId.FilterFunctionGreaterThanOrEqualTo: return "بزرگتر یا مساوی با"; //Greater than or equal to 
                case RadGridStringId.FilterOperatorGreaterThanOrEqualTo: return "بزرگتر یا مساوی با";
                case RadGridStringId.FilterFunctionIsEmpty: return "خالی باشد"; //Is empty
                case RadGridStringId.FilterOperatorIsEmpty: return "خالی باشد";
                case RadGridStringId.FilterFunctionIsNull: return "تهی باشد"; //Is null
                case RadGridStringId.FilterOperatorIsNull: return "تهی باشد";
                case RadGridStringId.FilterFunctionLessThan: return "کمتر از"; //Less than 
                case RadGridStringId.FilterOperatorLessThan: return "کمتر از";
                case RadGridStringId.FilterFunctionLessThanOrEqualTo: return "کمتر یا مساوی با"; //Less than or equal to
                case RadGridStringId.FilterOperatorLessThanOrEqualTo: return "کمتر یا مساوی با";
                case RadGridStringId.FilterFunctionNoFilter: return "بدون شرط"; //No filter 
                case RadGridStringId.FilterOperatorNoFilter: return "بدون شرط";
                case RadGridStringId.FilterFunctionNotBetween: return "نباشد بین"; //Not between
                case RadGridStringId.FilterOperatorNotBetween: return "نباشد بین"; //Operator
                case RadGridStringId.FilterFunctionNotEqualTo: return "برابر نباشد با"; //Not equal to 
                case RadGridStringId.FilterOperatorNotEqualTo: return "برابر نباشد با";
                case RadGridStringId.FilterFunctionNotIsEmpty: return "خالی نباشد"; //Is not empty          
                case RadGridStringId.FilterFunctionNotIsNull: return "خالی نباشد"; //Is not null         
                case RadGridStringId.FilterFunctionStartsWith: return "شروع شود با"; //Starts with          
                case RadGridStringId.FilterFunctionCustom: return "شرط دلخواه"; //Custom          
                case RadGridStringId.CustomFilterMenuItem: return "شرط دلخواه منو"; //Custom         
                case RadGridStringId.CustomFilterDialogCaption: return "انتخاب شرط دلخواه"; //RadGridView Custom Filter Dialog         
                case RadGridStringId.CustomFilterDialogLabel: return ":نشان دادن سطرهایی که"; //Show rows where:         
                case RadGridStringId.CustomFilterDialogRbAnd: return "و"; //And         
                case RadGridStringId.CustomFilterDialogRbOr: return "یا"; //Or          
                case RadGridStringId.CustomFilterDialogBtnOk: return "تایید"; //OK         
                case RadGridStringId.CustomFilterDialogBtnCancel: return "انصراف"; //Cancel 
                case RadGridStringId.AddNewRowString: return "برای افزودن سطر جدید اینجا کلیک کنید";
                case RadGridStringId.ClearValueMenuItem: return "پاک کردن مقدار سلول";
                case RadGridStringId.DeleteRowMenuItem: return "حذف سطر"; //Delete Row          
                case RadGridStringId.SortAscendingMenuItem: return "مرتب سازی صعودی"; //Sort Ascending         
                case RadGridStringId.SortDescendingMenuItem: return "مرتب سازی نزولی"; //Sort Descending         
                case RadGridStringId.ClearSortingMenuItem: return "حذف مرتب سازی"; //Clear Sorting         
                case RadGridStringId.ConditionalFormattingMenuItem: return "قالب بندی مشروط"; //Conditional Formatting         
                case RadGridStringId.GroupByThisColumnMenuItem: return "گروهبندی بر حسب این ستون"; //Group by this column         
                case RadGridStringId.UngroupThisColumn: return "حذف این ستون از گروهبندی "; //Ungroup this column         
                case RadGridStringId.ColumnChooserMenuItem: return "انتخابگر ستون"; //Column Chooser         
                case RadGridStringId.HideMenuItem: return "مخفی کردن ستون"; //Hide         
                case RadGridStringId.UnpinMenuItem: return "حالت پیش فرض"; //Unpin         
                case RadGridStringId.PinMenuItem: return "حالت ستون"; //Pin       
                case RadGridStringId.PinAtLeftMenuItem: return "چسپیدن به سمت چپ";
                case RadGridStringId.PinAtRightMenuItem: return "چسپیدن به سمت راست";
                case RadGridStringId.PinAtTopMenuItem: return "چسپیدن به بالا";
                case RadGridStringId.PinAtBottomMenuItem: return "چسپیدن به پایین";
                case RadGridStringId.BestFitMenuItem: return "اندازه بهینه ستون"; //Best Fit         
                case RadGridStringId.PasteMenuItem: return "چسپاندن"; //Paste         
                case RadGridStringId.EditMenuItem: return "ویرایش"; //Edit         
                case RadGridStringId.CopyMenuItem: return "کپی"; //Copy         
                case RadGridStringId.ConditionalFormattingCaption: return "قالب بندی مشروط"; //Custom Formatting Condition Editor   
                case RadGridStringId.ConditionalFormattingLblColumn: return "قالب بندی سلولهایی با شرط:"; //Column:         
                case RadGridStringId.ConditionalFormattingLblName: return "نام شرط:"; //Name:          
                case RadGridStringId.ConditionalFormattingLblType: return "مقدار سلول:"; //Type:         
                case RadGridStringId.ConditionalFormattingLblValue1: return "مقدار اول:"; //Value 1:         
                case RadGridStringId.ConditionalFormattingLblValue2: return "مقدار دوم:"; //Value 2:         
                case RadGridStringId.ConditionalFormattingGrpConditions: return "شرایط"; //Conditions         
                case RadGridStringId.ConditionalFormattingGrpProperties: return "مشخصات"; //Properties         
                case RadGridStringId.ConditionalFormattingChkApplyToRow: return "اعمال این شرط به کل سطر"; //Apply to row         
                case RadGridStringId.ConditionalFormattingBtnAdd: return "افزودن شرایط"; //Add         
                case RadGridStringId.ConditionalFormattingBtnRemove: return "حذف شرایط انتخابی"; //Remove         
                case RadGridStringId.ConditionalFormattingBtnOK: return "تایید"; //OK         
                case RadGridStringId.ConditionalFormattingBtnCancel: return "انصراف"; //Cancel         
                case RadGridStringId.ConditionalFormattingBtnApply: return "اعمال قالب بندی"; //Apply         
                case RadGridStringId.ColumnChooserFormCaption: return "انتخاب ستون ها"; //Column Chooser         
                case RadGridStringId.ColumnChooserFormMessage: return "برای حذف یکی از ستونها، آن ستون را به اینجا بکشید";//"Drag a column header from the grid here to remove it from the current view.";
                case RadGridStringId.CompositeFilterFormErrorCaption: return "خطا";
                case RadGridStringId.ConditionalFormattingChooseOne: return "[یکی را انتخاب کنید]";
                case RadGridStringId.ConditionalFormattingContains: return "[حاوی [مقدار اول";
                case RadGridStringId.ConditionalFormattingDoesNotContain: return "حاوی [مقدار اول] نباشد";
                case RadGridStringId.ConditionalFormattingEndsWith: return "با [مقدار اول] پایان یابد";
                case RadGridStringId.ConditionalFormattingEqualsTo: return "[برابر با [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsBetween: return "بین [مقدار اول] و [مقدار دوم] باشد";
                case RadGridStringId.ConditionalFormattingIsGreaterThan: return "[بزرگتر از [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsGreaterThanOrEqual: return "[بزرگتر یا مساوی با [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsLessThan: return "کوچکتر از [مقدار اول]";
                case RadGridStringId.ConditionalFormattingIsLessThanOrEqual: return "کوچکتر یا مساوی با [مقدار اول]";
                case RadGridStringId.ConditionalFormattingIsNotBetween: return "بین [مقدار اول] و [مقدار دوم] نباشد";
                case RadGridStringId.ConditionalFormattingIsNotEqualTo: return "برابر با [مقدار اول] نباشد";
                case RadGridStringId.ConditionalFormattingRuleAppliesOn: return "اعمال شرایط روی:";
                case RadGridStringId.ConditionalFormattingStartsWith: return "با [مقدار اول] شروع می‌شود";
                case RadGridStringId.CustomFilterDialogCheckBoxNot: return "با این شرایط نباشد";
                case RadGridStringId.CustomFilterDialogFalse: return "False";
                case RadGridStringId.CustomFilterDialogTrue: return "True";
                case RadGridStringId.FilterCompositeNotOperator: return "نباشد";
                case RadGridStringId.FilterLogicalOperatorAnd: return "و";
                case RadGridStringId.FilterLogicalOperatorOr: return "یا";
                case RadGridStringId.FilterMenuAvailableFilters: return "فیلتر شده";
                case RadGridStringId.FilterMenuButtonCancel: return "انصراف";
                case RadGridStringId.FilterMenuButtonOK: return "تایید";
                case RadGridStringId.FilterMenuClearFilters: return "پاک کردن فیلتر";
                case RadGridStringId.FilterMenuSearchBoxText: return "جستجو...";
                case RadGridStringId.FilterMenuSelectionAll: return "همه";
                //case RadGridStringId.FilterMenuSelectionAllSearched: return "نتیجه همه جستجو";
                case RadGridStringId.FilterMenuSelectionNotNull: return "خالی نباشد";
                case RadGridStringId.FilterMenuSelectionNull: return "خالی باشد";
                case RadGridStringId.FilterOperatorCustom: return "دلخواه";
                case RadGridStringId.FilterOperatorIsLike: return "مانند";
                case RadGridStringId.FilterOperatorNotIsContainedIn: return "نباشد در";
                case RadGridStringId.FilterOperatorNotIsEmpty: return "خالی نباشد";
                case RadGridStringId.FilterOperatorNotIsLike: return "نباشد شبیه";
                case RadGridStringId.FilterOperatorNotIsNull: return "خالی نباشد";
                case RadGridStringId.FilterOperatorStartsWith: return "شروع شود با";
                case RadGridStringId.GroupingPanelDefaultMessage: return "برای گروهبندی ستونها، ستونی را به اینجا بکشید";
                case RadGridStringId.GroupingPanelHeader: return ":گروهبندی بر حسب";
                case RadGridStringId.NoDataText: return "داده ای برای نمایش وجود ندارد";
                case RadGridStringId.UnpinRowMenuItem: return "حالت پیش فرض";
                default:
                    return base.GetLocalizedString(id);
            }
        }
    }

7 - حال اگر برنامه را اجرا کنید باز موارد انگلیسی گرید تلریک فارسی نمی‌شوند و باید در کلاس Program.cs پروژه این یک خط کد را هم اضافه نمائید.
//using Telerik.WinControls.UI.Localization;

 RadGridLocalizationProvider.CurrentProvider = new PersianRadGridLocalizationProvider();

8 - حال اگر برنامه را اجرا نمایید تمامی موارد را فارسی مشاهده خواهید نمود ( شکل ذیل )

لطفا ما را از نظرات سازنده خود بی نصیب نفرمائید. با تشکر


نظرات مطالب
یکسان سازی "ی" و "ک" دریافتی در حین استفاده از WCF RIA Services
سلام مهندس نصیری
خسته نباشید و دستمریزاد
اما اینکار که شما گفتین هم یه مشکل جدید دیگه را به وجود میاره و اون اینه که « ک » در مرتب سازی بعد از « ی » قرار میگیره! « ک » در جای خودش قرار میگیره یعنی بعد از حرف «ق».
این میتونه خیلی از کاربران را در خیلی از برنامه‌ها دچار مشکل کنه. یعنی خیلی وقتها (بخصوص کارمندان اداری) کار سرچ را توسط برنامه انجام نمیدن و در یک دیتاگرید اطلاعات لود شده را می‌بینن و با چشم دنبالش میگردن (من اینو در خیلی از برنامه‌ها و خیلی از ادارات و سازمان‌ها دیده‌م، حتی در خیلی از برنامه‌های حسابداری و فروش)
حالا فرض کنید کاربر داره دنبال یک نام و یا یک محصول میگرده که با حرف « ک » شروع شده ولی هرچی لیست را حول و حوش « ق » و « م » بالا و پایین می‌کنه چیزی نمیبینه و باید بدونه که لازمه به انتهای لیست بره تا اونایی را با حرف « ک » شروع شده‌ن بتونه پیدا کنه!
مشکل وقتی بدتر میشه که بدونیم این شکل از مرتب سازی فقط مورد حرف اول نیست و هرجایی از کلمه که حرف « ک » وجود داشته باشه، اگر کلمات دیری شبیه اون هم وجود داشته باشن، اون کلمه که « ک » داره به انتهای لیست میره (در محدودۀ همون کلمات) و این خودش میتونه ریپورت‌های خروجی برنامه را اگر بر حسب اون فیلد مرتب شده باشن دچار اشکال کنه و اگه پرینت هم لازم باشه مشکل کم‌کم بزرگتر و پیچیده‌تر هم میشه.

خلاصه اینکه همونطور که خودتون توی مطلب قبلی هم نوشتین باید راهکاری اندیشیده بشه که سیستم‌ها (بخصوص پایگاه‌های داده) این حروف را یکی در نظر بگیرن، ولی تا حصول این مقصود که چندان هم نزدیک بنظر نمیرسه، شخصاً فکر میکنم بجای تبدیل تمام « ک » ها (کاف عربی) به « ک » (کاف فارسی) بهتره که همۀ اونها را به « ک » عربی تبدیل کنیم و برای نمایش اوناه از فونتهای فارسی که « ک » را بصورت « ک » نشون میدن استفاده کنیم ولی در مورد «ی» یای فارسی اولویت داره.

نظرات و پیشنهادات دیگر شما و باقی دوستان را با اشتیاق دنبال میکنم.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
AngularJS یک فریم ورک جاوا اسکریپتی، برای ساخت برنامه‌های تک صفحه‌ای سمت کاربر، توسط HTML ،CSS و جاوا اسکریپت است. این فریم ورک، حاوی اجزایی برای data binding، طراحی ماژولار، کار با سرویس‌های سمت سرور وب و امثال آن است. Angular 2 بازنویسی کامل نگارش 1 آن است و اهداف زیر را دنبال می‌کند:
- سرعت بالاتر بارگذاری و اجرای کدها
- استفاده از آخرین فناوری‌های وب مانند ES 6 و وب کامپوننت‌ها با پشتیبانی تا IE 9.
- سطح API آن با طراحی جدید آن، به شدت کاهش یافته و خلاصه شده‌است. همین امر یادگیری آن‌را نیز ساده‌تر می‌کند.

برای یادگیری Angular 2 نیازی به فراگیری Angular 1 نیست. در اینجا با آشنایی با TypeScript، به این نتیجه خواهید رسید که برنامه‌های Angular 2 چیزی بیشتر از یک مثال عملی TypeScript نیستند. زبان TypeScript، زبان اول و توصیه شده‌ی کار با Angular 2 است و مزیت آن دسترسی به تمام قابلیت‌های ES 6 است؛ با این تفاوت که کامپایلر TypeScript قادر است آن‌ها را به ES 5 یا نگارش فعلی جاوا اسکریپت که توسط تمام مرورگرها پشتیبانی می‌شود، ترجمه و تبدیل کند. به این نحو به یک طراحی شیءگرا، مدرن و با قابلیت نگهداری بالا خواهید رسید که با تمام مرورگرهای جدید نیز سازگار است.
بنابراین پیشنیاز‌های اصلی کار با Angular 2، فراگیری ES 6 و TypeScript هستند که دو سری ویژه و مختص به آن‌ها در سایت جاری تهیه شده‌اند:
«مبانی ES 6»
«مبانی TypeScript»

در این قسمت قصد داریم پیشنیازهای دریافت و نصب اجزاء و وابستگی‌های AngularJS 2 را به همراه TypeScript، در ویژوال استودیو 2015 بررسی کنیم. البته استفاده از ویژوال استودیو در اینجا ضروری نیست و اساسا AngularJS وابستگی به ویژوال استودیو ندارد. اگر به دنبال پشتیبانی بهتری از TypeScript هستید، VSCode رایگان، سورس باز و چند سکویی، گزینه‌ی بسیار بهتری است نسبت به ویژوال استودیوی کامل. البته این مورد تعجبی هم ندارد؛ از این جهت که VSCode با TypeScript نوشته شده‌است.


اهمیت آشنایی با npm

اگر در طی سال‌های اخیر با ویژوال استودیو کار کرده باشید، به طور قطع با NuGet نیز آشنا هستید. NuGet به عنوان یک package manager دات نتی شناخته می‌شود و کار آن دریافت وابستگی‌های یک پروژه، از مخزنی مشخص و نصب و به روز رسانی خودکار آن‌ها است. اما این روزها خارج از محیط توسعه‌ی مایکروسافت، مدیرهای بسته‌های دیگری مانند Bower نیز برای نصب و به روز رسانی بسته‌های front end مانند کتابخانه‌های CSS ایی و جاوا اسکریپتی، بسیار رواج پیدا کرده‌اند. Bower یکی از ابزارهای NodeJS است که توسط NPM یا NodeJS Package Manager نصب می‌شود. به این معنا که استفاده از Bower به معنای استفاده از NPM است. پیش از این از NPM صرفا جهت نصب بسته‌های مرتبط با NodeJS استفاده می‌شد، اما در طی یکسال اخیر، استفاده از NPM نیز برای مدیریت بسته‌های front end رواج پیدا کرده‌است و در صدر مدیر بسته‌های نصب کتابخانه‌های front end قرار دارد. همچنین در این حالت شما تنها نیاز به یک ابزار و یک فایل تنظیمات آن خواهید داشت، بجای استفاده از چندین ابزار و چندین فایل تنظیمات جداگانه. به علاوه این روزها انجام کارهای جدی جاوا اسکریپتی بدون دانش NPM دیگر میسر نیست. بهترین ابزارها، کامپایلرها، فشرده کننده‌های front end و امثال آن‌ها، تحت NodeJS اجرا می‌شوند. برای کار با AngularJS 2.0 و دریافت وابستگی‌های آن نیز استفاده از npm، روش پیش فرض و توصیه شده‌است.


کار با npm جهت دریافت وابستگی‌های کتابخانه‌های front end

برای کار با npm یا می‌توان از دستورات خط فرمان آن به صورت مستقیم استفاده کرد و یا از امکانات یکپارچگی آن با ویژوال استودیو نیز بهره گرفت که ما در ادامه از این روش استفاده خواهیم کرد. البته توانمندی‌های خط فرمان npm فراتر است از امکانات توکار VS.NET، اما در اینجا نیازی به آن‌ها نیست و هدف صرفا دریافت و به روز رسانی وابستگی‌های مرتبط است.
ویژوال استودیوی 2015 به همراه ابزارهای NodeJS ارائه می‌شود. اما مشکل اینجا است که این ابزارها هم اکنون قدیمی شده‌اند و تطابقی با آخرین نگارش ابزارهای NodeJS ندارند. برای نمونه حین نصب وابستگی‌‌های AngularJS 2.0 که یکی از آن‌ها RxJS است، یک چنین خروجی را در پنجره‌ی output ویژوال استودیو مشاهده می‌کنید:
 npm WARN engine rxjs@5.0.0-beta.6: wanted: {"npm":">=2.0.0"} (current: {"node":"v0.10.31","npm":"1.4.9"})
به این معنا که نگارش‌های اخیر RxJS نیاز به npm با نگارشی بیشتر از 2 را دارند؛ اما نگارش npm پیش فرض ویژوال استودیو 1.4.9 است (البته باید دقت داشت که من به روز رسانی دوم VS 2015 را هم نصب کرده‌ام). برای رهایی از این مشکل، تنها کافی است تا به ویژوال استودیو اعلام کنیم از نسخه‌ی اصلی خط فرمان npm، بجای نسخه‌ی پیش فرض خودش استفاده کند:


همانطور که در تصویر نیز ملاحظه می‌کنید، به مسیر Tools->Options->Projects and Solutions->External Web Tools مراجعه کرده و متغیر محیطی PATH ویندوز را به ابتدای لیست منتقل کنید. به این صورت ابزارهای به روز شده‌ی خط فرمان، مقدم خواهند شد بر ابزارهای قدیمی به همراه نگارش فعلی ویژوال استودیو.

البته فرض بر این است که آخرین نگارش nodejs را از آدرس https://nodejs.org/en دریافت و نصب کرده‌اید. با نصب آن، برنامه npm، در خط فرمان ویندوز به صورت مستقیم قابل استفاده خواهد بود؛ از این جهت که مسیر آن به PATH ویندوز اضافه شده‌است:



افزودن فایل project.json به پروژه

تا اینجا فرض بر این است که nodejs را از سایت آن دریافت و نصب کرده‌اید. همچنین متغیر PATH را در External Web Tools ویژوال استودیو به ابتدای لیست آن منتقل کرده‌اید تا همواره از آخرین نگارش npm نصب شده‌ی در سیستم، استفاده شود.
پس از آن همانند نیوگت که از فایل xml ایی به نام packages.config برای ثبت آخرین تغییرات خودش استفاده می‌کند، npm نیز از فایلی به نام package.json برای ذکر وابستگی‌های مورد نیاز پروژه بهره می‌برد. برای افزودن آن، از منوی Project گزینه‌ی Add new item را انتخاب کرده و سپس npm را در آن جستجو کنید:


در اینجا نام پیش فرض package.json را پذیرفته و این فایل را به پروژه و ریشه‌ی آن اضافه کنید.
اگر گزینه‌ی فوق را در لیست Add new item مشاهده نمی‌کنید، مهم نیست. یک فایل متنی را به نام package.json به ریشه‌ی پروژه‌ی جاری اضافه کنید.
در ادامه محتوای این فایل را به نحو ذیل تغییر دهید:
{
    "name": "asp-net-mvc5x-angular2x",
    "version": "1.0.0",
    "author": "DNT",
    "description": "",
    "scripts": {},
    "license": "Apache-2.0",
    "dependencies": {
        "jquery": "2.2.3",
        "angular2": "2.0.0-beta.15",
        "systemjs": "^0.19.26",
        "es6-promise": "^3.1.2",
        "es6-shim": "^0.35.0",
        "reflect-metadata": "0.1.2",
        "rxjs": "5.0.0-beta.2",
        "zone.js": "^0.6.12",
        "bootstrap": "^3.3.6"
    },
    "devDependencies": {
        "typescript": "^1.8.9",
        "typings": "^0.8.1"
    },
    "repository": {
    }
}
این فایل تنظیمات، سبب بارگذاری خودکار وابستگی‌های مورد نیاز AngularJS 2.0 در ویژوال استودیو می‌شود.

چند نکته:
- هر زمانیکه این فایل را ذخیره کنید، بلافاصله کار دریافت این بسته‌ها از اینترنت آغاز خواهد شد. جزئیات آن‌را می‌توان در پنجره‌ی output ویژوال استودیو مشاهده کرد (و حتما این‌کار را جهت دیباگ کار انجام دهید. بسیاری از مشکلات و خطاها، در اینجا لاگ می‌شوند). بنابراین اگر قصد دارید کار همگام سازی تغییرات را انجام دهید، فقط کافی است دکمه‌های ctrl+s یا save را بر روی این فایل اعمال کنید.
- کاری که دکمه‌ی ctrl+s در این فایل انجام می‌دهد، اعمال خودکار دستور npm install بر روی پوشه‌ای است که package.json در آن قرار دارد. بنابراین برای دریافت این بسته‌ها، روش خط فرمان آن، ابتدا وارد شدن به ریشه‌ی پروژه‌ی جاری و سپس صدور دستور npm install است (که نیازی به آن نیست و ویژوال استودیو این‌کار را به صورت خودکار انجام می‌دهد).
- بسته‌های دریافت شده، در پوشه‌ای به نام node_modules در ریشه‌ی پروژه ذخیره می‌شوند:


برای مشاهده‌ی آن‌ها می‌توان بر روی دکمه‌ی show all files در solution explorer کلیک نمائید.

- درون فایل package.json، پشتیبانی کاملی از intellisense وجود دارد. برای مثال اگر علاقمند بودید تا آخرین نگارش AngularJS را مشاهده کنید، پس از خاصیت angular2 در تنظیمات فوق، " را تایپ کنید تا منوی تکمیل کننده‌ای ظاهر شود:


بدیهی است این منو جهت دریافت آخرین اطلاعات، نیاز به اتصال به اینترنت را دارد.

البته همیشه آخرین شماره نگارش AngularJS 2.0 را در این آدرس نیز می‌توانید مشاهده کنید: CHANGELOG.md

- در این فایل شماره نگارش آغاز شده‌ای با ^ مانند "3.1.2^" به این معنا است که اجازه‌ی نصب نگارش‌های جدیدتری از 3.1.2 نیز داده شده‌است.


به روز رسانی کامپایلر TypeScript

نیاز است یکبار مطلب «مبانی TypeScript؛ تنظیمات TypeScript در ویژوال استودیو» را جهت آشنایی با نحوه‌ی به روز رسانی اجزای مرتبط با TypeScript مطالعه کنید.


افزودن فایل tsconfig.json به پروژه

مرحله‌ی بعد شروع به کار با AngularJS و TypeScript، انجام تنظیمات کامپایلر TypeScript است. برای این منظور از منوی پروژه، گزینه‌ی Add new item، عبارت typescript را جستجو کرده و در لیست ظاهر شده، گزینه‌ی TypeScript JSON Configuration File را با نام پیش فرض آن انتخاب کنید:


اگر این گزینه‌ی ویژه را در لیست add new items ندارید، مهم نیست. یک فایل متنی را به نام tsconfig.json به ریشه‌ی پروژه‌ی جاری اضافه کنید.
پس از افزودن فایل tsconfig.json به ریشه‌ی سایت، تنظیمات آن‌را به نحو ذیل تغییر دهید:
{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es5",
        "module": "system",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "noEmitOnError": true,
        "removeComments": false,
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    },
    "exclude": [
        "node_modules",
        "wwwroot",
        "typings/main",
        "typings/main.d.ts"
    ]
}
در مورد جزئیات این تنظیمات در سری «مبانی TypeScript» و «تنظیمات کامپایلر TypeScript» بیشتر بحث شده‌است. فایل ویژه‌ی tsconfig.json تنظیمات پیش فرض ویژوال استودیو را جهت کار با TypeScript بازنویسی می‌کند. بنابراین زمانیکه از این فایل استفاده می‌شود، دیگر نیازی نیست به گزینه‌ی مختلف پروژه، جهت تنظیم TypeScript مراجعه کرد.
برای نمونه خاصیت compileOnSave سبب خواهد شد تا پس از ذخیره سازی یک فایل ts، بلافاصله فایل js معادل آْن تولید شود. نوع ماژول‌ها در اینجا به system.js تنظیم شده‌است و خروجی کدهای js آن از نوع ES 5 درنظر گرفته شده‌است. همچنین فعال سازی decorators نیز در اینجا صورت گرفته‌است. از این جهت که AngularJS2 استفاده‌ی گسترده‌ای را از این مفهوم، انجام می‌دهد.
در قسمت excludes به کامپایلر TypeScript اعلام شده‌است تا از یک سری از مسیرها مانند پوشه‌ی node_modules که پیشتر آن‌را تنظیم و دریافت کردیم، صرفنظر کند.


خلاصه‌ی بحث

تا اینجا با نحوه‌ی نصب پیشنیازهای AngularJS 2 و همچنین TypeScript آشنا شدیم. به صورت خلاصه:
- nmp اصلی را از سایت nodejs دریافت و نصب کنید.
- متغیر PATH را در ویژوال استودیو، به ابتدای لیست ابزارهای خارجی وب آن منتقل کنید تا از npm اصلی استفاده کند.
- فایل project.json را با محتوای ذکر شده، به ریشه‌ی پروژه اضافه کنید. سپس یکبار آن‌را save کنید تا وابستگی‌ها را از اینترنت دریافت کند.
- اطمینان حاصل کنید که آخرین کامپایلر TypeScript را نصب کرده‌اید.
- فایل tsconfig.json را با محتوای ذکر شده، به ریشه‌ی پروژه اضافه کنید.
مطالب
اعمال SEO بر روی AngularJS
در این بخش قصد داریم سئو را بر روی یک برنامه‌ی نوشته شده با آنگلولار و Asp.net Mvc اعمال نماییم. انگولار جی‌اس، صفحات را با  استفاده از جاوااسکریپت رندر میکند، ولی اکثر کرالر‌ها نمیتوانند جاوااسکریپت را اجرا کنند و موقع اجرای صفحات سایت ما  فقط یک div خالی را میبینند.
کاری که سرویس Prerender یا فیلتر سفارشی AjaxCrawlable برای ما انجام میدهد، درخواست‌هایی را که از طرف کرالرها آمده‌است را شناسایی میکند و مانند یک مرورگر، با استفاده از phantomjs آنرا اجرا میکند و نتیجه‌ی کامل صفحات ما را به صورت اچ تی ام ال استاتیک برمی‌گرداند.
فانتوم جی اس، موتور اختصاصی برای شبیه سازی مرورگر مبتنی بر Webkit می‌باشد. فانتوم جی اس را میتوانید بر روی ویندوز، لینوکس و مک نصب نمایید. فانتوم جی اس یک Console در اختیار برنامه نویس قرار می‌دهد که می‌توان توسط آن، برنامه‌های جاوااسکریپت را اجرا نمود. همچنین فانتوم جی اس میتواند اسکرین شاتی را نیز از محتوای وب سایت ما فراهم نماید.
 برای اینکه صفحات انگولار جی اس،ایندکس شوند سه مرحله وجود دارند:
1- به کرالر اطلاع دهیم که رندر کردن سایت، توسط جاوااسکریپت انجام میگردد؛ با اضافه کردن متاتگ زیر در اچ تی ام ال سایت (البته در حالت استفاده HTML5 push state ) :
<meta name="fragment" content="!">
<base href="/">
2- بعد از اضافه کردن متاتگ بالا، کرالر درخواست‌های خود را به صورت زیر به سایت ما ارسال میکند:
http://www.example.com/?_escaped_fragment_=
ما در این مثال از  HTML5 push state  استفاده میکنیم. بنابراین لینکی مانند http://www.example.com/user/123 توسط کرالر به صورت زیر دیده میشود: 
http://www.example.com/user/123?_escaped_fragment_=
3- اچ تی ام ال کاملا رندر شده توسط سایت ما به کرالر ارسال گردد.
برای رندر کردن  اچ تی ام ال صفحات، چندین روش وجود دارد:
روش اول: میتوانیم از سرویس‌های آماده‌ای همچون Prerender.io   استفاده کنیم که سرویسهایی را برای زبانهای مختلف ارائه کرده‌اند. باتوجه به توضیحات نمونه استفاده از آن در Asp.Net Mvc کافیست در سایت Prerender.io  ثبت نام کرده، Token را دریافت کنیم و در کانفیگ برنامه قرار دهیم و در کلاس PreStart قطعه کد زیر را قرار دهیم:
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
مثال استفاده از Prerender.io را میتوانید از این آدرس Simple_Demo_Prerender.zip دانلود نمایید.
 
یکی از ابزارهای مناسب تست کردن اینکه صفحات توسط کرالر ایندکس میشوند یا خیر، برنامه screamingfrog میباشد.
در پنل Ajax آن، صفحات ایندکس شده ما نمایش داده میشوند. لینکی مشابه زیر را در مرورگر اجرا کرده، با ViewPage Source کردن آن میتوانید نتیجه اچ تی ام ال کاملا رندر شده را مشاهده نمایید.
http://www.example.com/user/123?_escaped_fragment_=
نسخه رایگان سرویس Prerender.io تا 250 صفحه را پوشش میدهد.

روش دوم: فیلتر سفارشی AjaxCrawlable. در اولین قدم نیاز به نصب فانتوم جی اس داریم:
<package id="PhantomJS" version="1.9.2" targetFramework="net452" />
<package id="phantomjs.exe" version="1.9.2.1" targetFramework="net452" />
فایل phantomjs.exe را از پوشه packages\PhantomJS.1.9.2\tools\phantomjs\phantomjs.exe یافته و در پوشه bin برنامه قرار دهید. با Attribute زیر هر درخواستی که توسط کرالر ارسال گردد به اکشن returnHTML منتقل میگردد.
برای اینکه خطای معروف A potentially dangerous Request.Form value was detected from the client را دریافت نکنیم، کافیست قسمتهایی از آدرس را که شامل کاراکترهای خاصی مانند :// میباشند، از url حذف کنیم و در اکشن returnHtml قسمتهای حذف شده را  به url  اضافه نماییم.
کرالرها  با مشاهده تگ fragment، تمام لینکها را به همراه کوئری استرینگ _escaped_fragment_  میفرستند، که ما در سرور باید آنرا  با رشته خالی جایگزین نماییم.
 public class AjaxCrawlableAttribute : System.Web.Mvc.ActionFilterAttribute
    {
        private const string Fragment = "_escaped_fragment_";
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var request = filterContext.RequestContext.HttpContext.Request;
            var url = request.Url.ToString();
            if (request.QueryString[Fragment] != null && !url.Contains("HtmlSnapshot/returnHTML"))
            {
                url = url.Replace("?_escaped_fragment_=", string.Empty).Replace(request.Url.Scheme + "://", string.Empty);
                url = url.Split(':')[1];
                filterContext.Result = new RedirectToRouteResult(
                   new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } });
            }
            return;
        }
    }
Route‌های پیشفرض را با کدهای زیر جایگزین میکنیم:
public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
             name: "HtmlSnapshot",
             url: "HtmlSnapshot/returnHTML/{*url}",
             defaults: new { controller = "HtmlSnapshot", action = "returnHTML", url = UrlParameter.Optional });

            routes.MapRoute(
            name: "SPA",
            url: "{*catchall}",
            defaults: new { controller = "Home", action = "Index" })
        }
 اضافه کردن این فیلتر به فیلترهای Asp.net Mvc 
 public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new AjaxCrawlableAttribute());
        }
    }
ایجاد کنترلر HtmlSnapshot و متد returnHTML :
Url را به عنوان آرگومان به تابع page.open فایل جاوااسکریپتی فانتوم میدهیم و بعد از اجرای کامل، خروجی را درViewData قرار میدهیم 
public ActionResult returnHTML(string url)
        {
            var prefix = HttpContext.Request.Url.Scheme + "://" + HttpContext.Request.Url.Host+":";
            url = prefix+url;
            string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
            var startInfo = new ProcessStartInfo
            {
                Arguments = string.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo.js"), url),
                FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                StandardOutputEncoding = System.Text.Encoding.UTF8
            };
            var p = new Process();
            p.StartInfo = startInfo;
            p.Start();
            string output1 = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            ViewData["result"] = output1.Replace("<!-- ngView:  -->", "").Replace("ng-view=\"\"", "");
            return View();
        }
در فایل renderHtml.cshtml
@{ 
    Layout = null;
}
@Html.Raw(ViewBag.result)
ایجاد فایل seo.js  در پوشه Scripts سایت :
در این بخش webpage  را ایجاد میکنیم و آدرس صفحه را از[system.args[1  دریافت کرده و عملیات کپچر کردن را آغاز میکنیم و بعد از تکمیل اطلاعات در سرور، کد زیر اجرا میشود:
console.log(page.content)

var page = require('webpage').create();
var system = require('system');

var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();;
page.onResourceReceived = function (response) {
    if (requestIds.indexOf(response.id) !== -1) {
        lastReceived = new Date().getTime();
        responseCount++;
        requestIds[requestIds.indexOf(response.id)] = null;
    }
};
page.onResourceRequested = function (request) {
    if (requestIds.indexOf(request.id) === -1) {
        requestIds.push(request.id);
        requestCount++;
    }
};

function checkLoaded() {
    return page.evaluate(function () {
        return document.all["compositionComplete"];
    }) != null;
}
// Open the page
page.open(system.args[1], function () {

});

var checkComplete = function () {
    // We don't allow it to take longer than 5 seconds but
    // don't return until all requests are finished
    if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
        clearInterval(checkCompleteInterval);
        console.log(page.content);
        phantom.exit();
    }
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);
صفحه Layout.Cshtml
<!DOCTYPE html>
<html ng-app="appOne">
<head>
    <meta name="fragment" content="!">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    <base href="/">
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    <script src="~/Scripts/angular/angular.js"></script>
    <script src="~/Scripts/angular/angular-route.js"></script>
    <script src="~/Scripts/angular/angular-animate.js"></script>
    <script>
        angular.module('appOne', ['ngRoute'], function ($routeProvider, $locationProvider) {
            $routeProvider.when('/one', {
                template: "<div>one</div>", controller: function ($scope) {
                }
            })
            .when('/two', {
                template: "<div>two</div>", controller: function ($scope) {
                }
            }).when('/', {
                template: "<div>home</div>", controller: function ($scope) {
                }
            });
            $locationProvider.html5Mode({
                enabled: true
            });
        });
    </script>
</head>
<body>
    <div id="body">
        <section ng-view></section>
        @RenderBody()
    </div>
    <div id="footer">
        <ul class='xoxo blogroll'>
            <li><a href="one">one</a></li>
            <li><a href="two">two</a></li>
        </ul>
    </div>
</body>
</html>

چند نکته تکمیلی:
* فانتوم جی اس قادر به اجرای لینکهای فارسی (utf-8) نمیباشد.
 * اگر خطای syntax error را دریافت کردید ممکن است پروژه شما در مسیری طولانی در روی هارد دیسک قرار داشته باشد.
مطالب
گروه بندی اطلاعات و گزارشات Master-Details در PdfReport
اگر به بانک اطلاعاتی مثال‌های همراه سورس‌های PdfReport در مسیر Bin\Data\blogs.sqlite مراجعه کنید، دو جدول والدین و فرزندان هم در آن وجود دارند:



بر این اساس قصد داریم رابطه یک به چند فوق را گروه بندی شده نمایش دهیم:


(البته این اعداد و اطلاعات، به صورت اتفاقی تولید شده‌اند و الزامی ندارد که والد متولد 2002 هنوز والد شده باشد؛ یا اینکه فرزندی متولد 2003 داشته باشد!)

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

سورس کامل این مثال را در ادامه مشاهده می‌کنید:
using System;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.MasterDetails
{
    public class MasterDetailsPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\arial.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(new MasterDetailsHeaders { PdfRptFont = header.PdfFont });
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .MainTableTemplate(t => t.BasicTemplate(BasicTemplate.SilverTemplate))
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.GroupsPreferences(new GroupsPreferences
                {
                    GroupType = GroupType.HideGroupingColumns,
                    RepeatHeaderRowPerGroup = true,
                    ShowOneGroupPerPage = false,
                    SpacingBeforeAllGroupsSummary = 5f,
                    NewGroupAvailableSpacingThreshold = 170
                });
            })
            .MainTableDataSource(dataSource =>
            {
                dataSource.GenericDataReader(
                   providerName: "System.Data.SQLite",
                   connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
                   sql: @"select 
                            tblParents.BirthDate as ParentBirthDate,
                            tblParents.Name as ParentName,
                            tblParents.LastName as ParentLastName,
                            tblKids.Name as KidName,
                            tblKids.BirthDate as KidBirthDate
                            from tblParents
                                left outer join tblKids
                                     on tblKids.ParentId = tblParents.Id
                            order by 
                                tblParents.Name,
                                tblParents.LastName,
                                tblKids.Name"
               );
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentBirthDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(1);
                    column.Width(2);
                    column.HeaderCell("ParentBirthDate");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        var date1 = (DateTime)val1;
                        var date2 = (DateTime)val2;
                        return date1.Year == date2.Year && date1.Month == date2.Month && date1.Day == date2.Day;
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(2);
                    column.Width(2);
                    column.HeaderCell("ParentName");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("ParentLastName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("ParentLastName");
                    column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("KidName");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(4);
                    column.Width(2);
                    column.HeaderCell("Child Name");
                    column.IsVisible(true);
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("KidBirthDate");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.Order(5);
                    column.Width(2);
                    column.HeaderCell("BirthDate");
                    column.IsVisible(true);
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(e => e.ToExcel())
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptMasterDetailsSample.pdf"));
        }
    }
}
به همراه سر ستون‌های مجزای هر گروه و صفحه:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.ColumnsItemsTemplates;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.MasterDetails
{
    public class MasterDetailsHeaders : IPageHeader
    {
        public IPdfFont PdfRptFont { set; get; }

        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData)
        {
            var parentName = newGroupInfo.GetSafeStringValueOf("ParentName");
            var parentLastName = newGroupInfo.GetSafeStringValueOf("ParentLastName");
            var parentBirthDate = newGroupInfo.GetSafeStringValueOf("ParentBirthDate");

            var table = new PdfPTable(relativeWidths: new[] { 1f, 5f }) { WidthPercentage = 100 };
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Last Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentLastName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.Value = "Birth Date:";
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
               },
               (cellData, cellProperties) =>
               {
                   cellData.Value = parentBirthDate;
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
               });
            return table.AddBorderToTable(borderColor: BaseColor.LIGHT_GRAY, spacingBefore: 5f);
        }

        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            var table = new PdfPTable(numColumns: 1) { WidthPercentage = 100 };
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.CellTemplate = new ImageFilePathField();
                   cellData.Value = AppPath.ApplicationPath + "\\Images\\01.png";
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Center;
               });
            table.AddSimpleRow(
               (cellData, cellProperties) =>
               {
                   cellData.Value = "Family rpt";
                   cellProperties.PdfFont = PdfRptFont;
                   cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                   cellProperties.HorizontalAlignment = HorizontalAlignment.Center;
               });
            return table.AddBorderToTable();
        }
    }
}
توضیحات:
- منبع داده مورد استفاده در اینجا از نوع GenericDataReader است؛ جهت خواندن رکوردهای بانک اطلاعاتی SQLite ذکر شده در ابتدای بحث. (دو مثال دیگر هم به پوشه مثال‌های سورس‌های PdfReport اضافه شده‌اند به نام‌های Grouping و WrapGroupsInColumns که به همین موضوع گروه بندی می‌پردازند؛ البته با استفاده از StronglyTypedList‌ها. ولی درکل مفاهیم و اصول آن‌ها یکی است.)
select 
          tblParents.BirthDate as ParentBirthDate,
          tblParents.Name as ParentName,
          tblParents.LastName as ParentLastName,
          tblKids.Name as KidName,
          tblKids.BirthDate as KidBirthDate
          from tblParents
                  left outer join tblKids
                        on tblKids.ParentId = tblParents.Id
                            order by 
                                tblParents.Name,
                                tblParents.LastName,
                                tblKids.Name
در کوئری فوق (و کلا گروه بندی اطلاعات) دو نکته حائز اهمیت است:
الف) چون قرار است اطلاعات بر اساس مشخصات والدین و فرزندان آن‌ها گروه بندی شود، نیاز است حتما order by  و مرتب سازی رکوردها قید گردد.
ب) در PdfReport نمی‌توانید در خواص معرفی شده جهت تعریف ستون‌ها، از نام‌های تکراری استفاده کنید. برای رفع این مشکل استفاده از Alias پیشنهاد می‌شود؛ مانند:
tblParents.Name as ParentName,
tblKids.Name as KidName,
- مشخص سازی خاصیت و ستونی که قرار است در گروه بندی شرکت کند بسیار ساده است:
column.Group(true,
                    (val1, val2) =>
                    {
                        return val1.ToString() == val2.ToString();
                    });
در اینجا به کمک متد Group، قابلیت گروه بندی بر روی این ستون فعال شده و سپس باید فرمولی را جهت مشخص سازی حد و مرز گروه مشخص کنیم. برای مثال در اینجا اگر مقادیر ردیف جاری (val2) و ردیف قبلی (val1) یکسان نبودند، یعنی گروه خاتمه یافته و گروه جدیدی شروع می‌شود (به همین جهت عنوان شد که مرتب سازی اطلاعات ضروری است).

- تنظیم دیگری را که در اینجا می‌توان ذکر کرد، مورد ذیل است:
                table.GroupsPreferences(new GroupsPreferences
                {
                    GroupType = GroupType.HideGroupingColumns,
                    RepeatHeaderRowPerGroup = true,
                    ShowOneGroupPerPage = false,
                    SpacingBeforeAllGroupsSummary = 5f,
                    NewGroupAvailableSpacingThreshold = 170
                });
به این ترتیب می‌توان مشخص کرد که آیا باید ستون‌های دخیل در گروه بندی، در گزارش نمایش داده شوند یا خیر (GroupType.HideGroupingColumns)، آیا سر ستون هر جدول، به ازای هر گروه باید تکرار شود؟ (RepeatHeaderRowPerGroup)، آیا در هر صفحه یک گروه نمایش داده شود (ShowOneGroupPerPage) یا اینکه گروه‌ها به صورت متوالی در صفحات درج شوند. توسط SpacingBeforeAllGroupsSummary، فاصله جمع نهایی تمام گروه‌ها از آخرین گروه نمایش داده شده مشخص می‌شود. به کمک NewGroupAvailableSpacingThreshold مشخص می‌کنیم که در چه فاصله‌ای از انتهای صفحه، گروه جدیدی نباید درج شود و این گروه باید به صفحه بعدی منتقل شده و از آنجا شروع شود.

- اگر به تصویر ابتدای مطلب دقت کرده باشید، علاوه بر هدر صفحه، هر گروه نیز یک هدر مجزا دارد. برای طراحی آن باید اینترفیس IPageHeader را پیاده سازی کرد که نمونه‌ای از آن‌را در کلاس MasterDetailsHeaders فوق مشاهده می‌کنید.
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData)
        {
            var parentName = newGroupInfo.GetSafeStringValueOf("ParentName");
            var parentLastName = newGroupInfo.GetSafeStringValueOf("ParentLastName");
            var parentBirthDate = newGroupInfo.GetSafeStringValueOf("ParentBirthDate");

            var table = new PdfPTable(relativeWidths: new[] { 1f, 5f }) { WidthPercentage = 100 };
            table.AddSimpleRow(
                (cellData, cellProperties) =>
                {
                    cellData.Value = "Name:";
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.PdfFontStyle = DocumentFontStyle.Bold;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                },
                (cellData, cellProperties) =>
                {
                    cellData.Value = parentName;
                    cellProperties.PdfFont = PdfRptFont;
                    cellProperties.HorizontalAlignment = HorizontalAlignment.Left;
                });
ساختار آن هم بسیار ساده است. توسط  newGroupInfo می‌توان به اطلاعات گروه جدید، دسترسی یافت. برای مثال در اینجا اطلاعات والد گروه جدید در حال تهیه، دریافت شده و سپس در ردیف‌های یک جدول دو ستونه درج می‌شود. در ستون اول آن یک برچسب و در ستون دوم، مقدار دریافتی نمایش داده شده است و همینطور الی آخر برای سایر ردیف‌ها.
مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت دوم
پس از تهیه ساختار اولیه‌ی بلاگی مبتنی بر ember.js در قسمت قبل، در ادامه قصد داریم امکانات تعاملی را به آن اضافه کنیم. بنابراین کار را با تعریف کنترلرها که تعیین کننده‌ی رفتار برنامه هستند، ادامه می‌دهیم.


اضافه کردن دکمه‌ی More info به صفحه‌ی About و مدیریت کلیک بر روی آن

فایل Scripts\Templates\about.hbs را گشوده و سپس محتوای فعلی آن را به نحو ذیل تکمیل کنید:
<h2>About Ember Blog</h2>

<p>Bla bla bla!</p>

<button class="btn btn-primary" {{action 'showRealName' }}>more info</button>
در ember.js اگر قصد مدیریت عملی را که قرار است توسط کلیک بر روی المانی رخ دهد، داشته باشیم، می‌توان از handlebar helper ایی به نام action استفاده کرد. سپس برای تهیه کدهای مرتبط با آن، این اکشن را باید در کنترلر متناظر با route جاری (مسیریابی about) اضافه کنیم.
به همین جهت فایل جدید Scripts\Controllers\about.js را در پوشه‌ی کنترلرهای سمت کاربر اضافه کنید (نام آن با نام مسیریابی یکی است)؛ با این محتوا:
Blogger.AboutController = Ember.Controller.extend({
  actions: {
   showRealName: function () {
    alert("You clicked at showRealName of AboutController.");
   }
  }
});
کنترلرها به صورت یک خاصیت جدید به شیء Application برنامه اضافه می‌شوند. مطابق اصول نامگذاری ember.js، نام خاصیت کنترلر با حروف بزرگ متناظر با route آن شروع می‌شود و به نام Controller ختم خواهد شد. به این ترتیب ember.js هرگاه قصد پردازش مسیریابی about را داشته باشد، می‌داند که باید از کدام شیء جهت پردازش اعمال کاربر استفاده کند.
در ادامه این خاصیت را با تهیه یک زیرکلاس از کلاس پایه Controller تهیه شده توسط ember.js مقدار دهی می‌کنیم. به این ترتیب به کلیه امکانات این کلاس پایه دسترسی خواهیم داشت؛ به علاوه می‌توان ویژگی‌های سفارشی را نیز به آن افزود. برای مثال در اینجا در قسمت actions آن، دقیقا مطابق نام اکشنی که در فایل about.hbs تعریف کرده‌ایم، یک متد جدید اضافه شده‌است.

پس از تعریف کنترلر about.js نیاز است مدخل متناظر با آن‌را به فایل index.html برنامه نیز در انتهای تعاریف موجود، اضافه کرد:
 <script src="Scripts/Controllers/about.js" type="text/javascript"></script>

اکنون یکبار برنامه را اجرا کرده و در صفحه‌ی about بر روی دکمه‌ی more info کلیک کنید.



اضافه کردن دکمه‌ی ارسال پیام خصوصی به صفحه‌ی ‍Contact و مدیریت کلیک بر روی آن

در ادامه به قالب فعلی Scripts\Templates\contact.hbs یک دکمه را جهت ارسال پیام خصوصی اضافه می‌کنیم.
<h1>Contact</h1>
<div class="row">
  <div class="col-md-6">
   <p>
    Want to get in touch?
    <ul>
      <li>{{#link-to 'phone'}}Phone{{/link-to}}</li>
      <li>{{#link-to 'email'}}Email{{/link-to}}</li>
    </ul>
   </p>
 
   <p>
    Or, click here to send a secret message:
   </p>
   <button class="btn btn-primary" {{action 'sendMessage' }}>Send message</button>
  </div>
  <div class="col-md-6">
   {{outlet}}
  </div>
</div>
سپس برای مدیریت اکشن جدید sendMessage نیاز است کنترلر آن‌را نیز تعریف کنیم. با توجه به نام مسیریابی جاری، نام این کنترلر نیز contact خواهد بود. برای این منظور ابتدا فایل جدید Scripts\Controllers\contact.js را اضافه نمائید؛ با این محتوا:
Blogger.ContactController = Ember.Controller.extend({
  actions: {
   sendMessage: function () {
    var message = prompt('Type your message here:');
   }
  }
});
همچنین مدخل متناظر با فایل contact.js نیز باید به صفحه‌ی index.html اضافه شود:
 <script src="Scripts/Controllers/contact.js" type="text/javascript"></script>


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

تا اینجا با نحوه‌ی تعریف اکشن‌ها در قالب‌ها و مدیریت آن‌ها توسط کنترلرهای متناظر آشنا شدیم. در ادامه قصد داریم با اصول binding اطلاعات در ember.js آشنا شویم. برای مثال فرض کنید می‌خواهیم دکمه‌ای را در صفحه‌ی about قرار داده و با کلیک بر روی آن، لوگوی ember.js را که به صورت یک تصویر مخفی در صفحه قرار دارد، نمایان کنیم. برای اینکار نیاز است خاصیتی را در کنترلر متناظر، تعریف کرده و سپس آن‌را به template جاری bind کرد.
برای این منظور فایل Scripts\Templates\about.hbs را گشوده و تعاریف موجود آن‌را به نحو ذیل تکمیل کنید:
<h2>About Ember Blog</h2>
<p>Bla bla bla!</p>
<button class="btn btn-primary" {{action 'showRealName' }}>more info</button>
 
{{#if isAuthorShowing}}
<button class="btn btn-warning" {{action 'hideAuthor' }}>Hide Image</button>
<p><img src="Content/images/ember-productivity-sm.png"></p>
{{else}}
<button class="btn btn-info" {{action 'showAuthor' }}>Show Image</button>
{{/if}}
در اینجا بر اساس مقدار خاصیت isAuthorShowing تصمیم گیری خواهد شد که آیا تصویر لوگوی ember.js نمایش داده شود یا خیر. همچنین دو اکشن نمایش و مخفی کردن تصویر نیز اضافه شده‌اند که با کلیک بر روی هر کدام، سبب تغییر وضعیت خاصیت isAuthorShowing خواهیم شد.
کنترلر about (فایل Scripts\Controllers\about.js) جهت مدیریت این خاصیت جدید، به همراه دو اکشن تعریف شده، اینبار به نحو ذیل تغییر خواهد یافت:
Blogger.AboutController = Ember.Controller.extend({
  isAuthorShowing: false,
  actions: {
   showRealName: function () {
    alert("You clicked at showRealName of AboutController.");
   },
   showAuthor: function () {
    this.set('isAuthorShowing', true);
   },
   hideAuthor: function () {
    this.set('isAuthorShowing', false);
   }
  }
});
ابتدا خاصیت isAuthorShowing به کنترلر اضافه شده‌است. از این خاصیت بار اولی که مسیر http://localhost:25918/#/about توسط کاربر درخواست می‌شود، استفاده خواهد شد.
سپس در دو متد showAuthor و hideAuthor که به اکشن‌های دو دکمه‌ی جدید تعریف شده در قالب about متصل خواهند شد، نحوه‌ی تغییر مقدار خاصیت isAuthorShowing را توسط متد set ملاحظه می‌کنید.
این قسمت مهم‌ترین تفاوت ember.js با jQuery است. در jQuery مستقیما المان‌های صفحه در همانجا تغییر داده می‌شوند. در ember.js منطق مدیریت کننده‌ی رابط کاربری و کدهای قالب متناظر با آن از هم جدا شده‌اند تا بتوان یک برنامه‌ی بزرگ را بهتر مدیریت کرد. همچنین در اینجا مشخص است که هر قسمت و هر فایل، چه ارتباطی با سایر اجزای تعریف شده دارد و چگونه به هم متصل شده‌اند و اینبار شاهد انبوهی از کدهای جاوا اسکریپتی مخلوط بین المان‌های HTML صفحه نیستیم.



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

قصد داریم ویژگی مشابهی را به صفحه‌ی contact نیز اضافه کنیم. اگر کاربر بر روی دکمه‌ی ارسال پیام کلیک کرد، پیام تشکری به همراه عددی ویژه به او نمایش خواهیم داد.
برای این‌کار قالب Scripts\Templates\contact.hbs را به نحو ذیل تکمیل کنید:
<h1>Contact</h1>
<div class="row">
  <div class="col-md-6">
   <p>
    Want to get in touch?
    <ul>
      <li>{{#link-to 'phone'}}Phone{{/link-to}}</li>
      <li>{{#link-to 'email'}}Email{{/link-to}}</li>
    </ul>
   </p>
   {{#if messageSent}}
   <p>
    Thank you. Your message has been sent.
    Your confirmation number is {{confirmationNumber}}.
   </p>
   {{else}}
   <p>
    Or, click here to send a secret message:
   </p>
   <button class="btn btn-primary" {{action 'sendMessage' }}>Send message</button>
   {{/if}}
  </div>
  <div class="col-md-6">
   {{outlet}}
  </div>
</div>
در آن شرط بررسی if messageSent اضافه شده‌است؛ به همراه نمایش confirmationNumber در انتهای پیام تشکر.


برای تعریف منطق مرتبط با این خواص، به کنترلر contact واقع در فایل Scripts\Controllers\contact.js مراجعه کرده و آن‌را به نحو ذیل تغییر می‌دهیم:
Blogger.ContactController = Ember.Controller.extend({
  messageSent: false,
  actions: {
   sendMessage: function () {
    var message = prompt('Type your message here:');
    if (message) {
      this.set('confirmationNumber', Math.round(Math.random() * 100000));
      this.set('messageSent', true);
    }
   }
  }
});
همانطور که مشاهده می‌کنید، مقدار اولیه خاصیت messageSent مساوی false است. بنابراین در قالب contact.hbs قسمت else شرط نمایش داده می‌شود. اگر کاربر پیامی را وارد کند، خاصیت confirmationNumber به یک عدد اتفاقی و خاصیت messageSent به true تنظیم خواهد شد. به این ترتیب اینبار به صورت خودکار پیام تشکر به همراه عددی اتفاقی، به کاربر نمایش داده می‌شود.



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



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS03_02.zip
مطالب
تازه‌های سرویس پک یک VS 2010 ؛ یکپارچگی با IIS Express

در مورد تنظیمات دستی IIS Express که یک نسخه‌ی سبک IIS 7.5 قابل اجرا بر روی ویندوز XP نیز می‌باشد، پیشتر در این سایت مطلبی را مطالعه کرده‌اید (+). اکنون که سرویس پک یک VS 2010 ارائه شده (+)، دیگر نیازی به آن تنظیمات دستی نبوده و امکان استفاده یکپارچه و خودکار از این نسخه‌ی ساده شده IIS 7.5 به شرح زیر وجود دارد:

ابتدا نیاز است تا هر دو مورد سرویس پک یک VS 2010 و همچنین IIS Express به صورت جداگانه نصب شوند. سپس:

الف) ابتدا از منوی Tools‌ ، گزینه‌ی Options را انتخاب کنید. در صفحه‌ی باز شده در قسمت Projects and solutions ذیل گزینه‌ی Web projects نیاز است تا یکبار مجوز استفاده از IIS express صادر شود:



ب) اکنون بر روی نام پروژه در Solution explorer موجود در Visual studio کلیک راست کرده و گزینه‌ی Use IIS Express را انتخاب نمائید:



به این صورت تنظیمات لازم به صورت خودکار اعمال خواهد گردید و جهت مشاهده آن‌ها می‌توان به خواص پروژه، برگه‌ی Web مراجعه کرد:



نکته مهم:
نسخه‌ی RTM ویژوال استودیوی 2010 تنظیمات فوق را که در تصویر ملاحظه می‌کنید، ندارد. به عبارتی پس از اعمال تغییرات فوق باید دقت داشت سایرینی که قرار است از پروژه‌ی شما استفاده کنند نیز باید پیشنیازهای ذکر شده را رعایت نمایند و یا جهت توزیع سورس می‌توان مجددا بر روی نام پروژه کلیک راست کرده و اینبار گزینه‌ی Use Visual Studio Development Server قدیمی را انتخاب کرد.

نظرات مطالب
BloggerToCHM
آره امتحان کردم دید م از آدرس فایل که بصورت زی بود :
C:\Program Files\BloggerToCHM
میبینی بین کلمه Program و Files فاصله هست که این خطلا رو تولید میکنه. بخاطر راهنماییتون ممنون.
مطالب
مروری بر پلاگین‌های چندسکویی مرورگرها
NPAPI
این عبارت مخفف Netscape Plugin Application Programming Interface هست و یک پلاگین چندسکویی است که بسیاری از مرورگرها از آن استفاده می‌کنند. معماری این پلاگین ابتدا در سال 1995 برای مرورگر netscape نسخه 2 آن توسعه پیدا کرد. ولی رفته رفته توسط دیگر مرورگرها هم استفاده و پیاده سازی شد و البته تعدادی هم بعدها استفاده از آن را رها کردند و از فناوری‌های دیگری استفاده کردند.
کار این پلاگین تعریف یک سری ContentType مانند audio/mp3 است و موقعی‌که مرورگر در رندر کردن این صفحات به چنین محتوایی برخورد کند، پلاگین مربوط به این محتوا را در حافظه بار می‌کند تا پلاگین را در حافظه رندر کرده و جابجایی داده‌ها با پلاگین صورت بگیرد. سپس آن پلاگین محتوای مربوطه را رندر می‌کند. امروزه پلاگین‌ها در همان قسمت از حافظه صفحه مرورگر بار می‌شوند و مانند مرورگرهای قدیمی مجبور به اجرای برنامه خارجی برای محتواهای ناشناخته نیستند.
این API نیاز دارد هر پلاگین تقریبا 15 تابع را پیاده سازی کند. این توابع برای پیاده سازی، ایجاد، نابودسازی آن و موقعیت مکانی آن به کار می‌روند. NPAPI همچنین امکاناتی از قبیل اسکریت نویسی، چاپ کردن، تمام صفحه شدن، پلاگین بدون پنجره، استریم کردن محتوا را نیز دارد.
مبداء و منشا ابتدایی این فناوری از شرکت Adobe و محصول Adobe Reader آن و خواندن PDF‌های تحت وب آغاز گشت. در آن زمان در وب، تنها فرمت HTML به همراه تصاویر به عنوان محتوا حساب میشد و مابقی نوع‌ها به دانلود ختم می‌شدند و شرکت ادوب قصد داشت کاربران تجربه‌ی جدیدی از خواندن فایل‌های PDF را بر روی مرورگر وب داشته باشند و برای دیدن حتما نیازی به دانلود نداشته باشند.
شرکت ادوب برای جلب نظر توسعه دهنگان netscape از یک نسخه‌ی دمو استفاده کرد و موقعی که روی لینک یک PDF کلیک میشد به جای دانلود آن، محتوا داخل صفحه مرورگر رندر میشد که مخلوطی از کدهای html و PDF بود ولی آن‌ها خواستار یک سیستم یکپارچه بودند تا دیگر مرورگر netscape درگیر آن نباشد و بدون وابستگی به مرورگر و به طور مستقل این کار انجام شود که کم کم باعث ایجاد این API شد و از آن موقع در طول این سال‌ها مرورگرهای زیادی از این فناوری استفاده کردند تا استاندارد عمومی در توسعه مرورگرها باشد.

عدم پشتیبانی گوگل از این پلاگین API
گوگل در سال 2013 عدم پشتیبانی از این API را در مرورگرش اعلام کرد که در این حالت پشتیبانی از تمامی فناوری‌های مرتبط آن نیز خاتمه میابد که شامل جاوا و سیلورلایت می‌شود و دلیل آن مسائل پایداری و امنیت آن در کنار سایر موارد است.

پشتیبانی از اسکریپت نویسی
این ویژگی اجازه میدهد که شما با استفاده از کدهای جاوااسکریپت، در صفحات وب با پلاگین تعامل کنید. ابتدا netscape این امکان را اضافه کرد و بعدها موزیلا این ویژگی را با استفاده از فناوری هایی چون Live Connect ،  XP Connect و npRuntime ارتقاء بخشید.

Live Connect
از netscape 4 به بعد NPAPI برای اسکریپت نویسی توسعه یافت. این ویژگی به مرورگرها اجازه میدهد که برنامه‌های نوشته شده با جاوا و جاوااسکریپت، با صفحات وب به تعامل بپردازند. به عنوان مثال اپلت نوشته شده با جاوا می‌تواند با کدهای داخل صفحه ارتباط برقرار کند یا بیشتر از خود اسکریت‌های صفحه با محیط جاوااسکریت داخلی ارتباط برقرار کند.  با این ویژگی یک پلاگین میتواند یک نمونه کلاس جاوا را پیاده سازی و بازگرداند. این کلاس میتواند از طریق جاوااسکریت یا اپلت‌های جاوا صدا زده شود.

XP Connect
ابتدا قبل از هر چیز بگذارید با XPCOM اشنا شویم. XPCOM یا Cross Platform Component Object Model از طرف شرکت موزیلا ارائه شده است و اصلی‌ترین ویژگی آن اتصال چندین زبان به یکدیگر از طریق IDL یا یک زبان واسط است. بدین ترتیب برنامه نویس‌ها میتوانستند کدهای اختصاصی خود را با دیگر کامپوننت‌ها مرتبط کنند.
حال کار xpconnect برقراری ارتباط بین جاوااسکریپت با XPCOM یا کدهای نوشته شده توسعه گران است. XP Connect به اشیاء جاوااسکریپتی اجازه می‌دهد به طور مستقیم به اشیاء XPCom دسترسی داشته و آن‌ها را دستکاری کنند.

NPRuntime
در پایان سال 2004 بسیاری از شرکت‌های اصلی ارائه کننده مرورگرهای وب که از NPAPI استفاده می‌کردند، به این توافق رسیدند که از فناوری NPRuntime به عنوان یک اکستنشن (افزونه) برای تامین اسکریپت نویسی NPAPI استفاده کنند. اسکریپت نویسی این فناوری مستقل از دیگر فناوری‌هایی چون جاوا و XPCOM است و توسط گوگل (در آن زمان)، فایرفاکس، سافاری و اپرا (در آن زمان)  پشتیبانی می‌شود.

PPAPI
این فناوری در 12 آگوست 2009 معرفی شد. Pepper Plugin API مجموعه ای از تغییرات و اصلاحات روی NPAPI است که باعث انعطاف پذیری و امنیت بیشتر می‌شود. این افزونه جهت پیاده سازی آسان اجرای پلاگین در یک پروسه‌ی جداگانه است. هدف این پروژه تهیه یک فریمورک برای ایجاد پلاگین‌های مستقل یا Cross-Platform میباشد.
تعدادی از مواردی که در طراحی این افزونه در نظر گرفته شده است:
  • اجرای جداگانه از پروسه‌ی مرورگر
  • تعریف رویدادهای استاندارد و رسم تصاویر دو بعدی
  • تهیه گرافیک سه بعدی
  • ریجستری پلاگین
  • استاندارد سازی برای استفاده از سیستم رندر مرورگر Compositing Process 
  • تعریف مفهوم یا معنای واحد از NPAPI بین تمامی مروگرها
مرورگرهایی که در حال حاضر به این افزونه روی آورده اند طبعا مرورگر کروم و اپرا از نسخه 24 به بعد است.
شرکت موزیلا در سال 2011 اعلام کرد که فعلا علاقه ای به شرکت در این پروژه ندارد.
شرکت ادوب برای افزونه flash player اعلام کرد که دیگر تنها بر روی PPAPI فعالیت می‌کنند و از آخرین نسخه فلش پلیر 11.2 که برای NPAPI ایجاد شده است تنها به مدت پنج سال بسته‌های آپدیت آن را در دسترس قرار میدهد.

ActiveX
یکی دیگر از این پلاگین‌ها ActiveX است که توسط IE مورد استفاده قرار می‌گیرد ولی در مرورگر Edge  حذف شده است. به این دلیل، پشتیبانی از جاوا در این مرورگر قطع شده است و فلش پلیر را به صورت ذاخلی Built-in پشتیبانی می‌کند. امروزه بسیاری از تولیدکنندگان اعتقاد دارند که با Html5 بسیاری از مسائل حل می‌شود.