مطالب
آشنایی و بررسی ابزار MiniProfiler
در کنار کتابخانه elmah که وظیفه ثبت تمامی خطاهای برنامه را دارد کتابخانه MiniProfiler امکان یافتن مشکلات کارایی و تنگناهای وب سایت را در اختیارمان قرار می‌دهد. دو قابلیت عمده که این ابزار فراهم می‌نمایید
  1. امکان مشاهده و بررسی کوئری‌های خام ADO.NET از قبیل SQL Server,Oracle و LINQ-to-SQL و EF/First Code و...
  2. نمایش زمان اجرای عملی صفحات
برای استفاده از این ابزار کافیست تا آن را از nuget دریافت نمایید
PM> Install-Package MiniProfiler
 در ASP.NET MVC در صفحه Layout_ قبل از بسته شدن تگ body تابع RenderIncludes را مانند زیر صدا بزنید تا در همه صفحات نمایش داده شود
@using StackExchange.Profiling;
<head>
 ..
</head>
<body>
  ...
  @MiniProfiler.RenderIncludes()
</body>
در کلاس global کد زیر را برای اجرای MiniProfiler اضافه نمایید
protected void Application_BeginRequest()
{
    if (Request.IsLocal)
    {
        MiniProfiler.Start();
    }
}

protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}

برای پیکربندی MiniProfiler در web.config کد زیر را اضافه نمایید
<system.webServer>
  ...
  <handlers>
    <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" 
         type="System.Web.Routing.UrlRoutingModule"
         resourceType="Unspecified" 
         preCondition="integratedMode" />
  </handlers>
</system.webServer>
یا کتابخانه MiniProfiler.MVC را از nuget دریافت نمایید
PM> Install-Package MiniProfiler.MVC
با اضافه شدن این کتابخانه همه پیکربندی بصورت صورت خودکار انجام می‌گیرد. حال وب سایت را اجرا کنید در بالای صفحه مانند شکل زیر مدت زمان بارگذاری صفحه نمایش داده می‌شود که با کلیک بر روی آن اطلاعات بیشتری را مشاهده می‌نمایید

اگر در اکشن اجرا شده کوئری اجرا شد باشد ستونی به نام query times نمایش داده می‌شود که تعداد کوئری‌ها و مدت زمان آن را نمایش می‌دهد

حال بر روی گزینه sql کلیک کنید که صفحه دیگری باز شود و کوئری خام آن را مشاهد نمایید اگر کوئری تکرار شده باشد در کنار آن با DUPLICATE متمایز شده است

برای مشاهده کوئری‌های Entity Framework/First Code کتابخانه MiniProfiler.EF را اضافه نمایید
PM> Install-Package MiniProfiler.EF
اگر بصورت دستی MiniProfiler را پیکربندی کرده باشید می‌بایست در Application_Start دستور زیر را اجرا نمایید
protected void Application_BeginRequest()
{
    if (Request.IsLocal)
    {
        MiniProfiler.Start();
        MiniProfilerEF.Initialize();
    }
}
در حالت پبشرفته‌تر اگر قصد داشته باشید زمان یک قطعه کد را جداگانه محاسبه نمایید بصورت زیر عمل نمایید
public ActionResult Index()
{
    
    var profiler = MiniProfiler.Current;

    using (profiler.Step("Step 1"))
    {
        //code 1
    }

    using (profiler.Step("Step 2"))
    {
        //code 2
    }

    return View();
}
با این کار زمان هر step را بصورت جداگانه محاسبه می‌نماید. در ASP.NET Webforms دقیقا به همین صورت استفاده می‌شود فقط کافیست در masterpage اصلی یا اگر از masterpage استفاده نمی‌کنیم در صفحه مورد نظر تابع RenderIncludes را بصورت زیر صدا بزنیم
<%= StackExchange.Profiling.MiniProfiler.RenderIncludes() %>
امیدوارم مفید واقع شده باشد.
مطالب
ASP.NET MVC #7

آشنایی با Razor Views

قبل از اینکه بحث جاری ASP.NET MVC را بتوانیم ادامه دهیم و مثلا مباحث دریافت اطلاعات از کاربر، کار با فرم‌ها و امثال آن‌را بررسی کنیم، نیاز است حداقل به دستور زبان یکی از View Engineهای ASP.NET MVC آشنا باشیم.
MVC3 موتور View جدیدی را به نام Razor معرفی کرده است که به عنوان روش برگزیده ایجاد Viewها در این سیستم به شمار می‌رود و فوق العاده نسبت به ASPX view engine سابق، زیباتر، ساده‌تر و فشرده‌تر طراحی شده است و یکی از اهداف آن تلفیق code و markup می‌باشد. در این حالت دیگر پسوند فایل‌های Viewها همانند سابق ASPX نخواهد بود و به cshtml و یا vbhtml تغییر یافته است. همچنین برخلاف web forms view engine از System.Web.Page مشتق نشده است. و باید دقت داشت که Razor یک زبان برنامه نویسی جدید نیست. در اینجا از مخلوط زبان‌های سی شارپ و یا ویژوال بیسیک به همراه تگ‌های html استفاده می‌شود.
البته این را هم باید عنوان کرد که این مسایل سلیقه‌ای است. اگر با web forms view engine راحت هستید، با همان کار کنید. اگر با هیچکدام از این‌ها راحت نیستید (!) نمونه‌های دیگر هم وجود دارند، مثلا:

Razor Views یک سری قابلیت جالب را هم به همراه دارند:
1) امکان کامپایل آن‌ها به درون یک DLL وجود دارد. مزیت: استفاده مجدد از کد، عدم نیاز به وجود صریح فایل cshtml یا vbhtml بر روی دیسک سخت.
2) آزمون پذیری: از آنجائیکه Razor viewها به صورت یک کلاس کامپایل می‌شوند و همچنین از System.Web.Page مشتق نخواهند شد، امکان بررسی HTML نهایی تولیدی آن‌هابدون نیاز به راه اندازی یک وب سرور وجود دارد.
3) IntelliSense ویژوال استودیو به خوبی آن‌را پوشش می‌دهد.
4) با توجه به مواردی که ذکر شد، یک اتفاق جالب هم رخ داده است: امکان استفاده از Razor engine خارج از ASP.NET MVC هم وجود دارد. برای مثال یک سرویس ویندوز NT طراحی کرده‌اید که قرار است ایمیل فرمت شده‌ای به همراه اطلاعات مدل‌های شما را در فواصل زمانی مشخص ارسال کند؟ می‌توانید برای طراحی آن از Razor engine استفاده کنید و تهیه خروجی نهایی HTML آن نیازی به راه اندازی وب سرور و وهله سازی HttpContext ندارد.


ساختار پروژه مثال جاری

در ادامه مرور سریعی خواهیم داشت بر دستور زبان Razor engine و جهت نمایش این قابلیت‌ها، یک مثال ساده را در ابتدا با مشخصات زیر ایجاد خواهیم کرد:
الف) یک empty ASP.NET MVC 3 project را ایجاد کنید و نوع View engine را هم در ابتدای کار Razor انتخاب نمائید.
ب) دو کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
namespace MvcApplication3.Models
{
public class Product
{
public Product(string productNumber, string name, decimal price)
{
Name = name;
Price = price;
ProductNumber = productNumber;
}
public string ProductNumber { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}

using System.Collections.Generic;

namespace MvcApplication3.Models
{
public class Products : List<Product>
{
public Products()
{
this.Add(new Product("D123", "Super Fast Bike", 1000M));
this.Add(new Product("A356", "Durable Helmet", 123.45M));
this.Add(new Product("M924", "Soft Bike Seat", 34.99M));
}
}
}

کلاس Products صرفا یک منبع داده تشکیل شده در حافظه است. بدیهی است هر نوع ORM ایی که یک ToList را بتواند در اختیار شما قرار دهد، توانایی تشکیل لیست جنریکی از محصولات را نیز خواهد داشت و تفاوتی نمی‌کند که کدامیک مورد استفاده قرار گیرد.

ج) سپس یک کنترلر جدید به نام ProductsController را به پوشه Controllers برنامه اضافه می‌کنیم:

using System.Web.Mvc;
using MvcApplication3.Models;

namespace MvcApplication3.Controllers
{
public class ProductsController : Controller
{
public ActionResult Index()
{
var products = new Products();
return View(products);
}
}
}

د) بر روی نام متد Index کلیک راست کرده، گزینه Add view را جهت افزودن View متناظر آن، انتخاب کنید. البته می‌شود همانند قسمت پنجم گزینه Create a strongly typed view را انتخاب کرد و سپس Product را به عنوان کلاس مدل انتخاب نمود و در آخر خیلی سریع یک لیست از محصولات را نمایش داد، اما فعلا از این قسمت صرفنظر نمائید، چون می‌خواهیم آن‌ را دستی ایجاد کرده و توضیحات و نکات بیشتری را بررسی کنیم.

ه) برای اینکه حین اجرای برنامه در VS.NET هربار نخواهیم که آدرس کنترلر Products را دستی در مرورگر وارد کنیم، فایل Global.asax.cs را گشوده و سپس در متد RegisterRoutes، در سطر Parameter defaults، مقدار پیش فرض کنترلر را مساوی Products قرار دهید.


مرجع سریع Razor

ابتدا کدهای View متد Index را به شکل زیر وارد نمائید:

@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>

@:line-2: @data <‪br />
<text>line-3:</text> @data
}
<‪br />
site@(data)
<‪br />
@@name
<‪br />
@(number/10)
<‪br />
First product: @Model.First().Name
<‪br />
@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}
<‪br />
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}

@*
A Razor Comment
*@

