مطالب
Blazor 5x - قسمت سوم - مبانی Razor
پیش از شروع به کار توسعه‌ی برنامه‌های مبتنی بر Blazor، باید با مبانی Razor آشنایی داشت. Razor امکان ترکیب کدهای #C و HTML را در یک فایل میسر می‌کند. دستور زبان آن از @ برای سوئیچ بین کدهای #C و HTML استفاده می‌کند. کدهای Razor را می‌توان در فایل‌های cshtml. نوشت که عموما مخصوص صفحات و Viewها هستند و یا در فایل‌های razor. که برای توسعه‌ی کامپوننت‌های Balzor بکار گرفته می‌شوند. در اینجا مهم نیست که پسوند فایل مورد استفاده چیست؛ چون اصول razor بکار گرفته شده در آن‌ها یکی است. البته در اینجا تاکید ما بیشتر بر روی فایل‌های razor. است که در برنامه‌های مبتنی بر Blazor بکار گرفته می‌شوند.


ایجاد یک پروژه‌ی جدید Blazor WASM

برای پیاده سازی و اجرای مثال‌های این قسمت، نیاز به یک پروژه‌ی جدید Blazor WASM را داریم که می‌توان آن‌را با اجرای دستور dotnet new blazorwasm --hosted در یک پوشه‌ی خالی، ایجاد کرد.

یک نکته: دستور فوق به همراه یک سری پارامتر اختیاری مانند hosted-- نیز هست. برای مشاهده‌ی لیست آن‌ها دستور dotnet new blazorwasm --help را صادر کنید. برای مثال ذکر پارامتر hosted-- سبب می‌شود تا یک ASP.NET Core host نیز برای Blazor WebAssembly app ایجاد شده تولید شود.

حالت hosted-- آن یک چنین ساختاری را دارد که از سه پروژه و پوشه‌ی Client ،Server و Shared تشکیل می‌شود:


در اینجا یک پروژه‌ی خالی WASM ایجاد شده که برخلاف حالت معمولی dotnet new blazorwasm که در قسمت قبل آن‌را بررسی کردیم، دیگر از فایل استاتیک wwwroot\sample-data\weather.json در آن خبری نیست. بجای آن، یک پروژه‌ی استاندارد ASP.NET Core Web API را در پوشه‌ی جدید Server ایجاد کرده که کار ارائه‌ی اطلاعات این سرویس آب و هوا را انجام می‌دهد و برنامه‌ی WASM ایجاد شده، این اطلاعات را توسط HTTP Client خود، از سرور Web API دریافت می‌کند.

بنابراین اگر مدل برنامه‌ای که قصد دارید تهیه کنید، ترکیبی از یک Web API و WASM است، روش hosted--، آغاز آن‌را بسیار ساده می‌کند.

نکته: روش اجرای این نوع برنامه‌ها با اجرای دستور dotnet run در داخل پوشه‌ی Server پروژه، انجام می‌شود. با اینکار هم سرور ASP.NET Core آغاز می‌شود و هم برنامه‌ی WASM توسط آن ارائه می‌گردد. در این حالت اگر آدرس https://localhost:5001 را در مرورگر باز کنیم، هم قسمت‌های بدون نیاز به سرور پروژه‌ی WASM قابل دسترسی است (مانند کار با شمارشگر آن) و هم قسمت دریافت اطلاعات از سرور آن، در منوی Fetch Data.


شروع به کار با Razor

پس از ایجاد یک پروژه‌ی جدید WASM، به فایل Client\Pages\Index.razor آن مراجعه کرده و محتوای پیش‌فرض آن‌را بجز سطر اول زیر، حذف می‌کنیم:
@page "/"
این سطر، بیانگر مسیریابی منتهی به کامپوننت جاری است. یعنی با گشودن برنامه‌ی WASM در مرورگر و مراجعه به ریشه‌ی سایت، محتوای این کامپوننت را مشاهده خواهیم کرد.
در فایل‌های razor. می‌توان ترکیبی از کدهای #C و HTML را نوشت. برای مثال:
@page "/"

<p>Hello, @name</p>

@code
{
    string name = "Vahid N.";
}
در اینجا قصد داریم مقدار یک متغیر را در یک پاراگراف درج کنیم. به همین جهت برای تعریف آن و شروع به کدنویسی می‌توان با تعریف یک قطعه کد که در فایل‌های razor با code@ شروع می‌شود، اینکار را انجام داد. در این قطعه کد، نوشتن هر نوع کد #C ای مجاز است که نمونه‌ای از آن‌را در اینجا با تعریف یک متغیر مشاهده می‌کنید. اکنون برای درج مقدار این متغیر در بین کدهای HTML از حرف @ استفاده می‌کنیم؛ مانند name@ در اینجا. نمونه‌ای از خروجی تغییرات فوق را در تصویر زیر مشاهده می‌کنید:


یک نکته: با توجه به اینکه تغییرات زیادی را در فایل جاری اعمال خواهیم کرد، بهتر است برنامه را با دستور dotnet watch run اجرا کرد، تا این تغییرات را تحت نظر قرار داده و آن‌ها را به صورت خودکار کامپایل کند. به این صورت دیگر نیازی نخواهد بود به ازای هر تغییر، یکبار دستور dotnet run اجرا شود.

در زمان درج متغیرهای #C در بین کدهای HTML توسط razor، استفاده از تمام متدهای الحاقی زبان #C نیز مجاز هستند؛ مانند:
 <p>Hello, @name.ToUpper()</p>
بنابراین درج حرف @ در بین کدهای HTML به این معنا است که به کامپایلر razor اعلام می‌کنیم، پس از این حرف، هر عبارتی که قرار می‌گیرد، یک عبارت معتبر #C است.

یا حتی می‌توان یک متد جدید را مانند CustomToUpper در قطعه کد razor، تعریف کرد و از آن به صورت زیر استفاده نمود:
@page "/"

<p>Hello, @name.ToUpper()</p>
<p>Hello, @CustomToUpper(name)</p>

@code
{
    string name = "Vahid N.";

    string CustomToUpper(string value) => value.ToUpper();
}
در این مثال‌ها، ابتدای عبارت #C تعریف شده با حرف @ شروع می‌شود و انتهای آن‌را خود کامپایلر razor بر اساس بسته شدن تگ p تعریف شده، تشخیص می‌دهد. اما اگر قصد داشته باشیم برای مثال جمع دو عدد را در اینجا محاسبه کنیم چطور؟
<p>Let's add 2 + 2 : @2 + 2 </p>
در این حالت امکان تشخیص ابتدا و انتهای عبارت #C توسط کامپایلر میسر نیست. برای رفع این مشکل می‌توان از پرانتزها استفاده کرد:
<p>Let's add 2 + 2 : @(2 + 2) </p>
نمونه‌ی دیگر نیاز به تعریف ابتدا و انتهای یک قطعه کد، در حین تعریف مدیریت کنندگان رویدادها است:
<button @onclick="@(()=>Console.WriteLine("Test"))">Click me</button>
در اینجا onclick@ مشخص می‌کند که با کلیک بر روی این دکمه قرار است قطعه کد #C ای اجرا شود. سپس با استفاده از ()@ محدوده‌ی این قطعه کد، مشخص می‌شود و اکنون در داخل آن می‌توان یک anonymous function را تعریف کرد که خروجی آن را در قسمت console ابزارهای توسعه دهندگان مرورگر می‌توان مشاهده کرد:


در اینجا اگر از Console.WriteLine("Test")@ استفاده می‌شد، به معنای انتساب یک رشته‌ی محاسبه شده به رویداد onclick بود که مجاز نیست.
روش دیگر انجام اینکار به صورت زیر است:
@page "/"

<button @onclick="@WriteLog">Click me 2</button>

@code
{
    void WriteLog()
    {
        Console.WriteLine("Test");
    }
}
می‌توان یک متد void را تعریف کرد و سپس فقط نام آن‌را توسط @ به onlick انتساب داد. ذکر این نام، اشاره‌گری خواهد بود به متد اجرا نشده‌ی WriteLog. در این حالت اگر نیاز به ارسال پارامتری به متد WriteLog بود، چطور؟
@page "/"

<button @onclick="@(()=>WriteLogWithParam("Test 3"))">Click me 3</button>

@code
{
    void WriteLogWithParam(string value)
    {
        Console.WriteLine(value);
    }
}
در این حالت نیز می‌توان از روش بکارگیری anonymous function‌ها برای تعریف پارامتر استفاده کرد.

یک نکته: اگر به اشتباه بجای WriteLogWithParam، همان WriteLog قبلی را بنویسیم، کامپایلر (در حال اجرای توسط دستور dotnet watch run) خطای زیر را نمایش می‌دهد؛ پیش از اینکه برنامه در مرورگر اجرا شود:
BlazorRazorSample\Client\Pages\Index.razor(12,25): error CS1501: No overload for method 'WriteLog' takes 1 arguments


امکان تعریف کلاس‌ها در فایل‌های razor.

در فایل‌های razor.، محدود به تعریف یک سری متدها و متغیرهای ساده نیستیم. در اینجا امکان تعریف کلاس‌ها نیز وجود دارد و همچنین می‌توان از کلاس‌های خارجی (کلاس‌هایی که خارج از فایل razor جاری تعریف شده‌اند) نیز استفاده کرد.
@page "/"

<p>Hello, @StringUtils.MyCustomToUpper(name)</p>