<‪br />
@("First product: " + Model.First().Name)
<‪br />
<img src="@(number).jpg" />

در ادامه توضیحات مرتبط با این کدها ارائه خواهد شد:

1) نحوه معرفی یک قطعه کد
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>

@:line-2: @data <‪br />
<text>line-3:</text> @data
}

این کدها متعلق به Viewایی است که در قسمت (د) بررسی ساختار پروژه مثال جاری، ایجاد کردیم. در ابتدای آن هم نوع model مشخص شده تا بتوان ساده‌تر به اطلاعات شیء Model به کمک IntelliSense دسترسی داشت.
برای ایجاد یک قطعه کد در Viewایی از نوع Razor به این نحو عمل می‌شود:
@{  ...Code Block.... }

در اینجا مجاز هستیم کدهای سی شارپ را وارد کنیم. یک نکته جالب را هم باید درنظر داشت: امکان نوشتن تگ‌های html هم در این بین وجود دارد (بدون اینکه مجبور باشیم قطعه کد شروع شده را خاتمه دهیم، به حالت html معمولی سوئیچ کرده و دوباره یک قطعه کد دیگر را شروع نمائیم). مانند line1 مثال فوق. اگر کمی پایین‌تر از این سطر مثلا بنویسیم line2 (به عنوان یک برچسب) کامپایلر ایراد خواهد گرفت، زیرا این مورد نه متغیر است و نه از پیش تعریف شده است. به عبارتی نباید فراموش کنیم که اینجا قرار است کد نوشته شود. برای رفع این مشکل دو راه حل وجود دارد که در سطرهای دو و سه ملاحظه می‌کنید. یا باید از تگی به نام text برای معرفی یک برچسب در این میان استفاده کرد (سطر سه) یا اگر قرار است اطلاعاتی به شکل یک متن معمولی پردازش شود ابتدای آن مانند سطر دوم باید یک @: قرار گیرد.
کمی پایین‌تر از قطعه کد معرفی شده در بالا بنویسید:
<‪br />
site@data

اکنون اگر خروجی این View را در مرورگر بررسی کنید، دقیقا همین site@data خواهد بود. چون در این حالت Razor تصور خواهد کرد که قصد داشته‌اید یک آدرس ایمیل را وارد کنید. برای این حالت خاص باید نوشت:
<‪br />
site@(data)

به این ترتیب data از متغیر data تعریف شده در code block قبلی برنامه دریافت و نمایش داده خواهد شد.
شبیه به همین حالت در مثال زیر هم وجود دارد:
<img src="@(number).jpg" />

در اینجا اگر پرانتزها را حذف کنیم، Razor فرض را بر این خواهد گذاشت که شیء number دارای خاصیت jpg است. بنابراین باید به نحو صریحی، بازه کاری را مشخص نمائیم.

بکار گیری این علامت @ یک نکته جنبی دیگر را هم به همراه دارد. فرض کنید در صفحه قصد دارید آدرس توئیتری شخصی را وارد کنید. مثلا:
<‪br />
@name

در این حالت View کامپایل نخواهد شد و Razor تصور خواهد کرد که قرار است اطلاعات متغیری به نام name را نمایش دهید. برای نمایش این اطلاعات به همین شکل، یک @ دیگر به ابتدای سطر اضافه کنید:
<‪br />
@@name

2) نحوه معرفی عبارات
عبارات پس از علامت @ معرفی می‌شوند و به صورت پیش فرض Html Encoded هستند (در قسمت 5 در اینباره بیشتر توضیح داده شد):
First product: @Model.First().Name

در این مثال با توجه به اینکه نوع مدل در ابتدای View مشخص شده است، شیء Model به لیستی از Products اشاره می‌کند.

یک نکته:
مشخص سازی حد و مرز صریح یک متغیر در مثال زیر نیز کاربرد دارد:
<‪br />
@number/10

اگر خروجی این مثال را بررسی کنید مساوی 12/10 خواهد بود و محاسبه‌ای انجام نخواهد شد. برای حل این مشکل باز هم از پرانتز می‌توان کمک گرفت:
<‪br />
@(number/10)
3) نحوه معرفی عبارات شرطی

@if (@number>10)
{
  <span>@data</span>
}
else
{
  <text>Plain Text</text>
}

یک عبارت شرطی در اینجا با @if شروع می‌شود و سپس نکاتی که در «نحوه معرفی یک قطعه کد» بیان شد، در مورد عبارات داخل {} صادق خواهد بود. یعنی در اینجا نیز می‌توان عبارات سی شارپ مخلوط با تگ‌های html را نوشت.
یک نکته: عبارت شرطی زیر نادرست است. حتما باید سطرهای کدهای سی شارپ بین {} محصور شوند؛‌ حتی اگر یک سطر باشند:
@if( i < 1 ) int myVar=0;


4) نحوه استفاده از حلقه foreach
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}

حلقه foreach نیز مانند عبارات شرطی با یک @ شروع شده و داخل {} بدنه آن نکات «نحوه معرفی یک قطعه کد» برقرار هستند (امکان تلفیق code و markup با هم).
کسانی که پیشتر با web forms کار کرده باشند، احتمالا الان خواهند گفت که این یک پس رفت است و بازگشت به دوران ASP کلاسیک دهه نود! ما به ندرت داخل صفحات aspx وب فرم‌ها کد می‌نوشتیم. مثلا پیشتر یک GridView وجود داشت و یک دیتاسورس که به آن متصل می‌شد؛ مابقی خودکار بود و ما هیچ وقت حلقه‌ای ننوشتیم. در اینجا هم این مساله با نوشتن برای مثال «html helpers» قابل کنترل است که در قسمت‌های بعدی به آن‌ پرداخته خواهد شد. به عبارتی قرار نیست به این نحو با Viewهای Razor رفتار کنیم. این قسمت فقط یک آشنایی کلی با Syntax است.


5) امکان تعریف فضای نام در ابتدای View
@using namespace;

6) نحوه نوشتن توضیحات سمت سرور:
@*
A Razor Comment / Server side Comment
*@

7) نحوه معرفی عبارات چند جزئی:
@("First product: " + Model.First().Name)

همانطور که ملاحظه می‌کنید، ذکر یک پرانتز برای معرفی عبارات چندجزئی کفایت می‌کند.


استفاده از موتور Razor خارج از ASP.NET MVC

پیشتر مطلبی را در مورد «تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net» در این سایت مطالعه کرده‌اید. اولین سؤالی هم که در ذیل آن مطلب مطرح شده این است: «در برنامه‌های ویندوز چطور؟» پاسخ این است که کل آن مثال بر مبنای HttpContext.Current.Server.Execute کار می‌کند. یعنی باید مراحل وهله سازی HttpContext و شیء Server توسط یک وب سرور و درخواست رسیده طی شود و ... شبیه سازی آن آنچنان مرسوم و کار ساده‌ای نیست.
اما این مشکل با Razor وجود ندارد. به عبارتی در اینجا برای رندر کردن یک Razor View به html نهایی، نیازی به HttpContext نیست. بنابراین از این امکانات مثلا در یک سرویس ویندوز ان تی یا یک برنامه کنسول، WinForms، WPF و غیره هم می‌توان استفاده کرد.
برای اینکه بتوان از Razor خارج از ASP.NET MVC استفاده کرد، نیاز به اندکی کدنویسی هست مثلا استفاده از کامپایلر سی شارپ یا وی بی و کامپایل پویای کد و یک سری ست آپ دیگر. پروژه‌ای به نام RazorEngine این‌ کپسوله سازی رو انجام داده و از اینجا http://razorengine.codeplex.com/ قابل دریافت است.


مطالب
OpenCVSharp #4
کار با فیلترها در OpenCVSharp

فرض کنید قصد داریم یک چنین مثال زبان C را که در مورد کار با فیلترها در OpenCV است، به نمونه‌ی دات نتی آن تبدیل کنیم:
        #include <cv.h>
        #include <highgui.h>
        #include <stdio.h>
 
        int main (int argc, char **argv)
        {
          IplImage *src_img = 0, *dst_img;
          float data[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
          };
          CvMat kernel = cvMat (1, 21, CV_32F, data);
 
          if (argc >= 2)
            src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
          if (src_img == 0)
            exit (-1);
 
          dst_img = cvCreateImage (cvGetSize (src_img), src_img->depth, src_img->nChannels);
 
          cvNormalize (&kernel, &kernel, 1.0, 0, CV_L1);
          cvFilter2D (src_img, dst_img, &kernel, cvPoint (0, 0));
 
          cvNamedWindow ("Filter2D", CV_WINDOW_AUTOSIZE);
          cvShowImage ("Filter2D", dst_img);
          cvWaitKey (0);
 
          cvDestroyWindow ("Filter2D");
          cvReleaseImage (&src_img);
          cvReleaseImage (&dst_img);
 
          return 0;
        }
معادل OpenCVSharp آن به صورت ذیل خواهد بود:
using (var src = new IplImage(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
using (var dst = new IplImage(src.Size, src.Depth, src.NChannels))
{
    float[] data = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
    var kernel = new CvMat(rows: 1, cols: 21, type: MatrixType.F32C1, elements: data);
 
    Cv.Normalize(src: kernel, dst: kernel, a: 1.0, b: 0, normType: NormType.L1);
    Cv.Filter2D(src, dst, kernel, anchor: new CvPoint(0, 0));
 
    using (new CvWindow("src", image: src))
    using (new CvWindow("dst", image: dst))
    {
        Cv.WaitKey(0);
    }
}
با این خروجی:


در قسمت‌های قبلی در مورد بارگذاری تصاویر، تهیه‌ی یک Clone از آن و همچنین ساخت یک پنجره به روش‌های مختلف و رها سازی خودکار منابع مرتبط، بیشتر بحث شد. در اینجا تصویر اصلی با همان عمق و وضوح تغییر نیافته‌ی آن بارگذاری می‌شود.
کار cvMat، آغاز یک ماتریس OpenCV است. پارامترهای آن، تعداد ردیف‌ها، ستون‌ها، نوع داده‌ی المان‌ها و داده‌های مرتبط را مشخص می‌کنند.
در این مثال آرایه‌ی data، یک فیلتر را تعریف می‌کند که در اینجا، حالت یک بردار را دارد تا یک ماتریس. برای تبدیل آن به ماتریس، از شیء CvMat استفاده خواهد شد که آن‌را تبدیل به ماتریسی با یک ردیف و 21 ستون خواهد کرد.
در اینجا از نام کرنل استفاده شده‌است. کرنل در OpenCV به معنای ماتریسی از داده‌ها با یک نقطه‌ی anchor (لنگر) است. این لنگر به صورت پیش فرض در میانه‌ی ماتریس قرار دارد (نقطه‌ی 1- , 1- ).


مرحله‌ی بعد، نرمال سازی این فیلتر است. تاثیر نرمال سازی اطلاعات را به این نحو می‌توان نمایش داد:
 double sum = 0;
foreach (var item in data)
{
     sum += Math.Abs(item);
}
Console.WriteLine(sum); // => .999999970197678
در اینجا پس از نرمال سازی، جمع عناصر بردار data، تقریبا مساوی 1 خواهد بود و تمام عناصر بردار data، به داده‌هایی بین یک و صفر، نگاشت خواهند شد.
اگر مرحله‌ی نرمال سازی اطلاعات را حذف کنیم، تصویر نهایی حاصل، چنین شکلی را پیدا می‌کند:


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




فیلترهای توکار OpenCV

علاوه بر امکان طراحی فیلترهای سفارشی خطی مانند مثال فوق، کتابخانه‌ی OpenCV دارای تعدادی فیلتر توکار نیز می‌باشد که نمونه‌ای از آن‌را در مثال ذیل می‌توانید مشاهده کنید:
using (var src = new IplImage(@"..\..\Images\Car.jpg", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var dst = new IplImage(src.Size, src.Depth, src.NChannels))
    {
        using (new CvWindow("src", image: src))
        {
            Cv.Erode(src, dst);
            using (new CvWindow("Erode", image: dst))
            {
                Cv.Dilate(src, dst);
                using (new CvWindow("Dilate", image: dst))
                {
                    Cv.Not(src, dst);
                    using (new CvWindow("Invert", image: dst))
                    {
                        Cv.WaitKey(0);
                    }
                }
            }
        }
    }
}
با این خروجی



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
طرحبندی صفحات وب با بوت استرپ 4 - قسمت اول
یکی از مفیدترین قسمت‌های بوت استرپ 4، سیستم طرحبندی و گرید آن است که در ابتدا با یک Container آغاز می‌شود. این Container می‌تواند واکنشگرا و با عرض ثابت باشد و یا fluid انتخاب شود که کل عرض صفحه را به خود اختصاص می‌دهد. داخل هر Container می‌توان ستون‌ها و ردیف‌هایی را تعریف کرد. بوت استرپ 4، یک گرید طرحبندی 12 ستونه را ارائه می‌دهد که برای اندازه‌های زیر، تعدادی break-point را تعریف کرده‌است:
extra small, small, medium, large, extra large
در این نگارش، break-point از نوع extra small، نسبت به نگارش سوم بوت استرپ، جدید است.
به این ترتیب این گرید به شدت انعطاف پذیر شده و می‌توان برای اندازه‌های مختلف صفحه، طرحبندی‌های متفاوتی را ارائه داد.


دربرگیرنده‌ها و ردیف‌های گرید بوت استرپ

گرید طرحبندی بوت استرپ 4، 12 ستونه است و از فناوری خاصی به نام Flexbox استفاده می‌کند. برای کار با آن نیاز است با دو عنصر اصلی زیر آشنا بود:
- Containers: هدف آن‌ها تنظیم طرحبندی، مطابق با break-point (های) خاصی می‌باشد.
- ردیف‌ها و ستون‌ها: طرحبندی اصلی را تشکیل می‌دهند و ردیف‌ها، تعریف ستون‌ها را میسر می‌کنند.


بررسی Grid Containers

در بوت استرپ 4، دو نوع Container با کلاس‌های زیر وجود دارند:
- container: یک دربرگیرنده‌ی متداول است و طرحبندی را در میانه‌ی صفحه و بر اساس break-point (های) خاصی نمایش می‌دهد. این دربرگیرنده، در اطرف آن یک padding با اندازه‌ی 15px را نیز به همراه دارد و با break-pointهای زیر خودش را تطبیق می‌دهد:
extra small: کمتر از 576px
small: بیشتر از 576px
medium: بیشتر از 768px
large: بیشتر از 992px
extra-large: بیشتر از 1200px

- container-fluid: این دربرگیرنده، کل عرض نمایشی صفحه را پوشش می‌دهد.


یک مثال: تعریف container، row و columns

<head>
    <style>
        img {
          width: 100px;
          display: block;
        }
      </style>
</head>

<body>
    <header class="clearfix" style="height: 50vh; background: url(images/background.jpg) no-repeat center center; background-size: cover; margin-bottom: 20px;">
        <div class="container">
            <img src="images/wisdompetlogo.svg" alt="Wisdom Pet Logo">
        </div>
    </header>

    <div class="container">
        <section id="services">
            <div class="row">
                <article class="col">
                    <img class="mx-auto" src="images/icon-exoticpets.svg" alt="Icon">
                    <h3>Exotic Pets</h3>
                    <p>We offer specialized care for reptiles, rodents, birds,
                        and other exotic pets.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-grooming.svg" alt="Icon">
                    <h3>Grooming</h3>
                    <p>Our therapeutic grooming treatments help battle fleas,
                        allergic dermatitis, and other challenging skin
                        conditions.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-health.svg" alt="Icon">
                    <h3>General Health</h3>
                    <p>Wellness and senior exams, ultrasound, x-ray, and dental
                        cleanings are just a few of our general health
                        services.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-nutrition.svg" alt="Icon">
                    <h3>Nutrition</h3>
                    <p>Let our nutrition experts review your pet's diet and
                        prescribe a custom nutrition plan for optimum health
                        and disease prevention.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-pestcontrol.svg" alt="Icon">
                    <h3>Pest Control</h3>
                    <p>We offer the latest advances in safe and effective
                        prevention and treatment of fleas, ticks, worms, heart
                        worm, and other parasites.</p>
                </article>

                <article class="col">
                    <img class="mx-auto" src="images/icon-vaccinations.svg" alt="Icon">
                    <h3>Vaccinations</h3>
                    <p>Our veterinarians are experienced in modern vaccination
                        protocols that prevent many of the deadliest diseases
                        in pets.</p>
                </article>
            </div>
        </section>
    </div>
</body>
با این خروجی:


توضیحات:
- برای آزمایش این مثال، ابتدا کلاس container آن‌را حذف کنید:
<div class="container">
مشاهده خواهید کرد که کل صفحه بجای نمایش در وسط آن، از سمت چپ و بدون هیچ فاصله‌ای شروع می‌شود. این کلاس container است که محتوا را به میانه‌ی صفحه با فواصلی معین از دو طرف، منتقل می‌کند. به همین جهت عنصرheader  ابتدای آن‌را داخل این container قرار نداده‌ایم تا کل عرض صفحه را پر کند. مقدار height: 50vh، به معنای استفاده‌ی از 50 درصد view-port height است.
اما چون می‌خواهیم محل قرارگیری لوگوی داخل این هدر، با حاشیه‌ی سمت چپ container ذیل آن یکی باشد، این logo را داخل container قرار داده‌ایم.
- از این جهت که تصاویر استفاده شده از نوع svg هستند و بسیار بزرگ می‌باشند، با style تعریف شده‌ی در ابتدای head، اندازه‌ی آن‌ها را کاهش داده‌ایم و همچنین نوع نمایشی آن‌ها را نیز به block تنظیم کرده‌ایم. در این حالت برای اینکه تصاویر در میانه‌ی این block قرار گیرند، از کلاس mx-auto استفاده شده‌است.
- در مرحله‌ی بعد، کار تعریف سطرها و ستون‌ها انجام شده‌است:
    <div class="container">
        <section id="services">
            <div class="row">
                <article class="col">
هر row باید داخل container قرار گیرد. سپس داخل آن می‌توان 12 ستون را تعریف کرد که در اینجا از کلاس col برای این منظور استفاده شده‌است. col ساده‌ترین نوع ستون در بوت استرپ 4 است. کار آن این است که محتوا را تا جائیکه می‌تواند در فضای مهیا نمایش دهد.


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


انواع ستون‌های طرحبندی در بوت استرپ 4

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

در این تصویر، فرمول تعریف کلاس‌های ستون‌ها را در بوت استرپ 4 مشاهده می‌کنید. هر قسمتی از این فرمول که داخل پرانتز قرار گرفته‌است، یعنی ذکر آن اختیاری می‌باشد؛ به همین جهت در مثال قبلی، ذکر صرفا col نیز کار کرد. در این حالت اگر دو ستون را تعریف کنید، هر کدام 50 درصد عرض container را به خود اختصاص می‌دهند و اگر سه ستون تعریف شود، هر کدام 33 درصد عرض را اشغال می‌کنند.
در این فرمول BP به معنای break-point است و یکی از مقادیر ذکر شده‌ی مقابل آن‌را می‌تواند داشته باشد. برای مثال اگر col-sm را تعریف کردیم، یعنی این ستون تا زمانیکه اندازه‌ی صفحه تا 576px باشد، کل عرض صفحه را پر می‌کند.
COL در اینجا به معنای تعداد ستونی است که این محتوا قرار است به خود اختصاص دهد. برای مثال col-md-6 یعنی این ستون تا رسیدن به break-point از نوع md، کل عرض صفحه را به خود اختصاص می‌دهد؛ پس از آن (اندازه‌ی صفحه‌ی بیشتر از 768px) این ستون، 6 واحد از 12 واحد ممکن را به خود اختصاص خواهد داد.
ذکر BP نیز اختیاری است. یعنی اگر تنها col-6 را تعریف کردیم، این ستون در تمام break-pointها و در تمام اندازه‌های ممکن صفحه، همواره 6 واحد از 12 واحد موجود را به خود اختصاص می‌دهد.

مثال: بررسی فرمول تعریف ستون‌ها در بوت استرپ 4

<head>
    <style>
        img {
              width: 100%;
              height: 200px;
              max-height: 200px;
            }
    </style>
</head>

<body>
    <div class="container">
        <div id="services">
            <div class="row">
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Exotic Pets</h4>
                    <p>We offer <strong>specialized</strong> care for <em>reptiles,
                            rodents, birds,</em> and other exotic pets.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Grooming</h4>
                    <p>Our therapeutic <span class="font-weight-bold">grooming</span>
                        treatments help battle fleas, allergic dermatitis, and
                        other challenging skin conditions.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <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>
                </section>
                <section class="col">
                    <img class="img-fluid" src="images/image.png" alt="sample image">
                    <h4>Nutrition</h4>
                    <p>Let our nutrition experts review your pet's diet and
                        prescribe a custom nutrition plan for optimum health
                        and
                        disease prevention.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Pest Control</h4>
                    <p>We offer the latest advances in safe and effective
                        prevention and treatment of fleas, ticks, worms, heart
                        worm, and other parasites.</p>
                </section>
                <section class="col">
                    <img src="images/image.png" alt="sample image">
                    <h4>Vaccinations</h4>
                    <p>Our veterinarians are experienced in modern vaccination
                        protocols that prevent many of the deadliest diseases
                        in
                        pets.</p>
                </section>
            </div>
        </div>
    </div>
</body>
- ابتدا یک container تعریف شده‌است تا محتوا را به میانه‌ی صفحه منتقل کند و همچنین شیوه‌نامه‌های پیش‌فرض بوت استرپ را به آن اعمال نماید.
- سپس کل محتوا را داخل یک ردیف تعریف کرده‌ایم.
- در ادامه هر المان section تعریف شده را به صورت یک col در آورده‌ایم. به این ترتیب بوت استرپ سعی می‌کند تا کل عرض را با ستون‌های تعریف شده تا جائیکه ممکن است پر کند:


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


در ادامه می‌خواهیم مشخص کنیم، ستون‌های خاصی، همیشه عرض مشخصی را به خود اختصاص دهند:
    <div class="container">
        <div id="services">
            <div class="row">
                <section class="col-6">
با این خروجی:


در این حالت، اولین ستون تعریف شده، تحت هر حالتی و با هر اندازه‌ی صفحه‌ای، همیشه نصف عرض (6 واحد از 12 واحد) را به خود اختصاص خواهد داد.

مرحله‌ی بعد این است که مشخص کنیم یک ستون در چه اندازه‌ای از صفحه شروع کند به اختصاص دادن عرضی خاص به خود. برای این منظور هر 6 عنصر section تعریف شده را به صورت زیر ویرایش می‌کنیم:
<section class="col-sm">
در این حالت اگر اندازه‌ی صفحه کمتر از این break-point باشد، هر ستون، کل عرض صفحه را به خود اختصاص می‌دهد:


و اگر صفحه را بزرگتر کنیم، مجددا همان حالت تعریف col خالی تکرار می‌شود و بوت استرپ سعی می‌کند در هر سطر، تا جائیکه می‌تواند، تعداد آیتم بیشتری را جا دهد. اگر بخواهیم این تعداد جا دادن‌ها را نیز کنترل کنیم، می‌توان به صورت زیر عمل کرد:
<section class="col-sm-6">
در اینجا col-sm-6 را به هر المان section انتساب داده‌ایم. به این ترتیب باز هم اگر عرض صفحه کمتر از sm باشد، هر آیتم، کل عرض صفحه را به خود اختصاص می‌دهد. اما اگر اندازه‌ی صفحه بیشتر از sm شود، هر آیتم همواره 6 واحد، یا نیمی از عرض را به خود اختصاص خواهد داد:



امکان تعریف بیش از یک break-point برای هر ستون در بوت استرپ 4


در این جدول انواع break-pointهای قابل تعریف توسط بوت استرپ 4 را ملاحظه می‌کنید. تا اینجا تاثیر اعمال تنها یکی از این‌ها را بر روی یک ستون بررسی کردیم؛ اما می‌توان چندین break-point را بر روی یک ستون نیز اعمال کرد. برای مثال اگر به تمام sectionهای مثال این قسمت ترکیب زیر را اضافه کنیم:
<section class="col-sm-6 col-md-4">
به این معنا خواهد بود که تا زمانیکه عرض صفحه کمتر از 576px است (sm)، هر آیتم کل عرض صفحه را پوشش می‌دهد. زمانیکه از این break-point رد شدیم، هر آیتم 6 واحد از 12 واحد را به خود اختصاص خواهد داد؛ تا زمانیکه عرض صفحه کمتر از 768px است (md). پس از اینکه این break-point را نیز رد کردیم و اندازه‌ی صفحه بزرگتر از آن شد، اینبار هر آیتم، 4 واحد از 12 واحد را به خود اختصاص می‌دهد:



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


در تصویر فوق، فرمول تغییر موقعیت شروع ستون‌ها در بوت استرپ 4 را مشاهده می‌کنید. برای مثال اگر offset-sm-1 را به اولین section اضافه کنیم:
<section class="col-sm-6 col-md-4 offset-sm-1">
پس از رد شدن از break-point تعریف شده‌ی sm، اولین section، به اندازه‌ی یک واحد به سمت راست منتقل می‌شود و از لبه‌ی سمت چپ فاصله پیدا می‌کند:


برای نمونه این قابلیت، پیشتر یکی از روش‌های در مرکز صفحه قرار دادن ستون‌ها بود؛ اما چون بوت استرپ 4 از Flexbox استفاده می‌کند، روش‌های بهتری نیز برای آن وجود دارند که آن‌ها را در قسمت‌های بعد بررسی می‌کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_04.zip
مطالب دوره‌ها
تغییر ترتیب آیتم‌های یک لیست به کمک افزونه jquery.sortable در ASP.NET MVC
در این مطلب قصد داریم ترتیب عناصر نمایش داده شده توسط یک لیست را به کمک افزونه بسیار سبک وزن jquery.sortable تغییر داده و نتایج را در سمت سرور مدیریت کنیم. این افزونه بر اساس امکانات کشیدن و رها ساختن HTML5 کار می‌کند و با مرورگرهای IE8 به بعد سازگار است.

مدل‌های برنامه

using System.Collections.Generic;

namespace jQueryMvcSample05.Models
{
    public class Survey
    {
        public int Id { set; get; }
        public string Title { set; get; }

        public virtual ICollection<SurveyItem> SurveyItems { set; get; }
    }
}

namespace jQueryMvcSample05.Models
{
    public class SurveyItem
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public int Order { set; get; }

        //[ForeignKey("SurveyId")]
        public virtual Survey Survey { set; get; }
        public int SurveyId { set; get; }
    }
}
به کمک این ساختار قصد داریم اطلاعات یک سیستم نظر سنجی را نمایش دهیم.
تعدادی نظر سنجی به همراه گزینه‌های آن‌ها تعریف خواهند شد (یک رابطه one-to-many است). سپس توسط افزونه sortable می‌خواهیم ترتیب قرارگیری گزینه‌های آن‌را مشخص کنیم یا تغییر دهیم.


منبع داده فرضی برنامه


using System.Collections.Generic;
using jQueryMvcSample05.Models;

namespace jQueryMvcSample05.DataSource
{
    /// <summary>
    /// یک منبع داده فرضی جهت دموی ساده‌تر برنامه
    /// </summary>
    public static class SurveysDataSource
    {
        private static IList<Survey> _surveysCache;
        static SurveysDataSource()
        {
            _surveysCache = createSurveys();
        }

        public static IList<Survey> SystemSurveys
        {
            get { return _surveysCache; }
        }

        private static IList<Survey> createSurveys()
        {
            var results = new List<Survey>();
            for (int i = 1; i < 6; i++)
            {
                results.Add(new Survey
                {
                    Id = i,
                    Title = "نظر سنجی " + i,
                    SurveyItems = new List<SurveyItem>
                    {
                       new SurveyItem{ Id = 1, SurveyId = i, Title = "گزینه 1", Order = 1 },
                       new SurveyItem{ Id = 2, SurveyId = i, Title = "گزینه 2", Order = 2 },
                       new SurveyItem{ Id = 3, SurveyId = i, Title = "گزینه 3", Order = 3 },
                       new SurveyItem{ Id = 4, SurveyId = i, Title = "گزینه 4", Order = 4 }
                    }
                });
            }
            return results;
        }
    }
}
در اینجا نیز از یک منبع داده فرضی تشکیل شده در حافظه جهت سهولت دموی برنامه استفاده خواهد شد.


کدهای کنترلر برنامه

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample05.DataSource;
using jQueryMvcSample05.Security;

namespace jQueryMvcSample05.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var surveysList = SurveysDataSource.SystemSurveys;
            return View(surveysList);
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult SortItems(int? surveyId, string[] items)
        {
            if (items == null || items.Length == 0 || surveyId == null)
                return Content("nok");

            updateSurvey(surveyId, items);

            return Content("ok");
        }

        /// <summary>
        /// این متد جهت آشنایی با پروسه به روز رسانی ترتیب گزینه‌ها در اینجا قرار گرفته است
        /// بدیهی است محل قرارگیری آن باید در لایه سرویس برنامه اصلی باشد
        /// </summary>
        private static void updateSurvey(int? surveyId, string[] items)
        {
            var itemIds = new List<int>();
            foreach (var item in items)
            {
                itemIds.Add(int.Parse(item.Replace("item-row-", string.Empty)));
            }

            var survey = SurveysDataSource.SystemSurveys.FirstOrDefault(x => x.Id == surveyId.Value);
            if (survey == null)
                return;

            int order = 0;
            foreach (var itemId in itemIds)
            {
                order++;
                var surveyItem = survey.SurveyItems.FirstOrDefault(x => x.Id == itemId);
                if (surveyItem == null) continue;
                surveyItem.Order = order;
            }

            //todo: call save changes ....
        }
    }
}