@code
{
    public class StringUtils
    {
        public static string MyCustomToUpper(string value) => value.ToUpper();
    }
}
برای نمونه در اینجا یک کلاس کمکی را جهت تعریف متد MyCustomToUpper، اضافه کرده‌ایم. در ادامه نحوه‌ی استفاده از این متد را در پاراگراف تعریف شده، مشاهده می‌کنید که همانند کار با کلاس و متدهای متداول #C است.
البته این کلاس را تنها می‌توان داخل همین کامپوننت استفاده کرد. برای اینکه بتوان از امکانات این کلاس، در سایر کامپوننت‌ها نیز استفاده کرد، می‌توان آن‌را در پروژه‌ی Shared قرار داد. اگر به تصویر ابتدای مطلب جاری دقت کنید، سه پروژه ایجاد شده‌است:
الف) پروژه‌ی کلاینت: که همان WASM است.
ب) پروژه‌ی سرور: که یک پروژه‌ی ASP.NET Core Web API ارائه کننده‌ی سرویس و API آب و هوا است و همچنین هاست کننده‌ی WASM ما.
ج) پروژه‌ی Shared: کدهای این پروژه، بین هر دو پروژه به اشتراک گذاشته می‌شوند و برای مثال محل مناسبی است برای تعریف DTO ها. برای نمونه WeatherForecast.cs قرار گرفته‌ی در آن، DTO یا data transfer object سرویس API برنامه است که قرار است به کلاینت بازگشت داده شود. به این ترتیب دیگر نیازی نخواهد بود تا این تعاریف را در پروژه‌های سرور و کلاینت تکرار کنیم و می‌توان کدهای اینگونه را به اشتراک گذاشت.
کاربرد دیگر آن تعریف کلاس‌های کمکی است؛ مانند StringUtils فوق. به همین به پروژه‌ی Shared مراجعه کرده و کلاس StringUtils را به صورت زیر در آن تعریف می‌کنیم (و یا حتی می‌توان این قطعه کد را داخل یک پوشه‌ی جدید، در همان پروژه‌ی WASM نیز قرار داد):
namespace BlazorRazorSample.Shared
{
    public class StringUtils
    {
        public static string MyNewCustomToUpper(string value) => value.ToUpper();
    }
}
اگر به فایل‌های csproj دو پروژه‌ی سرور و کلاینت جاری مراجعه کنیم، از پیش، مدخلی را به فایل Shared\BlazorRazorSample.Shared.csproj دارند. بنابراین جهت معرفی این اسمبلی به آن‌ها، نیاز به کار خاصی نیست و از پیش، ارجاعی به آن تعریف شده‌است.

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

@using BlazorRazorSample.Shared

<p>Hello, @StringUtils.MyNewCustomToUpper(name)</p>
ابتدا فضای نام این کلاس را با استفاده از using@ مشخص می‌کنیم و سپس امکان دسترسی به امکانات آن میسر می‌شود.

یک نکته: می‌توان به فایل Client\_Imports.razor مراجعه و مدخل زیر را به انتهای آن اضافه کرد:
@using BlazorRazorSample.Shared
به این ترتیب دیگر نیازی به ذکر این using@ تکراری، در هیچکدام از فایل‌های razor. پروژه‌ی کلاینت نخواهد بود؛ چون تعاریف درج شده‌ی در فایل Client\_Imports.razor سراسری هستند.


کار با حلقه‌ها در فایل‌های razor.

همانطور که عنوان شد، یکی از کاربردهای پروژه‌ی Shared، امکان به اشتراک گذاشتن مدل‌ها، در برنامه‌های کلاینت و سرور است. برای مثال یک پوشه‌ی جدید Models را در این پروژه ایجاد کرده و کلاس MovieDto را به صورت زیر در آن تعریف می‌کنیم:
using System;

namespace BlazorRazorSample.Shared.Models
{
    public class MovieDto
    {
        public string Title { set; get; }

        public DateTime ReleaseDate { set; get; }
    }
}
سپس به فایل Client\_Imports.razor مراجعه کرده و فضای نام این پوشه را اضافه می‌کنیم؛ تا دیگر نیازی به تکرار آن در تمام فایل‌های razor. برنامه‌ی کلاینت نباشد:
@using BlazorRazorSample.Shared.Models
اکنون می‌خواهیم لیستی از فیلم‌ها را در فایل Client\Pages\Index.razor نمایش دهیم:
@page "/"

<div>
    <h3>Movies</h3>
    @foreach(var movie in movies)
    {
        <p>Title: <b>@movie.Title</b></p>
        <p>ReleaseDate: @movie.ReleaseDate.ToString("dd MMM yyyy")</p>
    }
</div>

@code
{
    List<MovieDto> movies = new List<MovieDto>
    {
        new MovieDto
        {
            Title = "Movie 1",
            ReleaseDate = DateTime.Now.AddYears(-1)
        },
        new MovieDto
        {
            Title = "Movie 2",
            ReleaseDate = DateTime.Now.AddYears(-2)
        },
        new MovieDto
        {
            Title = "Movie 3",
            ReleaseDate = DateTime.Now.AddYears(-3)
        }
    };
}
در اینجا در ابتدا لیستی از MovieDto‌ها در قسمت code@ تعریف شده و سپس روش استفاده‌ی از یک حلقه‌ی foreach سی‌شارپ را در کدهای razor نوشته شده، مشاهده می‌کنید که این خروجی را ایجاد می‌کند:


یک نکته: در حین تعریف فیلدهای code@، امکان استفاده‌ی از var وجود ندارد؛ مگر اینکه از آن بخواهیم در داخل بدنه‌ی یک متد استفاده کنیم.

و یا نمونه‌ی دیگری از حلقه‌های #‍C مانند for را می‌توان به صورت زیر تعریف کرد:
    @for(var i = 0; i < movies.Count; i++)
    {
        <div style="background-color: @(i % 2 == 0 ? "blue" : "red")">
            <p>Title: <b>@movies[i].Title</b></p>
            <p>ReleaseDate: @movies[i].ReleaseDate.ToString("dd MMM yyyy")</p>
        </div>
    }
در اینجا روش تغییر پویای background-color هر ردیف را نیز به کمک کدهای razor، مشاهده می‌کنید. اگر شماره‌ی ردیفی زوج بود، با آبی نمایش داده می‌شود؛ در غیراینصورت با قرمز. در اینجا نیز از ()@ برای تعیین محدوده‌ی کدهای #C نوشته شده، کمک گرفته‌ایم.


نمایش شرطی عبارات در فایل‌های razor.

اگر به مثال توکار Client\Pages\FetchData.razor مراجعه کنیم (مربوط به حالت host-- که در ابتدای مطلب عنوان شد)، کدهای زیر قابل مشاهده هستند:
@page "/fetchdata"
@using BlazorRazorSample.Shared
@inject HttpClient Http

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</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 WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

}
در این مثال، روش کار با یک سرویس تزریق شده‌ی async که قرار است از Web API اطلاعاتی را دریافت کند، مشاهده می‌کنید. در اینجا برخلاف مثال قبلی ما، از روال رویدادگردان OnInitializedAsync برای مقدار دهی لیست یا آرایه‌ای از اطلاعات وضعیت هوا استفاده شده‌است (و نه به صورت مستقیم در یک فیلد قسمت code@). این مورد جزو life-cycle‌های کامپوننت‌های razor است که در قسمت‌های بعد بیشتر بررسی خواهد شد. متد OnInitializedAsync برای بارگذاری اطلاعات یک سرویس از راه دور استفاده می‌شود و در اولین بار اجرای کامپوننت فراخوانی خواهد شد. نکته‌ی مهمی که در اینجا وجود دارد، نال بودن فیلد forecasts در زمان رندر اولیه‌ی کامپوننت جاری است؛ از این جهت که کار دریافت اطلاعات از سرور زمان‌بر است ولی رندر کامپوننت، به صورت آنی صورت می‌گیرد. در این حالت زمانیکه نوبت به اجرای foreach (var forecast in forecasts)@ می‌رسد، برنامه با یک استثنای نال بودن forecasts، متوقف خواهد شد؛ چون هنوز کار OnInitializedAsync به پایان نرسیده‌است:


 برای رفع این مشکل، ابتدا یک if@ مشاهده می‌شود، تا نال بودن forecasts را بررسی کند:
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
و همچنین عبارت در حال بارگذاری را نمایش می‌دهد. سپس در قسمت else آن، نمایش اطلاعات دریافت شده را توسط یک حلقه‌ی foreach مشاهده می‌کنید. با مقدار دهی forecasts در متد OnInitializedAsync، مجددا کار رندر جدول انجام خواهد شد.


روش نمایش عبارات HTML در فایل‌های razor.

فرض کنید عنوان اول فیلم مثال جاری، به همراه یک تگ HTML هم هست:
new MovieDto
{
   Title = "<i>Movie 1</i>",
   ReleaseDate = DateTime.Now.AddYears(-1)
},
در این حالت اگر برنامه را اجرا کنیم، خروجی آن دقیقا به صورت <Title: <i>Movie 1</i خواهد بود. این مورد به دلایل امنیتی انجام شده‌است. اگر پیشتر تگ‌های HTML را تمیز کرده‌اید و مطمئن هستید که خطری را ایجاد نمی‌کنند، می‌توانید با استفاده از روش زیر، آن‌ها را رندر کرد:
<p>Title: <b>@((MarkupString)movie.Title)</b></p>


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-03.zip
برای اجرای آن وارد پوشه‌ی Server شده و دستور dotnet run را اجرا کنید.
مطالب
فایل‌های chm و مشکل فارسی - قسمت اول

همانطور که مطلع هستید از ویندوز ویستا به بعد، فرمت قدیمی فایل‌های راهنمای ویندوز (فایل‌های hlp) منسوخ شده تلقی می‌شود و فرمت پیشنهادی، chm است. نرم افزارهای زیادی برای تهیه فایل‌های compiled html help یا همان chm های معروف وجود دارند که معروفترین آن‌ها برنامه‌ی Help & Manual است.
اما تمام این برنامه‌ها در حقیقت پوسته‌ای هستند برای برنامه‌ی رایگان html help work shop مایکروسافت و در نهایت از کامپایلر آن استفاده می‌کنند. بنابراین چرا از برنامه‌ی رایگان اصلی استفاده نشود؟

اگر تا به حال با html help work shop کار نکرده‌اید، در ادامه مروری سریع بر آن خواهیم داشت:

الف) درست کردن فایل‌های صفحات راهنما
برنامه‌ی Help & Manual ایی که معرفی شد و تمام نمونه‌های مشابه آن، تنها کار مهمی را که انجام می‌دهند این است که شما را از یک html editor بی‌نیاز می‌کنند. بنابراین زمانیکه می‌خواهیم از برنامه‌ی اصلی html help work shop استفاده کنیم نیاز به یک html editor خارجی برای تهیه فایل‌های راهنما خواهیم داشت. مثلا مرحوم front page یا نگارش جدید آن به نام Microsoft expression web یا Dreamweaver یا Aptana یا حتی notepad !
در اینجا تنها مشخص کردن نوع encoding نمایش صفحه برای صحیح نمایش داده شدن متون فارسی کافی است. اما این تمام ماجرا نیست.

ب) کامپایل کردن فایل‌های راهنمای ایجاد شده
برای این منظور بجای آپلود بیش از 30 تصویر جهت توضیحات قدم به قدم نحوه‌ی انجام این‌کار، یک فایل ویدیویی درست کرده‌ام که آن‌‌را از آدرس زیر می‌توانید دریافت کنید.
دریافت فایل

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

در قسمت دوم، نحوه‌ی رفع این دو مشکل (مشکل جستجوی عبارات فارسی و مشکل عنوان‌های به هم ریخته) را بررسی خواهیم کرد.

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

نظرات مطالب
طراحی گزارش در Stimulsoft Reports.Net – بخش 2
سلام. این مشکل تو گزارش ساز تلریک و کریستال هم وجود داره. تو کریستال قبل از نمایش می‌شد گزارش رو تبدیل به pdf کنیم بعد نمایش بدیم که فقط تو این حالت فونت‌های سرور نمایش داده می‌شدند. تو استیمول و تلریک فونت‌ها از قسمت کلاینت خونده میشن. تنها راه ممکن اینه که فیلدها به صورت export as image باشن که اینکار هم زیاد جالب نیست. به نظر من استیمول واسه وب اصلا مناسب نیست و بهتره که تو ویندوز اپلیکیشن ازش استفاده بشه. اگه با تلریک مقایسش بکنین فرقشونو میبینین
نظرات مطالب
ASP.NET MVC #20
- این مثال هم paging خودکار داره و هم sorting خودکار. به علاوه پارامترهای شماره صفحه و فیلد سورت شونده هم در اکشن متدها قابل دریافت است که ذکر شده.
- اگر می‌خواهید با ajax کار کنید باید ajaxUpdateContainerId آن‌را مقدار دهی کرده و سپس از Ajax.ActionLink استفاده کنید. یک مثال در اینجا: (^)
مطالب
شیوه‌نامه‌های مقدماتی بوت استرپ 4
وقتی صفحه‌ی وبی را باز می‌کنید، تنظیمات بسیاری بر روی ظاهر آن تاثیرگذار هستند. برای مثال خود مرورگر دارای تنظیماتی است که بر روی ظاهر پیش‌فرض عناوین و عناصر مختلف قرار گرفته شده‌ی بر روی صفحه تاثیر گذار است. به این موارد Browser Styles گفته می‌شود که با Custom Styles ما قابلیت بازنویسی را دارند. در این بین، شیوه‌نامه‌های بوت استرپ، بین Browser Styles و شیوه‌نامه‌های سفارشی ما قرار می‌گیرند تا ظاهر بهتری را برای عناصر مختلف صفحه ارائه دهند.


تایپوگرافی مقدماتی بوت استرپ 4

شیوه‌نامه‌های همراه با بوت استرپ، رفتار و تنظیمات پیش‌فرض مرورگر را بازنویسی می‌کنند. این بازنویسی با فایل node_modules\bootstrap\scss\_reboot.scss شروع می‌شود. اگر مجموعه‌ی بوت استرپ را توسط روش معرفی شده‌ی در مطلب «روش‌های مختلف دریافت و نصب بوت استرپ 4» دریافت کرده باشید، کدهای کامل SASS آن، در پوشه‌ی scss این مجموعه، موجود هستند که یکی از آن‌ها فایل reboot است. کار آن نرمال سازی شیوه‌نامه‌ها، به نحوی است که در مرورگرها مختلف و همچنین وسایل نمایشی متفاوت، یکسان به نظر برسند:
 - برای مثال در این فایل از روش اندازه گیری rem استفاده شده‌است تا مدیریت اندازه‌های آن در سکوهای کاری مختلف قابل کنترل شود.
 - در اینجا از margin-top به طور کامل صرفنظر شده‌است، تا بتوان اندازه‌گیری فواصل بین عناصر را بهتر محاسبه کرد. بوت استرپ 4 تنها یک margin را در پایین تمام عناصر صفحه، تنظیم می‌کند. بنابراین آگاهی از وجود این پیش‌فرض، تنظیم فواصل بین عناصر را نیز ساده‌تر می‌کند.
 - در این فایل در همه‌جا از خاصیت inherit استفاده شده‌است تا امکان بازنویسی شیوه‌نامه‌های آن توسط custom styles ما ساده‌تر شود.
 - پیش‌فرض دیگری که در این نگارش از بوت استرپ تنظیم شده‌است، border-box می‌باشد. به این ترتیب اندازه گیری عرض عناصر ساده‌تر می‌شوند. برای مثال اگر عرض یک div را مساوی 200px قرار دهید، یک padding پیش‌فرض نیز برای آن درنظر گرفته شده‌است و padding سفارشی تنظیم شده‌ی برای آن بی‌اثر خواهد بود.
 - در این نگارش، فونت پیش‌فرض صفحه، به فونت پیش‌فرض سیستم تنظیم شده‌است و نه فونت از پیش تعیین شده‌ی خاصی. از این جهت که این قلم‌های سیستمی، دارای ویژگی‌های خاصی هستند که آن‌ها را برای سکوهای کاری مختلف، منحصربفرد می‌کنند.

مثال: نمایش تاثیر بوت استرپ 4 بر روی تایپوگرافی پیش‌فرض مرورگر
<body>
    <div class="container">
        <section class="content" id="mission">
            <h1>Our Commitment <small>to you</small></h1>
            <p>Wisdom Pet Medicine strives to blend the best in traditional and
                <em>alternative medicine</em> in the <strong>diagnosis and
                    treatment</strong> of companion animals including dogs,
                cats, birds, reptiles, rodents, and fish. We apply the wisdom
                garnered in the <mark>centuries old tradition</mark> of
                veterinary medicine, to find the safest treatments
                and&nbsp;cures.</p>
            <p>We strive to be your pet's medical <del>staff</del> experts from
                youth through the senior years. <small>We build preventative
                    health care plans for each and every one of our patients,
                    based on breed, age, and sex, so that your pet receives the
                    most appropriate care at crucial milestones.</small> We
                want to give your pet a long and healthy&nbsp;life.</p>
        </section>

        <section class="content" id="services">
            <h2>Exotic Pets</h2>
            <p>We offer <strong>specialized</strong> care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>

            <h3>Grooming</h3>
            <p>Our therapeutic <span>grooming</span> treatments help battle
                fleas, allergic dermatitis, and other challenging skin
                conditions.</p>

            <h4>General Health</h4>
            <p>Wellness and senior exams, ultrasound, x-ray, and dental
                cleanings are just a few of our general health services.</p>

            <h5>Nutrition</h5>
            <p>Let our nutrition experts review your pet's diet and prescribe a
                custom nutrition plan for optimum health and disease
                prevention.</p>

            <h6>Pest Control</h6>
            <p>We offer the latest advances in safe and effective prevention
                and treatment of fleas, ticks, worms, heart worm, and other
                parasites.</p>

            <h2>Vaccinations</h2>
            <p>Our veterinarians are experienced in modern vaccination
                protocols that prevent many of the deadliest diseases in pets.</p>
        </section>
    </div>
</body>
با این خروجی:

با اعمال بوت استرپ


بدون اعمال بوت استرپ