و کدهای View متناظر

@model IList<jQueryMvcSample05.Models.Survey>
@{
    ViewBag.Title = "Index";
    var sortUrl = Url.Action(actionName: "SortItems", controllerName: "Home");
}
<h2>
    نظر سنجی‌ها</h2>
@foreach (var survey in Model)
{
    <fieldset>
        <legend>@survey.Title</legend>
        <div id="sortable-@survey.Id">
            @foreach (var surveyItem in survey.SurveyItems.OrderBy(x => x.Order))
            {            
                <div id="item-row-@surveyItem.Id">
                    <span class="handles">::</span>
                    @surveyItem.Title
                </div>
            }
        </div>
    </fieldset>
}
<div>
    لطفا برای تغییر ترتیب آیتم‌های تعریف شده، از امکان کشیدن و رها کردن تعریف شده بر
    روی آیکون‌های :: در کنار هر آیتم استفاده نمائید.
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('div[id^="sortable"]').sortable({ handle: 'span' }).bind('sortupdate', function (e, ui) {
                var sortableItemId = $(ui.item).parent().attr('id');
                var surveyId = sortableItemId.replace('sortable-', '');
                var items = [];
                $('#' + sortableItemId + ' div').each(function () {
                    items.push($(this).attr('id'));
                });
                //alert(items.join('&'));                
                $.ajax({
                    type: "POST",
                    url: "@sortUrl",
                    data: JSON.stringify({ items: items, surveyId: surveyId }),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    complete: function (xhr, status) {
                        var data = xhr.responseText;
                        if (xhr.status == 403) {
                            window.location = "/login";
                        } else if (status === 'error' || !data || data == "nok") {
                            alert('خطایی رخ داده است');
                        }
                        else {
                            alert('انجام شد');
                        }
                    }
                });
            });
        });
    </script>
}
توضیحات

در اینجا نیاز بود تا ابتدا کدهای کنترلر و View ارائه شوند، تا بتوان در مورد ارتباطات بین آن‌ها بهتر بحث کرد.
در ابتدای نمایش صفحه Home، رکوردهای نظرسنجی‌ها از منبع داده دریافت شده و به View ارسال می‌شوند. در View برنامه یک حلقه تشکیل گردیده و این موارد رندر خواهند شد.
هر نظر سنجی با یک div بیرونی که با id مساوی sortable شروع می‌شود، آغاز گردیده و گزینه‌های آن نظر سنجی نیز توسط divهایی با id مساوی item-row شروع خواهند گردید. هر کدام از این idها حاوی id رکوردهای متناظر هستند. از این id‌ها در کدهای برنامه جهت یافتن یک نظر سنجی یا یک ردیف مشخص برای به روز رسانی ترتیب آن‌ها استفاده خواهیم کرد.
ادامه کار، به تنظیمات و اعمال افزونه sortable مرتبط می‌شود. توسط تنظیم ذیل به jQuery اعلام خواهیم کرد، هرجایی یک div با id شروع شده با sortable یافتی، افزونه sortable را به آن متصل کن:
 $('div[id^="sortable"]').sortable
در ادامه در ناحیه و  div ایی که عمل کشیدن و رها شدن رخ داده، id این div را بدست آورده و سپس کلیه row-itemهای آن را در آرایه‌ای به نام items قرار می‌دهیم:
 var sortableItemId = $(ui.item).parent().attr('id');
var surveyId = sortableItemId.replace('sortable-', '');
var items = [];
$('#' + sortableItemId + ' div').each(function () {
  items.push($(this).attr('id'));
});
اکنون که به id یک نظر سنجی و همچنین idهای ردیف‌های مرتب شده حاصل دسترسی داریم، آن‌ها را توسط jQuery Ajax به کنترلر برنامه ارسال می‌کنیم:
 data: JSON.stringify({ items: items, surveyId: surveyId })
امضای اکشن متد SortItems نیز دقیقا بر همین مبنا تنظیم شده است:
 public ActionResult SortItems(int? surveyId, string[] items)

اطلاعاتی که در اینجا دریافت می‌شوند در متد updateSurvey مورد استفاده قرار خواهند گرفت. بر اساس surveyId دریافتی، نظرسنجی مرتبط را یافته و سپس به گزینه‌های آن دست خواهیم یافت. اکنون نوبت به پردازش آرایه items دریافت شده است. این آرایه بر اساس انتخاب کاربر مرتب شده است.


دریافت کدها و پروژه کامل این قسمت
jQueryMvcSample05.zip
 
مطالب
بررسی تغییرات Blazor 8x - قسمت پنجم - امکان تعریف جزیره‌های تعاملی Blazor Server
در Blazor 8x می‌توان صفحات SSR ای را به همراه Blazor server islands و یا Blazor WASM islands داشت؛ یعنی یک کامپوننت Blazor Server که داخل یک صفحه‌ی معمولی SSR قرار گرفته و با سرور، ارتباط SiganlR برقرار می‌کند و یا یک کامپوننت Blazor WASM که در قسمتی از صفحه‌ی SSR درج شده و درون مرورگر کاربر اجرا می‌شود. به هر کدام از این‌ها یک «جزیره‌ی تعاملی» گفته می‌شود (interactive island). در این قسمت، نکات مرتبط با جزایر تعاملی Blazor Server را بررسی می‌کنیم.


بررسی یک مثال: تهیه یک برنامه‌ی Blazor 8x برای نمایش لیست محصولات، به همراه جزئیات آن‌ها

به لطف وجود SSR در Blazor 8x، می‌توان HTML نهایی کامپوننت‌ها و صفحات Blazor را همانند صفحات MVC و یا Razor pages، در سمت سرور تهیه و بازگشت داد. این خروجی در نهایت یک static HTML بیشتر نیست و گاهی از اوقات ما به بیش از یک خروجی ساده HTML ای نیاز داریم.
در این مثال که بر اساس قالب dotnet new blazor --interactivity Server تهیه می‌شود، قصد داریم موارد زیر را پیاده سازی کنیم:
- صفحه‌ای که یک لیست محصولات فرضی را نمایش می‌دهد : بر اساس SSR
- صفحه‌ای که جزئیات یک محصول را نمایش می‌دهد: بر اساس SSR
- دکمه‌ای در ذیل قسمت نمایش جزئیات یک محصول، برای دریافت و نمایش لیست محصولات مشابه و مرتبط: بر اساس Blazor server islands

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


مدل برنامه: رکوردی برای ذخیره سازی اطلاعات یک محصول

namespace BlazorDemoApp.Models;

public record Product
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public required string Description { get; set; }
    public decimal Price { get; set; }

    public List<int> Related { get; set; } = new();
}
در اینجا، هدف تعریف لیستی از محصولات فرضی است؛ به همراه خاصیتی که Id محصولات مشابه را نگهداری می‌کند (خاصیت Related).


سرویس برنامه: سرویسی برای بازگشت لیست محصولات

چون Blazor Server و SSR هر دو بر روی سرور اجرا می‌شوند، از لحاظ دسترسی به اطلاعات و کار با سرویس‌ها، هماهنگی کاملی وجود داشته و می‌توان کدهای یکسان و یکدستی را در اینجا بکار گرفت.
در ادامه کدهای کامل سرویس Services\ProductStore.cs را مشاهده می‌کنید:
using BlazorDemoApp.Models;

namespace BlazorDemoApp.Services;

public interface IProductStore
{
    IList<Product> GetAllProducts();
    Product GetProduct(int id);
    IList<Product> GetRelatedProducts(int productId);
}

public class ProductStore : IProductStore
{
    private static readonly List<Product> ProductsDataSource =
        new()
        {
            new Product
            {
                Id = 1, Title = "Smart speaker", Price = 22m,
                Description =
                    "This smart speaker delivers excellent sound quality and comes with built-in voice control, offering an impressive music listening experience.",
                Related = new List<int> { 2, 3 },
            },
            new Product
            {
                Id = 2, Title = "Regular speaker", Price = 89m,
                Description =
                    "Enjoy room-filling sound with this regular speaker. With its slick design, it perfectly fits into any room in your house.",
                Related = new List<int> { 1, 3 },
            },
            new Product
            {
                Id = 3, Title = "Speaker cable", Price = 12m,
                Description =
                    "This high-quality speaker cable ensures a reliable and clear audio connection for your sound system.",
            },
        };

    public IList<Product> GetAllProducts() => ProductsDataSource;

    public Product GetProduct(int id) => ProductsDataSource.Single(p => p.Id == id);