در اینجا دو تصویر راملاحظه می‌کنید؛ یکی با اعمال bootstrap.min.css به صفحه‌است و دیگری با حذف آن از صفحه. به این ترتیب مشاهده می‌کنید که صرفا اعمال بوت استرپ به یک صفحه‌ی متداول، کیفیت نمایش آن‌را با بازنویسی شیوه‌نامه‌ی پیش‌فرض مرورگر، به نحو قابل ملاحظه‌ای بهبود بخشیده‌است و آن‌را زیباتر کرده‌است.
در اینجا تنها المان بوت استرپی که به صفحه اضافه شده‌است و جزو استانداردهای HTML نیست، یک div با کلاس container است:
<body>
    <div class="container">
کل محتوای صفحه جهت اعمال شیوه‌نامه‌های بوت استرپ، داخل این div قرار می‌گیرند. اولین تاثیر آن واکنشگرا کردن صفحه‌است و همچنین یک padding را نیز به قسمت‌های چپ و راست صفحه اضافه کرده‌است.
در این مثال تاثیر بوت استرپ را بر روی شیوه‌نامه‌های پیش‌فرض خصوصا  h1 تا h6، مشاهده می‌کنید.
روش دیگر تعریف headings در اینجا، استفاده از کلاس‌هایی با نام‌های مشابه است:
<div class="h1">Test div class H1</div>
علاوه بر آن، کلاس display نیز در اینجا برای تعیین اندازه‌ی headings سفارشی پیش بینی شده‌است که می‌توان از عدد 1 تا 4 را توسط آن تنظیم کرد:
<div class="display-1">Test div class display-1</div>
با این خروجی و اندازه در مقایسه با headings استاندارد که امکان تعریف تیترهایی بزرگ‌تر از اندازه‌های متداول را میسر می‌کنند:


همچنین اگر نیاز به بزرگتر نمایش دادن متن قسمت ابتدایی صفحه وجود داشت، می‌توان از کلاس Lead استفاده کرد:
<p class="lead">Testing a lead class</p>



کلاس‌های کمکی کار با متون در بوت استرپ 4

بوت استرپ 4 به همراه تعدادی کلاس کمکی کار با متون است که نیازهای متداول تایپوگرافی را برآورده می‌کنند:

1) کلاس‌های کمکی محل قرارگیری متون
- کلاس text-justify کار کشیدن و متناسب کردن یک پاراگراف را با گوشه‌های سمت چپ و راست صفحه انجام می‌دهد.
- کلاس text-nowrap از شکسته شدن متن به چندین سطر جلوگیری می‌کند. برای مثال می‌تواند برای نمایش کدها مناسب باشد.
- کلاس متغیر text-xx-pos برای تعیین محل قرارگیری متن کاربرد دارد:
در اینجا ذکر xx اختیاری است و می‌تواند sm، برای اندازه‌های صفحه‌ی بیشتر از 576px و یا md، برای اندازه‌های صفحه‌ی بیشتر از 768px و یا lg، برای اندازه‌های صفحه‌ی بیشتر از 992px و یا xl، برای اندازه‌های صفحه‌ی بیشتر از 1200px باشد. این اندازه‌های یاد شده را در ادامه بیشتر مشاهده خواهید کرد.
همچنین pos می‌تواند left ،center و یا right باشد.
برای مثال کلاس text-sm-center به این معنا است که متن مدنظر در break-point ایی به نام sm، یعنی با اندازه‌ی صفحه‌ی بیشتر از 576px، در وسط صفحه نمایش داده خواهد شد.

2) کلاس‌های نمایش upper-case و lower-case حروف
- کلاس text-lowercase کار نمایش lower-case کل یک پاراگراف اعمالی را انجام می‌دهد.
- کلاس text-uppercase کار نمایش upper-case کل یک پاراگراف اعمالی را انجام می‌دهد.
- کلاس text-capitalize، اولین حرف هر واژه را به صورت بزرگ نمایش می‌دهد.

3) کلاس‌های شیوه‌ی نمایش متون
کلاس font-weight-bold، کلاس font-weight-normal و کلاس font-italic نمایش ضخیم، عادی و یا italic متن را سبب می‌شوند.

مثال: بررسی تاثیر کلاس‌های کمکی کار با متون در بوت استرپ 4
<body>
    <div class="container">
        <section class="content" id="mission">
            <h1 class="text-center text-sm-right text-md-left text-uppercase">Our
                Commitment</h1>
            <p class="lead text-justify">Wisdom Pet Medicine strives to blend
                the best in
                traditional and <em>alternative medicine</em> in the <strong>diagnosis
                    and treatment</strong> of companion animals including dogs,
                cats, birds, reptiles, rodents, and fish. We apply the wisdom
                garnered in the <mark>centuries old tradition</mark> of
                veterinary medicine, to find the safest treatments
                and&nbsp;cures.</p>
            <p class="text-nowrap text-capitalize">We strive to be your pet's
                medical <del>staff</del>
                experts from
                youth through the senior years. <small>We build preventative
                    health care plans for each and every one of our patients,
                    based on breed, age, and sex, so that your pet receives the
                    most appropriate care at crucial milestones.</small> We
                want to give your pet a long and healthy&nbsp;life.</p>
        </section>

        <section class="content" id="services">
            <div class="display-4">Exotic Pets</div>
            <p>We <span class="font-weight-bold">offer</span> <strong class="font-weight-normal">specialized</strong>
                care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>

            <h3 class="text-left text-md-center text-sm-right">Grooming</h3>
            <p>Our therapeutic <span>grooming</span> treatments help battle
                fleas, allergic dermatitis, and other challenging skin
                conditions.</p>

            <h4>General Health</h4>
            <p>Wellness and senior exams, ultrasound, x-ray, and dental
                cleanings are just a few of our general health services.</p>

            <h5>Nutrition</h5>
            <p>Let our nutrition experts review your pet's diet and prescribe a
                custom nutrition plan for optimum health and disease
                prevention.</p>

            <h6>Pest Control</h6>
            <p>We offer the latest advances in safe and effective prevention
                and treatment of fleas, ticks, worms, heart worm, and other
                parasites.</p>

            <h2>Vaccinations</h2>
            <p>Our veterinarians are experienced in modern vaccination
                protocols that prevent many of the deadliest diseases in pets.</p>
        </section>
    </div>
</body>
با این خروجی


در اینجا اگر کلاس text-right را به heading اضافه کنیم:
<h1 class="text-right">Our Commitment <small>to you</small></h1>
چنین خروجی حاصل می‌شود:


و یا می‌توان این کلاس‌ها را با هم ترکیب کرد:
<h1 class="text-center text-sm-right">Our Commitment <small>to you</small></h1>
این ترکیب به این معنا است:
- متن h1 در حالت عادی در وسط صفحه نمایش داده شود.


- اما متن مدنظر در break-point ایی به نام sm، یعنی با اندازه‌ی صفحه‌ی بیشتر از 576px، در سمت راست صفحه نمایش داده خواهد شد.


این اعداد توسط افزونه‌ی ViewPort نمایش داده شده‌اند.


همچنین تاثیر text-justify را نیز به اولین پاراگراف
<p class="lead text-justify">
به صورت ذیل مشاهده می‌کنید که در مقایسه با تصویر قبلی، سبب کشیده شدن متن و تنظیم آن با سمت راست و چپ صفحه شده‌است:


و یا اگر text-nowrap را به پاراگرافی اعمال کنیم:
<p class="text-nowrap">


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


کلاس‌های کمکی کار با لیست‌ها و نقل قول‌ها در بوت استرپ 4

بوت استرپ 4 به همراه کلاس‌هایی کمکی برای کار با لیست‌ها و نقل قول‌ها است؛ مانند:
- کلاس list-unstyled سبب حذف bullets از یک لیست می‌شود.
- برای ایجاد لیست‌های Inline می‌توان از کلاس list-inline بر روی المان UL و سپس list-inline-item بر روی هر LI آن، کمک گرفت.
<body>
    <div class="container">
        <section class="content" id="services">
            <h2>Exotic Pets</h2>
            <p>We offer <strong>specialized</strong> care for <em>reptiles,
                    rodents, birds,</em> and other exotic pets.</p>
            <ul class="list-unstyled">
                <li>Grooming</li>
                <li>General Health</li>
                <li>Nutrition</li>
                <li>Pest Control</li>
                <li>Vaccinations</li>
            </ul>

            <ul class="list-inline">
                <li class="list-inline-item">Grooming</li>
                <li class="list-inline-item">General Health</li>
                <li class="list-inline-item">Nutrition</li>
                <li class="list-inline-item">Pest Control</li>
                <li class="list-inline-item">Vaccinations</li>
            </ul>
        </section>
    </div>
</body>
در این مثال نحوه‌ی حذف bullets و همچنین inline تعریف کردن دو لیست را مشاهده می‌کنید؛ با این خروجی:


در حالت لیست inline، آیتم‌های لیست از چپ به راست در یک سطر نمایش داده می‌شوند. برای مثال می‌تواند برای نمایش breadcrumbs در یک سایت مناسب باشد.
همچنین برای نمایش نقل قول‌ها می‌توان از کلاس blockquote و برای نمایش بهتر امضای آن از کلاس blockquote-footer استفاده کرد:
<body>
    <div class="container">
        <section class="content" id="testimonials">
            <h2>Testimonials</h2>

            <blockquote>
                During the summer, our rabbit, Tonto, began to have severe
                redness and itching on his belly and feet. I'm very thankful to
                the veterinarians and staff at Wisdom for the excellent care
                Tonto received, and for nipping his allergies in the bud, so to
                speak.
                Jane
            </blockquote>

            <blockquote class="blockquote text-right">
                When Samantha, our Siamese cat, began sleeping all the time and
                urinating excessively, we brought her to see the specialists at
                Wisdom. Now, two years later, Samantha is still free from any
                complications of diabetes, and her blood sugar regularly tests
                normal.
                <div class="blockquote-footer">
                    The McPhersons
                </div>
            </blockquote>
        </section>
    </div>