    public IList<Product> GetRelatedProducts(int productId)
    {
        var product = ProductsDataSource.Single(x => x.Id == productId);
        return ProductsDataSource.Where(p => product.Related.Contains(p.Id)).ToList();
    }
}
هدف از این سرویس، ارائه‌ی لیست تمام محصولات، دریافت اطلاعات یک محصول و همچنین یافتن لیست محصولات مشابه یک محصول خاص است.
این سرویس را باید در فایل Program.cs برنامه به صورت زیر معرفی کرد تا در فایل‌های razor برنامه‌ی جاری قابل دسترسی شود:
builder.Services.AddScoped<IProductStore, ProductStore>();


تکمیل صفحه‌ی نمایش لیست محصولات

قصد داریم زمانیکه کاربر برای مثال به آدرس فرضی http://localhost:5136/products مراجعه کرد، با تصویر لیستی از محصولات مواجه شود:


کدهای این صفحه را که در فایل Components\Pages\Store\ProductsList.razor قرار می‌گیرند، در ادامه مشاهده می‌کنید:

@page "/Products"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

<h3>Products</h3>

@if (_products == null)
{
    <p>Loading...</p>
}
else
{
    @foreach (var item in _products)
    {
        <a href="/ProductDetails/@item.Id">
            <div>
                <div>
                    <h5>@item.Title</h5>
                </div>
                <div>
                    <h5>@item.Price.ToString("c")</h5>
                </div>
            </div>
        </a>
    }
}

@code {
    private IList<Product>? _products;

    protected override Task OnInitializedAsync() => GetProductsAsync();

    private async Task GetProductsAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _products = Store.GetAllProducts();
    }

}
توضیحات:
- جهت دسترسی به سرویس لیست محصولات، ابتدا سرویس IProductStore به این صفحه تزریق شده‌است.
- سپس در روال رویدادگردان آغازین OnInitializedAsync، کار دریافت اطلاعات و انتساب آن به لیستی، صورت گرفته‌است.
- در این متد جهت شبیه سازی یک عملیات async از یک Task.Delay استفاده شده‌است.
- چون این صفحه، یک صفحه‌ی SSR عادی است، بدون تعریف ویژگی StreamRendering در آن، پس از اجرای برنامه، هیچگاه قسمت loading که در حالت products == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد.
- لینک‌های نمایش داده شده‌ی در اینجا، به صفحه‌ی ProductDetails اشاره می‌کنند که در آن، جزئیات محصول انتخابی نمایش داده می‌شوند.


تکمیل صفحه‌ی نمایش جزئیات یک محصول


در صفحه‌ی کامپوننت Components\Pages\Store\ProductDetails.razor، کار نمایش جزئیات محصول انتخابی صورت می‌گیرد:

@page "/ProductDetails/{ProductId}"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

@if (_product == null)
{
    <p>Loading...</p>
}
else
{
    <div>
        <div>
            <h5>
                @_product.Title (@_product.Price.ToString("C"))
            </h5>
            <p>
                @_product.Description
            </p>
        </div>
        @if (_product.Related.Count > 0)
        {
            <div>
                <RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
            </div>
        }
    </div>
    <NavLink href="/Products">Back</NavLink>
}

@code {
    private Product? _product;

    [Parameter]
    public string? ProductId { get; set; }

    protected override Task OnInitializedAsync() => GetProductAsync();

    private async Task GetProductAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _product = Store.GetProduct(Convert.ToInt32(ProductId));
    }

}
توضیحات:
- باتوجه به نحوه‌ی تعریف مسیریابی این صفحه، پارامتر ProductId از طریق آدرسی مانند http://localhost:5136/ProductDetails/1 دریافت می‌شود.
- سپس این ProductId را در روال رخ‌دادگردان OnInitializedAsync، برای یافتن جزئیات محصول انتخابی از سرویس تزریقی IProductStore، بکار می‌گیریم.
- در اینجا نیز از Task.Delay برای شبیه سازی یک عملیات طولانی async مانند دریافت اطلاعات از یک بانک اطلاعاتی، کمک گرفته شده‌است.
- همچنین برای نمایش قسمت loading صفحه در حالت SSR، بازهم از StreamRendering استفاده کرده‌ایم.
- اگر دقت کرده باشید، ذیل تصویر اطلاعات محصول، دکمه‌ای نیز جهت بارگذاری اطلاعات محصولات مشابه، قرار دارد که ProductId محصول انتخابی را دریافت می‌کند:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
بنابراین در ادامه کامپوننت RelatedProducts فوق را تکمیل می‌کنیم.


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

در فایل Components\Pages\Store\RelatedProducts.razor، کار نمایش یک دکمه و سپس نمایش لیستی از محصولات مشابه، صورت می‌گیرد:
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services
@inject IProductStore Store

<button @onclick="LoadRelatedProducts">Related products</button>

@if (_loadRelatedProducts)
{
    @if (_relatedProducts == null)
    {
        <p>Loading...</p>
    }
    else
    {
        <div>
            @foreach (var item in _relatedProducts)
            {
                <a href="/ProductDetails/@item.Id">
                    <div>
                        <h5>@item.Title (@item.Price.ToString("C"))</h5>
                    </div>
                </a>
            }
        </div>
    }
}

@code{

    private IList<Product>? _relatedProducts;
    private bool _loadRelatedProducts;

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

    private async Task LoadRelatedProducts()
    {
        _loadRelatedProducts = true;
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate InteractiveServer mode
        _relatedProducts = Store.GetRelatedProducts(ProductId);
    }

}

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

مشکل! اگر در این حالت برنامه را اجرا کرده و بر روی دکمه‌ی related products کلیک کنیم، هیچ اتفاقی رخ نمی‌دهد! یعنی روال رویدادگران LoadRelatedProducts اصلا اجرا نمی‌شود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیت‌های تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند.
محدودیتی که به همراه صفحات SSR وجود دارد این است: این نوع کامپوننت‌ها و صفحات فقط یکبار رندر می‌شوند و نه بیشتر. بله می‌توان بر روی آن‌ها ده‌ها دکمه، نوارهای لغزان، دراپ‌داون و غیره را قرار داد، اما ... نمی‌توان هیچگونه تعاملی را با آن‌ها داشت. کامپوننت نهایی رندر شده و نمایش داده شده، دیگر در هیچ‌جائی اجرا نمی‌شود. در این حالت است که می‌توان تصمیم گرفت که نیاز است قسمتی از این صفحه، تعاملی شود.
به همین جهت باید نحوه‌ی رندر کامپوننت RelatedProducts را به صورت یک جزیره‌ی تعاملی Blazor server درآورد تا رویداد منتسب به دکمه‌ی related products موجود در آن، پردازش شود. بنابراین به صفحه‌ی ProductDetails.razor مراجعه کرده و rendermode@ این کامپوننت را به صورت زیر به حالت InteractiveServer تغییر می‌دهیم:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@InteractiveServer"/>
اکنون اگر برنامه را مجددا اجرا کرده و بر روی دکمه‌ی نمایش محصولات مشابه قرار گرفته در ذیل جزئیات یک محصول کلیک کنیم، بدون مشکل کار می‌کند:


نحوه‌ی پردازش پشت صحنه‌ی این نوع صفحات هم جالب است. برای اینکار به برگه‌ی network مخصوص developer tools مرورگر مراجعه کرده و مراحل رسیدن به صفحه‌ی نمایش جزئیات محصول را طی می‌کنیم:


- اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمی‌شود (و یا حتی برنامه‌های MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت می‌گیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم به‌روز رسانی نمی‌کند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.
این موارد توسط فایل blazor.web.js درج شده‌ی در کامپوننت آغازین App.razor، به صورت خودکار مدیریت می‌شوند:
<script src="_framework/blazor.web.js"></script>

به علاوه در این حالت ای‌جکسی fetch، کار دریافت مجدد فایل‌های استاتیک مرتبط یک صفحه، مانند فایل‌های js.، css.، تصاویر و غیره، مجددا انجام نمی‌شود که این مورد خود مزیتی است نسبت به حالت متداول برنامه‌های ASP.NET Core MVC و یا Razor pages. در حالت Blazor 8x SSR، فقط یک partial update از نوع Ajax ای انجام می‌شود.
به این قابلیت، enhanced navigation هم گفته می‌شود. برای مثال زمانیکه یک فرم SSR را در Blazor 8x به سمت سرور ارسال می‌کنیم، موقعیت scroll به صورت خودکار ذخیره و بازیابی می‌شود تا کاربر با یک full post back مواجه نشده و موقعیت جاری خود را در صفحه از دست ندهد (چنین ایده‌ای، یک زمانی در برنامه‌های ASP.NET Web forms هم برقرار بود و هست! به نظر مایکروسافت هنوز دلتنگ طراحی قدیمی ASP.NET Web forms است!).

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

- یک disconnect را هم در اینجا مشاهده می‌کنید. اگر به یک صفحه‌ی تعاملی مراجعه کنیم، همانطور که مشخص است، یک اتصال SignalR برقرار می‌شود (که به آن در اینجا circuit هم می‌گویند). اما اگر از این صفحه به سمت یک صفحه‌ی SSR حرکت کنیم، پس از نمایش آن صفحه، اتصال SignalR قبلی که دیگر نیازی به آن نیست، بسته خواهد شد تا منابع سمت سرور، رها شوند.


در حین disconnect، شماره ID اتصال SignalR ای که دیگر به آن نیازی نیست، به برنامه ارسال می‌شود تا به صورت خودکار در سمت سرور بسته شود. تمام این موارد توسط blazor.web.js فریم‌ورک، مدیریت می‌شوند.
در این تصویر ابتدا به آدرس http://localhost:5136/ProductDetails/1 مراجعه کرده‌ایم که سبب برقراری اتصال یک وب‌سوکت شده‌است. سپس با کلیک بر روی دکمه‌ی back، به صفحه‌ی SSR مشاهده‌ی لیست محصولات برگشته‌ایم. در این حالت، دستور قطع اتصال SignalR قبلی صادر شده‌است.


نحوه‌ی مدیریت Pre-rendering در جزایر تعاملی Blazor 8x

به صورت پیش‌فرض زمانیکه از حالت رندر InteractiveServer استفاده می‌کنیم، قابلیت pre-rendering آن نیز فعال است. یعنی ابتدا حداقل قالب و قسمت‌های ثابت کامپوننت، در سمت سرور پردازش و رندر شده و سپس به سمت کلاینت ارسال می‌شوند. در این حالت کاربر، تجربه‌ی کاربری روان‌تری را شاهد خواهد بود؛ چون برای مدتی نباید منتظر آماده شدن کل UI مرتبط باشد و حداقل، قسمت‌هایی از صفحه که تعاملی نیستند، قابل دسترسی و مشاهده هستند.
اگر به هر دلیلی نیاز به غیرفعال کردن این قابلیت را دارید، باید به صورت زیر عمل کرد:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@(new InteractiveServerRenderMode(false))"/>
در این حالت اگر برنامه را اجرا کنید، در حین نمایش صفحه‌ی اصلی در برگیرنده‌ی از نوع SSR، فقط جای این کامپوننت در صفحه مشخص می‌شود و پس از برقراری اتصال با سرور از طریق اتصال SignalR، شاهد UI کامپوننت RelatedProducts خواهیم بود، که نسبت به قبل، وقفه‌ای را سبب خواهد شد.

نحوه‌ی تعریف خواص استاتیک InteractiveServer بکار گرفته شده و یا کلاس InteractiveServerRenderMode را در ادامه مشاهده می‌کنید. جهت سهولت تعریف این موارد، سطر زیر که یک using static است، به فایل Imports.razor_ اضافه شده‌است:
@using static Microsoft.AspNetCore.Components.Web.RenderMode

public static class RenderMode
  {
    public static InteractiveServerRenderMode InteractiveServer { get; } = new InteractiveServerRenderMode();

    public static InteractiveWebAssemblyRenderMode InteractiveWebAssembly { get; } = new InteractiveWebAssemblyRenderMode();

    public static InteractiveAutoRenderMode InteractiveAuto { get; } = new InteractiveAutoRenderMode();
  }


public class InteractiveServerRenderMode : IComponentRenderMode
  {
    public InteractiveServerRenderMode()
      : this(true)
    {
    }

    public InteractiveServerRenderMode(bool prerender) => this.Prerender = prerender;

    public bool Prerender { get; }
  }


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: Blazor8x-Server-Normal.zip  
بازخوردهای پروژه‌ها
استفاده از DataAnnotations , dynamic column
سلام 
یه سوال از حضورتون این کدی که در حال حاضر استفاده میکنم ، . میخوام از (DisplayName) استفاده کنم که باز هم از خود نام پروپرتی‌ها در codefirst  استفاده میکنه ، میشه بفرمایید کدوم قسمت رو باید تغییر بدم
public IPdfReportData CreatePdfReport(string title)
        {
            if (title=="" || title==null)
            {
                title = "گذارش";
            }
            return new PdfReport().DocumentPreferences(doc =>
               {
                   doc.RunDirection(PdfRunDirection.RightToLeft);
                   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(AppPath.ApplicationPath + "\\fonts\\BNazanin.ttf",
                                     Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
               })
                .PagesFooter(footer =>
                {
                    footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
                })
                .PagesHeader(header =>
                {
                    header.DefaultHeader(defaultHeader =>
                    {
                       // defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                        defaultHeader.Message(title);
                    });
                })
                .MainTableTemplate(template =>
                {
                    template.BasicTemplate(BasicTemplate.SilverTemplate);
                })
                .MainTablePreferences(table =>
                {
                    table.ColumnsWidthsType(TableColumnWidthType.Relative);
                })
                .MainTableDataSource(dataSource =>
                {
                    Cash.Models.ApplicationDbContext db = new Models.ApplicationDbContext();
                    var listOfRows = db.Users.Join(

                        db.CheckOuts, u => u.Id, c => c.ApplicationUserID,
                        (u, c) => new { u.FName, u.Lname, u.NationalCode, c.Creditor, c.Debtor, c.Decsription }
                        ).Where(x => x.Creditor != 0).ToList();


                    dataSource.AnonymousTypeList(listOfRows);
                })
                .MainTableSummarySettings(summary =>
                {
                    summary.OverallSummarySettings("جمع کل");
                    summary.PreviousPageSummarySettings("نقل از صفحه قبل");
                    summary.PageSummarySettings("جمع صفحه");
                })
                .MainTableAdHocColumnsConventions(adHocColumns =>
                {
                    //We want sum of the int columns
                    adHocColumns.AddTypeAggregateFunction(
                        typeof(Int64),
                        new AggregateProvider(AggregateFunction.Sum)
                        {
                            DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
                        });
                    adHocColumns.AddTypeAggregateFunction(
                      typeof(Decimal),
                      new AggregateProvider(AggregateFunction.Sum)
                      {
                          DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
                      });

                    //We want to dispaly all of the dateTimes as ShamsiDateTime
                    adHocColumns.AddTypeDisplayFormatFormula(
                        typeof(DateTime),
                        data => { return PersianDate.ToPersianDateTime((DateTime)data); }
                    );
                    adHocColumns.ShowRowNumberColumn(true);
                    adHocColumns.RowNumberColumnCaption("ردیف");
                })
                .MainTableEvents(events =>
                {
                    events.DataSourceIsEmpty(message: "هیچ اطلاعاتی موجود نیست");
                })
                .Export(export =>
                {
                    export.ToExcel();
                    export.ToXml();
                })
                .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\AdHocColumnsSampleRpt.pdf"));
        }

مطالب
اعمال صفحه بندی به کمک OFFSET و FETCH در SQL Server 2012
در T-SQL 2012 قابلیت صفحه بندی، نمایش خروجی یک Query فراهم گردیده است، که برای نرم افزارهای تحت وب بسیار پرکاربرد میباشد، به عنوان مثال، از جمله کاربردهای بارز آن، می‌توان به نمایش نتیجه یک جستجو بصورت صفحه بندی با تعداد رکورد محدود،اشاره نمود.
 مایکروسافت برای ایجاد قابلیت صفحه بندی و محدود نمودن نمایش خروجی یک Query، تغییراتی را در Syntax مربوط به Order by ایجاد نموده است، که در ذیل مشاهده می‌نمایید:
ORDER BY order_by_expression
    [ COLLATE collation_name ] 
    [ ASC | DESC ] 
    [ ,...n ] 
[ <offset_fetch> ]


<offset_fetch> ::=
{ 
    OFFSET { integer_constant | offset_row_count_expression } { ROW | ROWS }
    [
      FETCH { FIRST | NEXT } {integer_constant | fetch_row_count_expression } { ROW | ROWS } ONLY
    ]
}
OFFSET (نقطه شروع) :  شامل یک پارامتر است،بطوریکه،پارامتر فوق می‌تواند یک عدد (integer_constant) یا یک عبارت (offset_row_count_expression) بپذیرد. در اینجا منظور از عبارت می‌تواند یک Subquery باشد، که خروجی آن فقط یک مقدار عددی است. یا یک متغیر و غیرو... 
در مورد ROW یا ROWS باید بگویم باهم فرقی ندارند.
FETCH : همانند OFFSET شامل یک پارامتر است، و پارامتر آن می‌تواند یک عدد یا عبارت بپذیرد.
Next یا First نیز با هم تفاوتی ندارند و جهت سازگاری با ANSI می‌باشند.
OFFSET : در وافع تعداد سطر قابل حذف، پیش از نمایش اولین سطر در خروجی را بیان می‌کند.
FETCH : بیانگر تعداد رکورد قابل نمایش در یک صفحه می‌باشد.
برای درک بیشتر مثالی می‌زنیم:
ابتدا بوسیله Script زیر یک جدول ایجاد می‌نماییم، سپس چند رکورد درون آن درج می‌کنیم:
Create Table Testoffset
(BusinessEntityID int,
FirstName varchar(100) ,
LastName varchar(100)
);

Insert into Testoffset (BusinessEntityID,FirstName,LastName)
            Values(1,'Ken','Sánchez')
    ,(2,'Terri','Duffy')
,(3,'Roberto','Tamburello')
,(4,'Rob','Walters')
,(5,'Gail','Erickson')
,(6,'Jossef','Goldberg')
,(7,'Dylan','Miller')
,(8,'Diane','Margheim')
,(9,'Gigi','Matthew')
,(10,'Michael','Raheem')
در ادامه Script زیر را اجرا نمایید، تا تعداد رکورد‌های درج شده را مشاهده کنید:

در شکل، سه سطر (منظور رکورد 4و5و6) در کادر قرمز رنگ دیده می‌شود، می‌خواهیم Script ی ایجاد نماییم، که فقط سه سطر فوق را نمایش دهد. بنابراین خواهیم داشت:
SELECT BusinessEntityID, FirstName, LastName
FROM Testoffset
ORDER BY BusinessEntityID
OFFSET 3 ROWS
FETCH First 3 ROWS only
خروجی:

اگر به Query اجرا شده دقت کنیم. در قسمت Order By جلوی Offset مقدار 3 اختصاص داده شده بود، یعنی نقطه شروع از سطر چهارم میباشد، به عبارت دیگر مقداری که به Offset اختصاص داده می‌شود، به SQL Server می‌فهماند،چه تعداد رکورد را نمایش ندهد. اگر شکل اول و دوم را با هم مقایسه نمایید، براحتی متوجه می‌شوید که OFFSET نقطه شروع را مشخص کرده است.
مقداریکه برای Fetch در نظر گرفته شده بود برابر 3 است، که بیانگر تعداد سطر نمایش داده شده در خروجی از نقطه آغازین (offset) می‌باشد.
امیدوارم مفید واقع شده باشد.
مطالب دوره‌ها
کار با فرم‌ها در بوت استرپ 3
در مطلب «استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب» به نکات مرتبط با کار با فرم‌ها در بوت استرپ 2 پرداخته شد. همچنین مطالبی مانند «ویرایش قالب پیش فرض Add View در ASP.NET MVC برای سازگار سازی آن با Twitter bootstrap» برای خودکار سازی تولید فرم‌های بوت استرپ 2 در برنامه‌های ASP.NET MVC و نکات «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» نیز بررسی شدند. در بوت استرپ 3، بسیاری از این نکات تغییر کرده‌اند و نیاز است با نحوه ارتقاء فرم‌های بوت استرپ 2 به 3 و کلا نحوه کار با فرم‌ها در بوت استرپ 3 بیشتر آشنا شد.


نحوه ارتقاء فرم‌های بوت استرپ 2 به 3

تمام این تغییرات در بوت استرپ 3، جهت پیاده سازی ایده mobile-first بودن آن است. برای مثال فرم‌های افقی بوت استرپ 3 با کوچک شدن اندازه صفحه، به صورت خودکار واکنش نشان داده و تبدیل به فرم‌های معمولی که اجزای آن به صورت یک stack عمودی قرار گرفته‌اند، می‌شوند.
اکنون اگر فرم‌هایی را دارید که در برنامه‌های پیشین خود از بوت استرپ 2 استفاده کرده‌اند، نیاز است تغییرات ذیل را به آن‌ها اعمال کنید تا با سیستم جدید بوت استرپ 3 سازگار شوند:

- کلاس control-group را به کلاس form-group تبدیل کنید.
- form-search حذف شده است. آن‌را با form-inline جایگزین کنید.
- دیگر نیازی به استفاده از input-block-level نیست؛ از آنجائیکه به صورت پیش فرض کلیه inputها دارای عرض 100 درصد هستند.
- help-inline حذف شده است. آن‌را با help-block جایگزین کنید.
- عرض ستون‌ها را در فرم‌های افقی، برچسب‌ها و کنترل‌ها مشخص کنید.
- کلاس controls حذف شده است.
- کلاس form-control را به inputها و selectها اضافه کنید.
- checkboxها و radioها باید در یک div محصور شوند.
- کلاس‌های radio.inline و checkbox.inline باید با inline جایگزین شوند.
- کلاس‌های input-small به input-sm و input-large به input-lg تبدیل شده‌اند.
- کلاس‌های input-prepend با input-group و input-append با input-group جایگزین شده‌اند.
- کلاس alert-error حذف شده‌است. بجای آن می‌شود از alert-warning استفاده کرد.
- کلاس alert-block را با alert جایگزین کنید.


ایجاد اولین فرم افقی با بوت استرپ 3

فرض کنید که قصد داریم یک چنین فرم افقی را توسط امکانات بوت استرپ 3 ایجاد کنیم:



همانطور که ملاحظه می‌کنید، با کوچک شدن اندازه صفحه، این فرم نیز تغییر شکل می‌دهد:



کدهای کامل این فرم را در ادامه ملاحظه می‌کنید:
    <div class="container">
        <h4 class="alert alert-info">
            فرم‌های بوت استرپ 3</h4>
        <div class="row">
            <article class="registrationform">
                <h2>
                    فرم ثبت نام</h2>               
                <form class="registration form-horizontal" action="#">
                <fieldset id="personalinfo">
                    <legend>اطلاعات شخصی</legend>
                    <section class="row">
                        <label class="col col-lg-4 control-label" for="myname">
                            نام</label>
                        <div class="controls">
                            <input class="col col-lg-8" type="text" name="myname" 
                                   id="myname" autofocus placeholder="نام و نام خانوادگی"
                                   required>
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                    <section class="row">
                        <label class="col col-lg-4 control-label" for="companyname">
                            نام شرکت</label>
                        <div class="controls">
                            <input class="col col-lg-8" type="text" name="companybname" id="companyname" />
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                    <section class="row">
                        <label class="col col-lg-4 control-label" for="myemail">
                            ایمیل</label>
                        <div class="controls">
                            <input class="col col-lg-8" type="email" name="myemail" id="myemail" 
                                   required autocomplete="off" />
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                </fieldset>
                <!-- personal info -->
                <fieldset id="otherinfo">
                    <legend>سایر اطلاعات</legend>
                    <section class="row">
                        <label class="col col-lg-4 control-label">
                            نوع درخواست</label>
                        <div class="controls col col-lg-8">
                            <label class="radio">
                                <input type="radio" name="requesttype" value="question" />
                                سؤال
                            </label>
                            <label class="radio">
                                <input type="radio" name="requesttype" value="comment" />
                                انتقاد
                            </label>
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                    <section class="row">
                        <label class="col col-lg-4 control-label">
                            خبرنامه</label>
                        <div class="controls col col-lg-8">
                            <label class="checkbox">
                                <input type="checkbox" id="subscribe" name="subscribe" checked value="yes" />
                               آیا مایل به دریافت ایمیل‌های خبرنامه ما هستید؟
                            </label>
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                    <section class="row">
                        <label class="col col-lg-4 control-label" for="reference">
                            چطور از وجود سایت ما آگاه شدید؟</label>
                        <div class="controls col col-lg-8">
                            <select name="reference" id="reference">
                                <option>لطفا انتخاب کنید...</option>
                                <option value="friend">از طریق یک دوست</option>
                                <option value="facebook">Facebook</option>
                                <option value="twitter">Twitter</option>
                            </select>
                        </div>
                        <!-- controls -->
                    </section><!-- row -->
                </fieldset>
                <button class="btn" type="submit">
                    ارسال</button>
                </form>
            </article>
        </div>
        <!-- end row -->
    </div>
    <!-- /container -->
توضیحات:

- باید درنظر داشت که اگر هیچگونه فرمتی را به فرم‌های بوت استرپ 3 اعمال نکنیم، به صورت پیش فرض فرمت دهی شده و تبدیل به فرم‌های عمودی شکیلی می‌شوند که شاید از دیدگاه خیلی‌ها مناسب بوده و نیاز به تغییرات خاصی نداشته باشند.
- برای تبدیل این فرم عمودی پیش فرض، به فرم‌های افقی دو ستونه، نیاز است یک سری کلاس بوت استرپ 3 را به المان‌های آن اضافه کنیم. برای این منظور ابتدا کلاس form-horizontal را به تگ فرم اضافه می‌کنیم.
- هر سطر فرم، در یک المان section با کلاس row قرار خواهد گرفت.
- اکنون هر سطر، از یک برچسب به همراه یک یا چند المان تشکیل خواهد شد. در هر سطر، کنترل‌ها در یک div با کلاس controls قرار می‌گیرند.
- برای اینکه برچسب‌های هر ردیف با کنترل‌ها و المان‌های آن ردیف، تراز شوند، تنها کافی است به آن‌ها کلاس control-label را اضافه کنیم.
در ادامه تمام این مراحل را باید به ازای هر سطر فرم تکرار کنیم.

- زمانیکه به radio buttons یا check boxes می‌رسیم، باید به چند نکته دقت داشت:
الف) حین کار با radio buttons، علاوه بر برچسب آن سطر که با label مشخص می‌شود، هر radio button نیز باید داخل یک label با کلاس radio محصور شود.
ب) تمام radio buttons یک سطر نیز باید در یک div ایی با کلاس controls محصور شوند.
این نکته در مورد check boxes نیز صادق است.