</body>


در اینجا دو blockquote را مشاهده می‌کنید. مورد اول بدون کلاس blockquote است و دومی به همراه این کلاس و یک footer تعریف شده‌است. همچنین می‌توان کلاس‌هایی مانند text-right را نیز به blockquote اضافه کرد.
البته در نگارش 4، حاشیه‌ی خاکستری blockquote که در نگارش سوم آن وجود داشت، حذف شده‌است.


کار با رنگ‌ها در بوت استرپ 4

بوت استرپ، به همراه تعدادی کلاس مخصوص رنگ‌ها است که از آن در همه جا استفاده می‌کند؛ مانند رنگ‌های دکمه‌ها، پس زمینه‌ها و متون.
1) کلاس‌های تعیین رنگ متون:


برای مثال در اینجا بجای Color می‌توان یکی از ثوابت ذیل آن‌را قید کرد؛ مانند text-primary و یا text-danger
این کلاس‌ها برای تعیین رنگ متون و همچنین لینک‌ها کاربرد دارند.


2) کلاس‌های تعیین رنگ پس زمینه:


در اینجا برای نمونه بجای Color می‌توان یکی از ثوابت ذیل آن‌را قید کرد؛ مانند bg-primary و یا bg-danger

مثال: اعمال رنگ‌های زمینه‌ای بوت استرپ
<body>
    <div class="container">
        <section class="content" id="services">
            <h2 class="text-danger">Our Mission</h2>
            <p class="bg-danger text-white">Wisdom Pet Medicine strives to
                blend the best in
                traditional and
                alternative medicine in the diagnosis and treatment of
                companion animals including dogs, cats, birds, reptiles,
                rodents, and fish. We apply the wisdom garnered in the
                centuries old tradition of veterinary medicine, to find the
                safest treatments and cures.</p>

            <ul>
                <li><a class="text-warning" href="#">Grooming</a></li>
                <li><a href="#">General Health</a></li>
                <li><a href="#">Nutrition</a></li>
                <li><a href="#">Pest Control</a></li>
                <li><a href="#">Vaccinations</a></li>
            </ul>

        </section>

        <section class="content" id="testimonials">
            <h2>Testimonials</h2>

            <blockquote class="blockquote bg-faded text-info">
                During the summer, our rabbit, Tonto, began to have severe
                redness and itching on his belly and feet. I'm very thankful to
                the veterinarians and staff at Wisdom for the excellent care
                Tonto received, and for nipping his allergies in the bud, so to
                speak.
                <div class="blockquote-footer">
                    Jane
                </div>
            </blockquote>
        </section>
    </div>
</body>
با این خروجی


در اینجا مثال‌هایی را از اعمال کلاس‌های رنگ‌های بوت استرپ مشاهده می‌کنید. همچنین امکان ترکیب آن‌ها مانند مثال زیر نیز وجود دارد:
<p class="bg-danger text-white">



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_02.zip
مطالب
تهیه گزارشات Crosstab به کمک LINQ - قسمت دوم

اگر به قسمت اول «تهیه گزارشات Crosstab به کمک LINQ» دقت کرده باشید، یک مشکل کوچک دارد و آن هم لزوم مشخص سازی دقیق ستون‌هایی است که می‌خواهیم در گزارش ظاهر شوند. مثلا دقیقا مشخص کنیم که نام واحد چیست یا دقیقا روز را مشخص کنیم. این مورد برای گزارش‌های کوچک مشکلی ندارد؛ ولی اگر همان مثال دوم را در نظر گرفته و بازه را کمی بیشتر کنیم، مثلا یک ماه، آن وقت باید حداقل 30 بار بنویسیم Day1IsPresent تا ... Day30IsPresent و یا اگر بازه‌ی گزارشگیری به اختیار کاربر باشد آن وقت چه باید کرد؟ مثلا یکبار 7 روز پایان ماه را انتخاب کند، یکبار 14 روز را، شاید یک بار هم مثلا 90 روز را مد نظر داشته باشد (تعداد ستون‌ها متغیر باشد یا به عبارتی Dynamic Crosstab نیاز است ایجاد شود).
برای حل این مساله، می‌توان از متد الحاقی زیر از سایت extensionmethod.net کمک گرفت:

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

namespace PivotExtensions
{
public static class Ext
{
public static Dictionary<TKey1, Dictionary<TKey2, TValue>>
Pivot<TSource, TKey1, TKey2, TValue>
(
this IEnumerable<TSource> source,
Func<TSource, TKey1> key1Selector,
Func<TSource, TKey2> key2Selector,
Func<IEnumerable<TSource>, TValue> aggregate
)
{
return source.GroupBy(key1Selector)
.Select(
key1Group => new
{
Key = key1Group.Key,
Value = key1Group.GroupBy(key2Selector)
.Select(
key2Group => new
{
K = key2Group.Key,
V = aggregate(key2Group)
})
.ToDictionary(e => e.K, o => o.V)
})
.ToDictionary(e => e.Key, o => o.Value);
}
}
}

در این متد:
key1Selector مشخص کننده ستون‌های ثابت و مشخص سمت راست یا چپ (بر اساس جهت صفحه) گزارش است. در سیستم‌های مختلف این ستون‌ها نام‌هایی مانند keyColumn ، leftColumn و Row Heading ممکن است داشته باشند.
key2Selector ستون‌های پویای گزارش را تشکیل می‌دهد. در سایر سیستم‌ها این پارامتر، pivotNameColumn ،VariableColumn ، topField و یا Column Heading هم نامیده می‌شود.
Aggregate در اینجا مشخص می‌کند که مقادیر ستون‌های پویای یاد شده چگونه باید محاسبه شوند.

با توجه به این متد، برای نمونه جهت حل مثال اول قسمت قبل خواهیم داشت:

var list = ExpenseDataSource.ExpensesDataSource();
var pivotList = list.Pivot(
x =>
new
{
x.Date.Year,
x.Date.Month
},
x1 => x1.Department,
x2 => x2.Sum(x => x.Expenses));

با خروجی


فایل LINQPad آن از اینجا قابل دریافت است.


و برای حل مثال دوم قسمت قبل می‌توان نوشت:

var list2 = StudentsStatDataSource.CreateWeeklyReportDataSource();
var lst = list2.Pivot(
x =>
new
{
x.Id,
x.Name
},
x1 => "Day " + x1.Date.Day,
x2 => x2.First().IsPresent);

با خروجی


فایل LINQPad آن از اینجا قابل دریافت است.

مطالب
روش بهینه نمایش عکس در Xamarin Forms
پیشاپیش از شما دعوت می‌کنم اگر از سایر روش‌های توسعه برنامه‌های موبایل چون Flutter ،React native و ... استفاده می‌کنید نیز این مقاله را مطالعه کنید؛ چرا که ایده کلی و نکات مهم آن می‌تواند برای شما نیز مفید باشد.

زمانیکه شما از طراح، کاری را می‌گیرید و فرضا می‌خواهید در صفحه‌ای از برنامه موبایل، یک لیوان را نمایش دهید، به شما یک فایل png یا svg تحویل داده می‌شود. اگر چه عموما روش‌هایی وجود دارند تا همان فایل را به صورت مستقیم در صفحه برنامه خود استفاده کنید، ولی این کار به چند دلیل توصیه نمی‌شود:
۱- دستگاه‌های مختلف، Resolution‌های متفاوتی دارند و سخت‌افزار بعضا ضعیف موبایل، باید عکس را برای نمایش بهینه، Scale کند که عملی است هدر دهنده پردازنده و حافظه.
۲- حتی وقتی عمل Scale انجام شد، وقتی صفحه مربوطه بسته شود و به صفحه دیگری برویم، در باز کردن دوباره صفحه مربوطه، این عمل هزینه بر مجددا رخ می‌دهد.

برای حل این مشکل، می‌توان دو قدم برداشت:
قدم اول اینکه عکس svg و یا png آن لیوان را در Scaleهای مختلف، از پیش ذخیره کرد. این کار خود دو اشکال دارد. یک اینکه اگر این عمل به صورت دستی انجام شود، احتمال اشتباه بالا می‌رود و انجام این کار برای Android/iOS/Windows و برای Scaleهای مختلف، برای هر عکس، نیاز به ذخیره لااقل یک دو جین عکس دارد. دوم اینکه با ذخیره‌سازی چند باره یک عکس در Scaleهای مختلف، حجم فایل نهایی پروژه بالا می‌رود.

برای خودکاری سازی این فرآیند دستی، در Xamarin Forms، React native و ... ابزارهایی تعبیه شده است که ResizetizerNT نمونه Xamarin Forms آن است که یک عکس svg یا png را از شما گرفته و در Scaleهای مختلف، برای Android/iOS/Windows ذخیره می‌کند و طبیعتا احتمال اشتباه کم می‌شود و کار بسیار ساده می‌شود.

برای حل مشکل سایز خروجی نهایی، در Android و Google Play Store فرمت جدیدی جایگزین apk شده است به نام aab یا Android App Bundle که با توجه به Resolution دستگاهی که در حال دانلود برنامه است، فایلی را برای وی ساخته و می‌فرستد که فقط عکس‌های با Scale مناسب در آن است؛ پس می‌توانیم لااقل در Android نگران حجم نباشیم.
البته تاثیر این‌کار در Performance آن‌چنان بالاست که بهتر است برای Storeهای متفرقه که از aab پشتیبانی نمی‌کنند و کماکان فقط از apk پشتیبانی می‌کنند نیز کماکان عکس در Scaleهای مختلف ذخیره شود.

در iOS نیز ابزار pngcrunch وجود دارد که عکس‌های png را بهینه و compress می‌کند و در کاهش حجم فایل نهایی مؤثر است. در Xamarin.iOS در قسمت تنظیمات پروژه، در قسمت iOS Build، زدن تیک Checkbox مربوطه با نام Optimize PNG images به معنای درخواست استفاده از این ابزار مفید است.

با این حال، حتی اگر عکس‌ها در Scaleهای مختلفی ذخیره شوند، مرحله‌ای وجود دارد که آن png، به bitmap قابل نمایش تبدیل می‌شود. این عمل نیز هزینه‌بر بوده و زمانیکه شما صفحه ای را بسته و مجددا باز می‌کنید نیز این عمل تکرار می‌شود. همچنین اگر در یک List view پنج آیتم را داشته باشید که یک عکس را دارند، این عمل پنج بار تکرار می‌شود.

در Android ابزاری معرفی شده‌است که Glide نام دارد و این ابزار، مرحله تبدیل png به bitmap قابل نمایش را cache می‌کند و دیگر باز و بسته کردن صفحات، یا استفاده از یک عکس در List view، اشکالی را ایجاد نمی‌کند. در Xamarin Forms با استفاده از GlideX.Forms، می‌توانید کاری کنید که در Android این بهینه سازی بسیار مؤثر صورت پذیرد و به Scroll نرم در List view و باز شدن سریع صفحات برسید. در iOS معادل همین کار را کتابخانه مطرح Nuke انجام می‌دهد که برای استفاده از آن در Xamarin Forms میتوانید از این Package استفاده کنید.
مزیت دیگر استفاده از GlideX.Forms و Nuke در این است که اگر جایی از برنامه، عکس‌هایی را از سرور دریافت و نمایش دهید (مثلا عکس مشتری‌ها در لیست مشتریان)، این دو ابزار عکس مربوطه را پس از Scale شدن و تبدیل شدن به Bitmap برای استفاده‌های بعدی Cache می‌کنند و سری بعد که نیاز به نمایش همان عکس می‌شود، به جای Scale شدن مجدد و تبدیل شدن به Bitmap دوباره، فقط از Cache محتوای آماده به نمایش به کاربر نمایش داده می‌شود.

در نهایت شما با چند خط تنظیمات ساده و نصب کردن چند package می‌توانید با داشتن یک svg یا png، آن‌را به بهینه‌ترین شکل ممکن در Android/iOS/Windows نمایش دهید.
این پروژه در Github تمامی این موارد را در کنار هم گردآوری کرده‌است که می‌توانید از سورس آن نیز برای درک بهتر موارد استفاده کنید.
مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت اول - پیاده سازی سمت سرور
یکی از نیازهای مهم هر برنامه‌ای، امکانات گزارشگیری و نمایش لیستی از اطلاعات است. به همین جهت در طی چند قسمت، قصد داریم یک گرید ساده را به همراه امکانات نمایش، صفحه بندی و مرتب سازی اطلاعات، تنها به کمک امکانات توکار Angular و ASP.NET Core تهیه کنیم.




تهیه مقدمات سمت سرور

مدلی که در تصویر فوق نمایش داده شده‌است، در سمت سرور چنین ساختاری را دارد:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Product
    {
        public int ProductId { set; get; }
        public string ProductName { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}

همچنین یک منبع ساده درون حافظه‌ای را نیز جهت بازگشت 1500 محصول تهیه کرده‌ایم. علت اینجا است که ساختار نهایی اطلاعات آن شبیه به ساختار اطلاعات حاصل از ORMها باشد و همچنین به سادگی قابلیت اجرا و بررسی را داشته باشد:
using System.Collections.Generic;

namespace AngularTemplateDrivenFormsLab.Models
{
    public static class ProductDataSource
    {
        private static readonly IList<Product> _cachedItems;
        static ProductDataSource()
        {
            _cachedItems = createProductsDataSource();
        }

        public static IList<Product> LatestProducts
        {
            get { return _cachedItems; }
        }

        private static IList<Product> createProductsDataSource()
        {
            var list = new List<Product>();
            for (var i = 0; i < 1500; i++)
            {
                list.Add(new Product
                {
                    ProductId = i + 1,
                    ProductName = "نام " + (i + 1),
                    IsAvailable = (i % 2 == 0),
                    Price = 1000 + i
                });
            }
            return list;
        }
    }
}


مشخص کردن قرارداد اطلاعات دریافتی از سمت کلاینت

زمانیکه کلاینت Angular برنامه، اطلاعاتی را به سمت سرور ارسال می‌کند، یک چنین ساختاری را دریافت خواهیم کرد:
 http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7
درخواست، به یک اکشن متد مشخص ارسال شده‌است و حاوی یک سری کوئری استرینگ مشخص کننده‌ی نام خاصیت یا فیلدی که قرار است مرتب سازی بر اساس آن صورت گیرد، صعودی و نزولی بودن این مرتب سازی، شماره صفحه‌ی درخواستی و تعداد آیتم‌های در هر صفحه، می‌باشد.
بنابراین اینترفیسی را دقیقا بر اساس نام کلیدهای همین کوئری استرینگ‌ها تهیه می‌کنیم:
    public interface IPagedQueryModel
    {
        string SortBy { get; set; }
        bool IsAscending { get; set; }
        int Page { get; set; }
        int PageSize { get; set; }
    }


کاهش کدهای تکراری صفحه بندی اطلاعات در سمت سرور

با تعریف این اینترفیس چند هدف را دنبال خواهیم کرد:
الف) استاندارد سازی نام خواصی که مدنظر هستند و اعمال یک دست آن‌ها به ViewModelهایی که قرار است از سمت کلاینت دریافت شوند:
    public class ProductQueryViewModel : IPagedQueryModel
    {
        // ... other properties ...

        public string SortBy { get; set; }
        public bool IsAscending { get; set; }
        public int Page { get; set; }
        public int PageSize { get; set; }
    }
برای مثال در اینجا یک ViewModel مخصوص Product را ایجاد کرده‌ایم که می‌تواند شامل یک سری فیلد دیگر نیز باشد. اما یک سری خواص مرتب سازی و صفحه بندی آن، یک دست و مشخص هستند.

ب) امکان استفاده‌ی از این قرارداد در متدهای کمکی که نوشته خواهند شد:
    public static class IQueryableExtensions
    {
        public static IQueryable<T> ApplyPaging<T>(
          this IQueryable<T> query, IPagedQueryModel model)
        {
            if (model.Page <= 0)
            {
                model.Page = 1;
            }

            if (model.PageSize <= 0)
            {
                model.PageSize = 10;
            }

            return query.Skip((model.Page - 1) * model.PageSize).Take(model.PageSize);
        }
    }
در حین ارائه‌ی اطلاعات نهایی صفحه بندی شده به کلاینت، همیشه یک قسمت Skip و Take وجود خواهند داشت. این متدها نیز باید بر اساس یک سری خاصیت و مقدار مشخص، مانند صفحه شماره صفحه‌ی جاری و تعداد ردیف‌های در هر صفحه کار کنند. اکنون که قرارداد IPagedQueryModel را تهیه کرده‌ایم و ViewModel ما نیز آن‌را پیاده سازی می‌کند، مطمئن خواهیم بود که می‌توان به سادگی به این خواص دسترسی یافت و همچنین این کد تکراری صفحه بندی را توانسته‌ایم به یک متد الحاقی کمکی منتقل و حجم کدهای نهایی را کاهش دهیم.
همچنین دراینجا بجای صدور استثناء در حین دریافت مقادیر غیرمعتبر شماره صفحه یا تعداد ردیف‌های هر صفحه، از حالت «بخشنده» بجای حالت «تدافعی» استفاده شده‌است. برای مثال در حالت «بخشنده» اگر شماره صفحه منفی بود، همان صفحه‌ی اول اطلاعات نمایش داده می‌شود؛ بجای صدور یک استثناء (یا حالت «تدافعی و defensive programming»).


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

همانطور که عنوان شد، از سمت کلاینت، چنین لینکی را دریافت خواهیم کرد:
 http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7
در اینجا، هربار sortBy و isAscending می‌توانند متفاوت باشند و در نهایت به یک چنین کدهایی خواهیم رسید:
if(model.SortBy == "f1")
{
   query = !model.IsAscending ? query.OrderByDescending(x => x.F1) : query.OrderBy(x => x.F1);
}
امکان نوشتن این نوع کوئری‌ها توسط قابلیت تعریف زنجیره‌وار کوئری‌های LINQ میسر است و در نهایت زمانیکه ToList نهایی فراخوانی می‌شود، آنگاه است که کوئری SQL معادل این‌ها تولید خواهد شد.
اما در این حالت نیاز است به ازای تک تک فیلدها، یکبار if/else یافتن فیلد و سپس بررسی صعودی و نزولی بودن آن‌ها صورت گیرد که در نهایت ظاهر خوشایندی را نخواهند داشت.