با رعایت همین چند نکته ساده می‌توان به یک طراحی دو ستونی خودکار واکنشگرا رسید.



اصلاح قالب ایجاد فرم‌های خودکار ASP.NET MVC بر اساس بوت استرپ 3

مطلب «ویرایش قالب پیش فرض Add View در ASP.NET MVC برای سازگار سازی آن با Twitter bootstrap» جهت بوت استرپ 2 تهیه شده بود. فایل نهایی ویرایش شده آن‌را با توجه به توضیحات مطلب جاری برای بوت استرپ 3 از پیوست انتهای بحث دریافت کنید و برای استفاده از آن فقط کافی است آن‌را در مسیر CodeTemplates\AddView\CSHTML\CreateBootstrap3Form.tt ریشه پروژه جاری خود کپی و به پروژه اضافه کنید تا در صفحه دیالوگ Add view ظاهر شود (خاصیت custom tool آن‌را هم خالی کنید).


در مورد اعتبارسنجی‌های فرم‌ها چطور؟

اصلاح مطالبی مانند «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» جهت کار با فرم‌های بوت استرپ 3 بسیار ساده است. از این جهت که در کدهای آن فقط باید نام کلاس‌های CSS قدیمی به جدید ویرایش شوند. مابقی کدها یکسان است. مثلا نام کلاس control-group شده است form-group (همان توضیحات ابتدای بحث جاری). کلاس‌های error شده‌اند has-error و success شده است has-success.



فایل‌های نهایی این قسمت را از اینجا نیز می‌توانید دریافت کنید:
bs3-sample05.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 - حال اگر برنامه را اجرا نمایید تمامی موارد را فارسی مشاهده خواهید نمود ( شکل ذیل )

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