یک نمونه از مزیت‌های تهیه‌ی قرارداد IPagedQueryModel را در حین نوشتن متد ApplyPaging مشاهده کردید. نمونه‌ی دیگر آن کاهش کدهای تکراری مرتب سازی اطلاعات است:
namespace AngularTemplateDrivenFormsLab.Utils
{
    public static class IQueryableExtensions
    {
        public static IQueryable<T> ApplyOrdering<T>(
          this IQueryable<T> query,
          IPagedQueryModel model,
          IDictionary<string, Expression<Func<T, object>>> columnsMap)
        {
            if (string.IsNullOrWhiteSpace(model.SortBy) || !columnsMap.ContainsKey(model.SortBy))
            {
                return query;
            }

            if (model.IsAscending)
            {
                return query.OrderBy(columnsMap[model.SortBy]);
            }
            else
            {
                return query.OrderByDescending(columnsMap[model.SortBy]);
            }
        }
    }
}
در اینجا متد الحاقی ApplyOrdering، کار دریافت و بررسی خواص مدنظر را از طریق یک دیکشنری انجام می‌دهد و مابقی کدهای تکراری نوشته شده، حذف خواهند شد. برای نمونه، مثالی از نحوه‌ی استفاده‌ی از این متد الحاقی را در ذیل مشاهده می‌کنید:
var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>()
            {
                ["productId"] = p => p.ProductId,
                ["productName"] = p => p.ProductName,
                ["isAvailable"] = p => p.IsAvailable,
                ["price"] = p => p.Price
            };
query = query.ApplyOrdering(queryModel, columnsMap);
ابتدا نگاشتی بین خواص رشته‌ای دریافتی از سمت کلاینت، با خواص شیء Product برقرار شده‌است. سپس این نگاشت به متد ApplyOrdering ارسال شده‌است. به این ترتیب از نوشتن تعداد زیادی if/else یا switch بر اساس خاصیت SortBy بی‌نیاز شده‌ایم، حجم کدهای نهایی تولیدی کاهش پیدا می‌کنند و برنامه نیز خواناتر می‌شود.


تهیه قرارداد ساختار اطلاعات بازگشتی از سمت سرور به سمت کلاینت

تا اینجا قرارداد اطلاعات دریافتی از سمت کلاینت را مشخص کردیم. همچنین از آن برای ساده سازی عملیات مرتب سازی و صفحه بندی اطلاعات کمک گرفتیم. در ادامه نیاز است مشخص کنیم چگونه می‌خواهیم این اطلاعات را به سمت کلاینت ارسال کنیم:
using System.Collections.Generic;

namespace AngularTemplateDrivenFormsLab.Models
{
    public class PagedQueryResult<T>
    {
        public int TotalItems { get; set; }
        public IEnumerable<T> Items { get; set; }
    }
}
عموما ساختار اطلاعات صفحه بندی شده، شامل تعداد کل آیتم‌های تمام صفحات (خاصیت TotalItems) و تنها اطلاعات ردیف‌های صفحه‌ی جاری درخواستی (خاصیت Items) است و چون در اینجا این Items از هر نوعی می‌تواند باشد، بهتر است آن‌را جنریک تعریف کنیم.


پایان کار بازگشت اطلاعات سمت سرور با تهیه اکشن متد GetPagedProducts

در اینجا اکشن متدی را مشاهده می‌کنید که اطلاعات نهایی مرتب سازی شده و صفحه بندی شده را بازگشت می‌دهد:
    [Route("api/[controller]")]
    public class ProductController : Controller
    {
        [HttpGet("[action]")]
        public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
        {
            var pagedResult = new PagedQueryResult<Product>();

            var query = ProductDataSource.LatestProducts
                                         .AsQueryable();

            //TODO: Apply Filtering ... .where(p => p....) ...

            var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>()
            {
                ["productId"] = p => p.ProductId,
                ["productName"] = p => p.ProductName,
                ["isAvailable"] = p => p.IsAvailable,
                ["price"] = p => p.Price
            };
            query = query.ApplyOrdering(queryModel, columnsMap);

            pagedResult.TotalItems = query.Count();
            query = query.ApplyPaging(queryModel);
            pagedResult.Items = query.ToList();
            return pagedResult;
        }
    }
توضیحات تکمیلی

امضای این اکشن متد، شامل دو مورد مهم است:
 public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
الف) ViewModel ایی که پیاده سازی کننده‌ی IPagedQueryModel است. به این ترتیب می‌توان به ساختار استانداردی از مقادیر مورد نیاز برای صفحه بندی و مرتب سازی رسید و همچنین این ViewModel می‌تواند حاوی خواص اضافی ویژه‌ی خود نیز باشد.
ب) خروجی آن از نوع PagedQueryResult است که در مورد آن توضیح داده شد. بنابراین باید به همراه تعداد کل رکوردهای جدول محصولات و همچنین تنها آیتم‌های صفحه‌ی جاری درخواستی باشد.

در ابتدای کار، دسترسی به منبع داده‌ی درون حافظه‌ای ابتدای برنامه را مشاهده می‌کنید. برای اینکه کارکرد آن‌را شبیه به کوئری‌های ORMها کنیم، یک AsQueryable نیز به انتهای آن اضافه شده‌است.
 var query = ProductDataSource.LatestProducts
  .AsQueryable();

//TODO: Apply Filtering ... .where(p => p....) ...
اینجا دقیقا جائی است که در صورت نیاز می‌توان کار فیلتر اطلاعات و اعمال متد where را انجام داد.

پس از مشخص شدن منبع داده و فیلتر آن در صورت نیاز، اکنون نوبت به مرتب سازی اطلاعات است:
var columnsMap = new Dictionary<string, Expression<Func<Product, object>>>()
            {
                ["productId"] = p => p.ProductId,
                ["productName"] = p => p.ProductName,
                ["isAvailable"] = p => p.IsAvailable,
                ["price"] = p => p.Price
            };
query = query.ApplyOrdering(queryModel, columnsMap);
توضیحات این مورد را پیشتر مطالعه کردید و هدف از آن، تهیه یک نگاشت ساده‌ی بین خواص رشته‌ای رسیده‌ی از سمت کلاینت به خواص مدل متناظر با آن است و سپس ارسال آن‌ها به همراه queryModel دریافتی از کاربر، برای اعمال مرتب سازی نهایی.

در آخر مطابق ساختار PagedQueryResult بازگشتی، ابتدا تعداد کل آیتم‌های منبع داده محاسبه شده‌است و سپس صفحه بندی به آن اعمال گردیده‌است. این ترتیب نیز مهم است و گرنه TotalItems دقیقا به همان تعداد ردیف‌های صفحه‌ی جاری محاسبه می‌شود:
var pagedResult = new PagedQueryResult<Product>();
pagedResult.TotalItems = query.Count();
query = query.ApplyPaging(queryModel);
pagedResult.Items = query.ToList();
return pagedResult;


در قسمت بعد، نحوه‌ی نمایش این اطلاعات را در سمت Angular بررسی خواهیم کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
پردازش توالی توالی‌ها در Reactive extensions
به صورت پیش فرض، Rx هر بار تنها یک مقدار را بررسی می‌کند. اما گاهی از اوقات نیاز است تا در هربار، بیشتر از یک مقدار دریافت و پردازش شوند. برای این منظور Rx متدهای الحاقی ویژه‌ای را به نام‌های Buffer ،Scan و Window تدارک دیده‌است تا بتواند از یک توالی، چندین توالی را تولید کند (توالی توالی‌ها = Sequence of sequences).


متد Scan

فرض کنید قصد دارید تعدادی عدد را با هم جمع بزنید. برای اینکار عموما عدد اول با عدد دوم جمع زده شده و سپس حاصل آن با عدد سوم جمع زده خواهد شد و به همین ترتیب تا آخر توالی. کار متد Scan نیز دقیقا به همین نحو است. هربار که قرار است توالی پردازش شود، حاصل عملیات مرحله‌ی قبل را در اختیار مصرف کننده قرار می‌دهد.
در مثال ذیل، قصد داریم حاصل جمع اعداد موجود در آرایه‌ای را بدست بیاوریم:
var sequence = new[] { 12, 3, -4, 7 }.ToObservable();
var runningSum = sequence.Scan((accumulator, value) =>
{
    Console.WriteLine("accumulator {0}", accumulator);
    Console.WriteLine("value {0}", value);
    return accumulator + value;
});
runningSum.Subscribe(result => Console.WriteLine("result {0}\n", result));
با این خروجی
result 12

accumulator 12
value 3
result 15

accumulator 15
value -4
result 11

accumulator 11
value 7
result 18
در اولین بار اجرای متد Subscribe، کار مقدار دهی accumulator با اولین عنصر آرایه صورت می‌گیرد.
در دفعات بعدی، مقدار این accumulator با عدد جاری جمع زده شده و حاصل این عملیات در تکرار آتی، مجددا توسط accumulator قابل دسترسی خواهد بود.

یک نکته: اگر علاقمند باشیم که مقدار اولیه‌ی accumulator، اولین عنصر توالی نباشد، می‌توان آن‌را توسط پارامتر seed متد Scan مقدار دهی کرد:
 var runningSum = sequence.Scan(seed: 10, accumulator: (accumulator, value) =>


متد Buffer

متد بافر، کار تقسیم یک توالی را به توالی‌های کوچکتر، بر اساس زمان، یا تعداد عنصر مشخص شده، انجام می‌دهد. برای مثال در برنامه‌های دسکتاپ شاید نیازی نباشد تا به ازای هر عنصر توالی، یکبار رابط کاربری را به روز کرد. عموما بهتر است تا تعداد مشخصی از عناصر یکجا پردازش شده و نتیجه‌ی این پردازش به تدریج نمایش داده شود.
var sequence = Enumerable.Range(1, 200)
                 .ToObservable()
                 .Buffer(count: 10);

sequence.Subscribe(onNext: numbers =>
{
   Console.WriteLine(numbers.Sum());
});
در اینجا نحوه‌ی استفاده از متد بافر را به همراه مشخص کردن تعداد اعضای بافر ملاحظه می‌کنید. هربار که onNext متد Subscribe فراخوانی شود، 10 عنصر از توالی را در اختیار خواهیم داشت (بجای یک عنصر حالت متداول بافر نشده).
به این ترتیب می‌توان فشار حجم اطلاعات ورودی با فرکانس بالا را کنترل کرد و در نتیجه از منابع موجود بهتر استفاده نمود. برای مثال اگر می‌خواهید عملیات bulk insert را انجام دهید، می‌توان بر اساس یک batch size مشخص، گروه گروه اطلاعات را به بانک اطلاعاتی اضافه کرد تا فشار کار کاهش یابد.

همینکار را بر اساس زمان نیز می‌توان انجام داد:
 var sequence = Enumerable.Range(1, 200)
                   .ToObservable()
                   .Buffer(timeSpan: TimeSpan.FromSeconds(2));
در مثال فوق هر 2 ثانیه یکبار، مجموعه‌ای از عناصر به متد onNext ارسال خواهند شد.


متد Window

متد Window نیز دقیقا همان پارامترهای متد بافر را قبول می‌کند. با این تفاوت که هربار، یک توالی obsevable را به متد onNext ارسال می‌کند.
نوع numbers پارامتر onNext، در حین بکارگیری متد بافر در مثال‌های فوق، IList of int است. اما اگر متدهای Buffer را تبدیل به متد Window کنیم، اینبار نوع numbers، معادل IObservable of int خواهد شد.
 var sequence = Enumerable.Range(1, 200)
                           .ToObservable()
                           .Window(timeSpan: TimeSpan.FromSeconds(2));

sequence.Subscribe(onNext: numbers =>
{
       numbers.Subscribe(onNext: number => Console.WriteLine(number));
});


چه زمانی باید از Buffer استفاده کرد و چه زمانی از Window؟

در متد بافر، به ازای هر توالی که به پارامتر onNext ارسال می‌شود، یکبار وهله‌ی جدیدی از توالی مدنظر در حافظه ایجاد و ارسال خواهد شد. در متد Window صرفا اشاره‌گرهایی به این توالی را در اختیار داریم؛ بنابراین مصرف حافظه‌ی کمتری را شاهد خواهیم بود. متد Window بسیار مناسب است برای اعمال aggregation. مثلا اگر نیاز است جمع، میانگین، حداقل و حداکثر عناصر دریافتی محاسبه شوند، بهتر است از متد Window استفاده شود که نهایتا قابلیت استفاده از متدهای الحاقی Sum و Max و Min را به همراه دارد. با این تفاوت که حاصل این‌ها نیز یک IObservable است که باید Subscribe آن‌را برای دریافت نتیجه فراخوانی کرد:
 var sequence = Enumerable.Range(1, 200)
                          .ToObservable()
                          .Window(10);

sequence.Subscribe(onNext: numbers =>
{
     numbers.Sum().Subscribe(onNext: number => Console.WriteLine(number));
});
در این حالت متد Window، برخلاف متد Buffer، توالی numbers را هربار کش نمی‌کند و به این ترتیب می‌توان به مصرف حافظه‌ی کمتری رسید.


کاربردهای دنیای واقعی

در اینجا دو مثال از بکارگیری متد Buffer را جهت پردازش مجموعه‌های عظیمی از اطلاعات و نمایش همزمان آن‌ها در رابط کاربری ملاحظه می‌کنید.
مثال اول: فرض کنید قصد دارید تمام فایل‌های درایو C خود را توسط یک TreeView نمایش دهید. در این حالت نباید رابط کاربری برنامه در حالت هنگ به نظر برسد. همچنین به علت زیاد بودن تعداد فایل‌ها و نمایش همزمان آن‌ها در UI، نباید CPU Usage برنامه تا حدی باشد که در کار سایر برنامه‌ها اخلال ایجاد کند. در این مثال‌ها با استفاده از Rx و متد بافر آن، هربار مثلا 1000 آیتم را بافر کرده و سپس یکجا در TreeView نمایش می‌دهند. به این ترتیب دو شرط یاد شده محقق می‌شوند.
The Rx Framework By Example

مثال دوم: خواندن تعداد زیادی رکورد از بانک اطلاعاتی به همراه نمایش همزمان آن‌ها در UI بدون اخلالی در کار سیستم و همچنین هنگ کردن برنامه.
Using Reactive Extensions for Streaming Data from Database
مطالب
یک نکته از ASP.NET 4.5 GridView
تا قبل از ASP.NET 4.5 ، هنگام کار با GridView رسم بر این بوده که به خاصیت DataSource ، یک منبع داده (مانند SqlDataSource و ...) را  Bind کرده و متد DataBind را صدا نموده و نتیجه نمایش داده می‌شد.
اما با استفاده از ویژگی‌های جدید اضافه شده(هر چند با تأخیر نسبت به Grid‌های پیشرفته دیگر ) کار با این کنترل راحت‌تر و خواناتر شده است. یکی از این ویژگی‌ها را با هم بررسی می‌کنیم:
با استفاده از ویژگی SelectMethod میتوان متدی را به GridView معرفی کرد که وظیفه منبع داده را انجام داده و هنگام Bind فراخوانی شده و گرید را پر کند:
مثال:
    <asp:GridView ID="gvCities"
                runat="server"
                AutoGenerateColumns="False"
                ItemType="WebApplication3.City"
                SelectMethod="GetAllCities">
                <Columns>
                    <asp:TemplateField HeaderText="نام">
                        <ItemTemplate><%#: Item.Name %></ItemTemplate>
                    </asp:TemplateField>
                </Columns>
     </asp:GridView>
نکته مهم در این کد ItemType است. با استفاده از این خاصیت به جای اینکه مانند قبل نام فیلدهایی که قرار است در گرید نمایش  داده شود را بصورت string معرفی کنیم (مثلا در اینجا ("Eval("Name ، اگر نام فیلد را غلط بنویسیم هنگام کامپایل خطایی صادر نمی‌شود)، آنرا بصورت  Strongly Type نوشته و از اشتباه جلوگیری می‌کنیم.( + )
کد متد:
 public IQueryable<City> GetAllCities()
        {
            var context = new EFContext();
            var q = from c in context.City
                    orderby c.Name
                    select c;
            return q;
        }
و سپس دستور زیر  را فراخوانی می‌کنیم:
 gvCities.DataBind();
اگر بخواهیم در گرید Paging داشته باشیم بصورت زیر عمل می‌کنیم:
   <asp:GridView ID="gvCities" 
                runat="server" 
                AutoGenerateColumns="False"
                AllowPaging="True" 
                PageSize="10" 
                ItemType="WebApplication3.City" 
                SelectMethod="GetAllCities">
                 <Columns>
                    <asp:TemplateField HeaderText="نام">
                        <ItemTemplate><%#: Item.Name %></ItemTemplate>
                    </asp:TemplateField>
                </Columns>
  </asp:GridView>
که در اینجا دو خصوصیت AllowPaging و PageSize را مقدار دهی کرده ایم. این خصوصیت‌ها اجازه صفحه بندی را به گرید می‌دهند.حال برای اینکه متد نیز برای صفحه بندی آماده شود باید سه آرگومان به آن اضافه کنیم:(نام پارامترها باید دقیقا موارد زیر باشد)
1- startRowIndex: نقطه شروع صفحه بندی را مشخص می‌کند.
2- maximumRows: تعداد سطرهایی که گرید باید نمایش دهد را مشخص می‌کند.
3- totalRowCount: این پارامتر  باید در تابع مقدار دهی شود (مانند مثال) تا مشخص شود نتیجه Query چند رکورد است و در نهایت گرید تعداد صفحات را بر این اساس نمایش می‌دهد.

 و برای اینکه صفحه بندی را در Query هم لحاظ کنیم از دو تا بع Skip و Take استفاده شده است.
 public IQueryable<City> GetAllCities(int startRowIndex, int maximumRows, out int totalRowCount)
 {
        var context = new EFContext();
        var q = from c in context.City
                    select c;

        totalRowCount = q.Count();

        return q.OrderBy(x=>x.Name).Skip(startRowIndex).Take(maximumRows);
 }

نکته مهم در این متد  IQueryable  بودن آن است که باعث واکشی داده‌ها بصورت صفحه به صفحه میشود.
دستورات SQL تولید شده در پروفایلر:

همانطور که مشاهده می‌کنید دو دستور SQL  تولید شده ، یکی برای بازگرداندن تعداد رکوردها و یکی هم برای واکشی داده‌ها به اندازه تعداد رکوردهای مجاز در هر صفحه.