مطالب
ASP.NET MVC #10

آشنایی با روش‌های مختلف ارسال اطلاعات یک درخواست به کنترلر

تا اینجا با روش‌های مختلف ارسال اطلاعات از یک کنترلر به View متناظر آن آشنا شدیم. اما حالت عکس آن چطور؟ مثلا در ASP.NET Web forms، دوبار بر روی یک دکمه کلیک می‌کردیم و در روال رویدادگردان کلیک آن، همانند برنامه‌های ویندوزی، دسترسی به اطلاعات اشیاء قرار گرفته بر روی فرم را داشتیم. در ASP.NET MVC که کلا مفهوم Events را حذف کرده و وب را همانگونه که هست ارائه می‌دهد و به علاوه کنترلرهای آن، ارجاع مستقیمی را به هیچکدام از اشیاء بصری در خود ندارند (برای مثال کنترلر و متدی در آن نمی‌دانند که الان بر روی View آن، یک گرید قرار دارد یا یک دکمه یا اصلا هیچی)، چگونه می‌توان اطلاعاتی را از کاربر دریافت کرد؟
در اینجا حداقل سه روش برای دریافت اطلاعات از کاربر وجود دارد:
الف) استفاده از اشیاء Context مانند HttpContext، Request، RouteData و غیره
ب) به کمک پارامترهای اکشن متدها
ج) با استفاده از ویژگی جدیدی به نام Data Model Binding

یک مثال کاربردی
قصد داریم یک صفحه لاگین ساده را طراحی کنیم تا بتوانیم هر سه حالت ذکر شده فوق را در عمل بررسی نمائیم. بحث HTML Helpers استاندارد ASP.NET MVC را هم که در قسمت قبل شروع کردیم، لابلای توضیحات قسمت جاری و قسمت‌های بعدی با مثال‌های کاربردی دنبال خواهند شد.
بنابراین یک پروژه جدید خالی ASP.NET MVC را شروع کرده و مدلی را به نام Account با محتوای زیر به پوشه Models برنامه اضافه کنید:

namespace MvcApplication6.Models
{
public class Account
{
public string Name { get; set; }
public string Password { get; set; }
}
}

یک کنترلر جدید را هم به نام LoginController به پوشه کنترلرهای برنامه اضافه کنید. بر روی متد Index پیش فرض آن کلیک راست نمائید و یک View خالی را اضافه نمائید.
در ادامه به فایل Global.asax.cs مراجعه کرده و نام کنترلر پیش‌فرض را به Login تغییر دهید تا به محض شروع برنامه در VS.NET، صفحه لاگین ظاهر شود.
کدهای کامل کنترلر لاگین را در ادامه ملاحظه می‌کنید:

using System.Web.Mvc;
using MvcApplication6.Models;

namespace MvcApplication6.Controllers
{
public class LoginController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(); //Shows the login page
}

[HttpPost]
public ActionResult LoginResult()
{
string name = Request.Form["name"];
string password = Request.Form["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
[ActionName("LoginResultWithParams")]
public ActionResult LoginResult(string name, string password)
{
if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
public ActionResult Login(Account account)
{
if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}
}
}

همچنین Viewهای متناظر با این کنترلر هم به شرح زیر هستند:
فایل index.cshtml به نحو زیر تعریف خواهد شد:

@model MvcApplication6.Models.Account
@{
ViewBag.Title = "Index";
}
<h2>
Login</h2>
@using (Html.BeginForm(actionName: "LoginResult", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "LoginResultWithParams", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult(string name, string password)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "Login", controllerName: "Login"))
{
<fieldset>
<legend>Test Login(Account acc)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}

و فایل result.cshtml هم محتوای زیر را دارد:

@{
ViewBag.Title = "Result";
}
<fieldset>
<legend>Login Result</legend>
<p>
@ViewBag.Message</p>
</fieldset>

توضیحاتی در مورد View لاگین برنامه:
در View صفحه لاگین سه فرم را مشاهده می‌کنید. در برنامه‌های ASP.NET Web forms در هر صفحه، تنها یک فرم را می‌توان تعریف کرد؛ اما در ASP.NET MVC این محدودیت برداشته شده است.
تعریف یک فرم هم با متد کمکی Html.BeginForm انجام می‌شود. در اینجا برای مثال می‌شود یک فرم را به کنترلری خاص و متدی مشخص در آن نگاشت نمائیم.
از عبارت using هم برای درج خودکار تگ بسته شدن فرم، در حین dispose شیء MvcForm کمک گرفته شده است.
برای نمونه خروجی HTML اولین فرم تعریف شده به صورت زیر است:

<form action="/Login/LoginResult" method="post">   
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: <input id="Name" name="Name" type="text" value="" /></p>
<p>
Password: <input id="Password" name="Password" type="password" /></p>
<input type="submit" value="Login" />
</fieldset>
</form>

توسط متدهای کمکی Html.TextBoxFor و Html.PasswordFor یک TextBox و یک PasswordBox به صفحه اضافه می‌شوند، اما این For آن‌ها و همچنین lambda expression ایی که بکارگرفته شده برای چیست؟
متدهای کمکی Html.TextBox و Html.Password از نگارش‌های اولیه ASP.NET MVC وجود داشتند. این متدها نام خاصیت‌ها و پارامترهایی را که قرار است به آن‌ها بایند شوند، به صورت رشته می‌پذیرند. اما با توجه به اینکه در اینجا می‌توان یک strongly typed view را تعریف کرد،‌ تیم ASP.NET MVC بهتر دیده است که این رشته‌ها را حذف کرده و از قابلیتی به نام Static reflection استفاده کند (^ و ^).

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


توضیحاتی در مورد نحوه عملکرد کنترلر لاگین برنامه:

در این کنترلر صرفنظر از محتوای متدهای آن‌ها، دو نکته جدید را می‌توان مشاهده کرد. استفاده از ویژگی‌های HttpPost، HttpGet و ActionName. در اینجا به کمک ویژگی‌های HttpGet و HttpPost در مورد نحوه دسترسی به این متدها، محدودیت قائل شده‌ایم. به این معنا که تنها در حالت Post است که متد LoginResult در دسترس خواهد بود و اگر شخصی نام این متدها را مستقیما در مرورگر وارد کند (یا همان HttpGet پیش فرض که نیازی هم به ذکر صریح آن نیست)، با پیغام «یافت نشد» مواجه می‌گردد.
البته در نگارش‌های اولیه ASP.NET MVC از ویژگی دیگری به نام AcceptVerbs برای مشخص سازی نوع محدودیت فراخوانی یک اکشن متد استفاده می‌شد که هنوز هم معتبر است. برای مثال:

[AcceptVerbs(HttpVerbs.Get)]

یک نکته امنیتی:
همیشه متدهای Delete خود را به HttpPost محدود کنید. به این علت که ممکن است در طی مثلا یک ایمیل، آدرسی به شکل http://localhost/blog/delete/10 برای شما ارسال شود و همچنین سشن کار با قسمت مدیریتی بلاگ شما نیز در همان حال فعال باشد. URL ایی به این شکل، در حالت پیش فرض، محدودیت اجرایی HttpGet را دارد. بنابراین احتمال اجرا شدن آن بالا است. اما زمانیکه متد delete را به HttpPost محدود کردید، دیگر این نوع حملات جواب نخواهند داد و حتما نیاز خواهد بود تا اطلاعاتی به سرور Post شود و نه یک Get ساده (مثلا کلیک بر روی یک لینک معمولی)، کار حذف را انجام دهد.


توسط ActionName می‌توان نام دیگری را صرفنظر از نام متد تعریف شده در کنترلر، به آن متد انتساب داد که توسط فریم ورک در حین پردازش نهایی مورد استفاده قرار خواهد گرفت. برای مثال در اینجا به متد LoginResult دوم، نام LoginResultWithParams را انتساب داده‌ایم که در فرم دوم تعریف شده در View لاگین برنامه مورد استفاده قرار گرفته است.
وجود این ActionName هم در مثال فوق ضروری است. از آنجائیکه دو متد هم نام را معرفی کرده‌ایم و فریم ورک نمی‌داند که کدامیک را باید پردازش کند. در این حالت (بدون وجود ActionName معرفی شده)، برنامه با خطای زیر مواجه می‌گردد:

The current request for action 'LoginResult' on controller type 'LoginController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult LoginResult() on type MvcApplication6.Controllers.LoginController
System.Web.Mvc.ActionResult LoginResult(System.String, System.String) on type MvcApplication6.Controllers.LoginController

برای اینکه بتوانید نحوه نگاشت فرم‌ها به متدها را بهتر درک کنید، بر روی چهار return View موجود در کنترلر لاگین برنامه، چهار breakpoint را تعریف کنید. سپس برنامه را در حالت دیباگ اجرا نمائید و تک تک فرم‌ها را یکبار با کلیک بر روی دکمه لاگین، به سرور ارسال نمائید.


بررسی سه روش دریافت اطلاعات از کاربر در ASP.NET MVC

الف) استفاده از اشیاء Context

در ویژوال استودیو، در کنترلر لاگین برنامه، بر روی کلمه Controller کلیک راست کرده و گزینه Go to definition را انتخاب کنید. در اینجا بهتر می‌توان به خواصی که در یک کنترلر به آن‌ها دسترسی داریم، نگاهی انداخت:

public HttpContextBase HttpContext { get; }
public HttpRequestBase Request { get; }
public HttpResponseBase Response { get; }
public RouteData RouteData { get; }

در بین این خواص و اشیاء مهیا، Request و RouteData بیشتر مد نظر ما هستند. در مورد RouteData در قسمت ششم این سری، توضیحاتی ارائه شد. اگر مجددا Go to definition مربوط به HttpRequestBase خاصیت Request را بررسی کنیم، موارد ذیل جالب توجه خواهند بود:

public virtual NameValueCollection QueryString { get; } // GET variables
public NameValueCollection Form { get; } // POST variables
public HttpCookieCollection Cookies { get; }
public NameValueCollection Headers { get; }
public string HttpMethod { get; }

توسط خاصیت Form شیء Request می‌توان به مقادیر ارسالی به سرور در یک کنترلر دسترسی یافت که نمونه‌ای از آن‌را در اولین متد LoginResult می‌توانید مشاهده کنید. این روش در ASP.NET Web forms هم کار می‌کند. جهت اطلاع این روش با ASP کلاسیک دهه نود هم سازگار است!
البته این روش آنچنان مرسوم نیست؛ چون NameValueCollection مورد استفاده، ایندکسی عددی یا رشته‌ای را می‌پذیرد که هر دو با پیشرفت‌هایی که در زبان‌های دات نتی صورت گرفته‌اند، دیگر آنچنان مطلوب و روش مرجح به حساب نمی‌آیند. اما ... هنوز هم قابل استفاده است.
به علاوه اگر دقت کرده باشید در اینجا HttpContextBase داریم بجای HttpContext. تمام این کلاس‌های پایه هم به جهت سهولت انجام آزمون‌های واحد در ASP.NET MVC ایجاد شده‌اند. کار کردن مستقیم با HttpContext مشکل بوده و نیاز به شبیه سازی فرآیندهای رخ داده در یک وب سرور را دارد. اما این کلاس‌های پایه جدید، مشکلات یاد شده را به همراه ندارند.


ب) استفاده از پارامترهای اکشن متدها

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

Request.Form
Request.QueryString
Request.Files
RouteData.Values

برای نمونه در متدی که با نام LoginResultWithParams مشخص شده، چون نام‌های دو پارامتر آن، با نام‌های بکارگرفته شده در Html.TextBoxFor و Html.PasswordFor یکی هستند، با مقادیر ارسالی آن‌ها مقدار دهی شده و سپس در متد قابل استفاده خواهند بود. در پشت صحنه هم از همان رکوردهای موجود در Request.Form (یا سایر موارد ذکر شده)، استفاده می‌شود. در اینجا هر رکورد مثلا مجموعه Request.Form، کلیدی مساوی نام ارسالی به سرور را داشته و مقدار آن هم، مقداری است که کاربر وارد کرده است.
اگر همانندی یافت نشد، آن پارامتر با نال مقدار دهی می‌گردد. بنابراین اگر برای مثال یک پارامتر از نوع int را معرفی کرده باشید و چون نوع int، نال نمی‌پذیرد، یک استثناء بروز خواهد کرد. برای حل این مشکل هم می‌توان از Nullable types استفاده نمود (مثلا بجای int id نوشت int? id تا مشکلی جهت انتساب مقدار نال وجود نداشته باشد).
همچنین باید دقت داشت که این بررسی تطابق‌های بین نام عناصر HTML و نام پارامترهای متدها، case insensitive است و به کوچکی و بزرگی حروف حساس نیست. برای مثال، پارامتر معرفی شده در متد LoginResult مساوی string name است، اما نام خاصیت تعریف شده در کلاس Account مساوی Name بود.


ج) استفاده از ویژگی جدیدی به نام Data Model Binding

در ASP.NET MVC چون می‌توان با یک Strongly typed view کار کرد، خود فریم ورک این قابلیت را دارد که اطلاعات ارسالی یکی فرم را به صورت خودکار به یک وهله از یک شیء نگاشت کند. در اینجا model binder وارد عمل می‌شود، مقادیر ارسالی را استخراج کرده (اطلاعات دریافتی از Form یا کوئری استرینگ‌ها یا اطلاعات مسیریابی و غیره) و به خاصیت‌های یک شیء نگاشت می‌کند. بدیهی است در اینجا این خواص باید عمومی باشند و هم نام عناصر HTML ارسالی به سرور. همچنین model binder پیش فرض ASP.NET MVC را نیز می‌توان کاملا تعویض کرد و محدود به استفاده از model binder توکار آن نیستیم.
وجود این Model binder، کار با ORMها را بسیار لذت بخش می‌کند؛ از آنجائیکه خود فریم ورک ASP.NET MVC می‌تواند عناصر شیءایی را که قرار است به بانک اطلاعاتی اضافه شود، یا در آن به روز شود، به صورت خودکار ایجاد کرده یا به روز رسانی نماید.
نحوه کار با model binder را در متد Login کنترلر فوق می‌توانید مشاهده کنید. بر روی return View آن یک breakpoint قرار دهید. فرم سوم را به سرور ارسال کنید و سپس در VS.NET خواص شیء ساخته شده را در حین دیباگ برنامه، بررسی نمائید.
بنابراین تفاوتی نمی‌کند که از چندین پارامتر استفاده کنید یا اینکه کلا یک شیء را به عنوان پارامتر معرفی نمائید. فریم ورک سعی می‌کند اندکی هوش به خرج داده و مقادیر ارسالی به سرور را به پارامترهای تعریفی، حتی به خواص اشیاء این پارامترهای تعریف شده، نگاشت کند.

در ASP.NET MVC سه نوع Model binder وجود دارند:
1) Model binder پیش فرض که توضیحات آن به همراه مثالی ارائه شد.
2) Form collection model binder که در ادامه توضیحات آن‌را مشاهده خواهید نمود.
3) HTTP posted file base model binder که توضیحات آن به قسمت بعدی موکول می‌شود.

یک نکته:
اولین متد LoginResult کنترلر را به نحو زیر نیز می‌توان بازنویسی کرد:
[HttpPost]
[ActionName("LoginResultWithFormCollection")]
public ActionResult LoginResult(FormCollection collection)
{
string name = collection["name"];
string password = collection["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

در اینجا FormCollection به صورت خودکار بر اساس مقادیر ارسالی به سرور توسط فریم ورک تشکیل می‌شود (FormCollection هم یک نوع model binder ساده است) و اساسا یک NameValueCollection می‌باشد.
بدیهی است در این حالت باید نگاشت مقادیر دریافتی، به متغیرهای متناظر با آن‌ها، دستی انجام شود (مانند مثال فوق) یا اینکه می‌توان از متد UpdateModel کلاس Controller هم استفاده کرد:

[HttpPost]
public ActionResult LoginResultUpdateFormCollection(FormCollection collection)
{
var account = new Account();
this.UpdateModel(account, collection.ToValueProvider());

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

متد توکار UpdateModel، به صورت خودکار اطلاعات FormCollection دریافتی را به شیء مورد نظر، نگاشت می‌کند.
همچنین باید عنوان کرد که متد UpdateModel، در پشت صحنه از اطلاعات Model binder پیش فرض و هر نوع Model binder سفارشی که ایجاد کنیم استفاده می‌کند. به این ترتیب زمانیکه از این متد استفاده می‌کنیم، اصلا نیازی به استفاده از FormCollection نیست و متد بدون آرگومان زیر هم به خوبی کار خواهد کرد:

[HttpPost]
public ActionResult LoginResultUpdateModel()
{
var account = new Account();
this.UpdateModel(account);

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

استفاده از model binderها همینجا به پایان نمی‌رسد. نکات تکمیلی آن‌ها در قسمت بعدی بررسی خواهند شد.

مطالب
سفارشی سازی Header و Footer در PdfReport
صورت مساله:
- می‌خواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش می‌دهد، به عبارت «صفحه x از n» تغییر دهیم.
- می‌خواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالب‌های PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).

تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آن‌ها طراحی شده‌اند؛ قالب‌ها، هدر، فوتر، منابع داده، قالب‌های نمایش سلول‌ها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازی‌های پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، می‌توانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.

مدل‌های مورد استفاده:
namespace PdfReportSamples.Models
{
    public class Task
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int PercentCompleted { set; get; }
        public bool IsActive { set; get; }
        public User Assignee { set; get; }
    }
}

using System;

namespace PdfReportSamples.Models
{
    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public string LastName { set; get; }
        public long Balance { set; get; }
        public DateTime RegisterDate { set; get; }
    }
}
توسط این مدل‌ها قصد داریم تعدادی فعالیت (Task) را که به تعدادی کاربر انتساب یافته است، نمایش دهیم. همچنین نمایش مقادیر خواص تو در تو  نیز در اینجا مد نظر است؛ برای مثال ستونی مانند این:
 column.PropertyName<Task>(x => x.Assignee.Name) 
کدهای کامل مثال را در ادامه ملاحظه خواهید نمود:
using System;
using System.Collections.Generic;
using System.Drawing;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeaderFooterPdfReport
    {
        readonly CustomHeader _customHeader = new CustomHeader();
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(_customHeader);
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 22,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = false,
                    TopMargin = 7
                });
            })
            .MainTableDataSource(dataSource =>
            {
                var rows = new List<Task>();
                var rnd = new Random();
                for (int i = 1; i < 210; i++)
                {
                    rows.Add(new Task
                    {
                        Assignee = new User
                        {
                            Id = i,
                            Name = "user-" + i
                        },
                        IsActive = rnd.Next(0, 2) == 1 ? true : false,
                        Name = "task-" + i
                    });
                }
                dataSource.StronglyTypedList(rows);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Name);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(3);
                    column.HeaderCell("Task Name");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Assignee.Name); // nested property support
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(3);
                    column.HeaderCell("Assignee");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.IsActive);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("Active");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\CustomHeaderFooterPdfReportSample.pdf"));
        }
    }
}

به همراه Header سفارشی:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeader : IPageHeader
    {
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData)
        {
            return null;
        }

        Image _image;
        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            if (_image == null) //cache is empty
            {
                var templatePath = AppPath.ApplicationPath + "\\data\\PdfHeaderTemplate.pdf";
                _image = PdfImageHelper.GetITextSharpImageFromPdfTemplate(pdfWriter, templatePath);
            }

            var table = new PdfPTable(1);
            var cell = new PdfPCell(_image, true) { Border = 0 };
            table.AddCell(cell);
            return table;
        }
    }
}

و Footer سفارشی استفاده شده:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomFooter : IPageFooter
    {
        PdfContentByte _pdfContentByte;
        readonly IPdfFont _pdfRptFont;
        readonly Font _font;
        readonly PdfRunDirection _direction;
        PdfTemplate _template;

        public CustomFooter(IPdfFont pdfRptFont, PdfRunDirection direction)
        {
            _direction = direction;
            _pdfRptFont = pdfRptFont;
            _font = _pdfRptFont.Fonts[0];
        }

        public void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            _template.BeginText();
            _template.SetFontAndSize(_pdfRptFont.Fonts[0].BaseFont, 8);
            _template.SetTextMatrix(0, 0);
            _template.ShowText((writer.PageNumber - 1).ToString());
            _template.EndText();
        }

        public void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            var pageSize = document.PageSize;
            var text = "Page " + writer.PageNumber + " / ";
            var textLen = _font.BaseFont.GetWidthPoint(text, _font.Size);
            var center = (pageSize.Left + pageSize.Right) / 2;
            var align = _direction == PdfRunDirection.RightToLeft ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT;

            ColumnText.ShowTextAligned(
                        canvas: _pdfContentByte,
                        alignment: align,
                        phrase: new Phrase(text, _font),
                        x: center,
                        y: pageSize.GetBottom(25),
                        rotation: 0,
                        runDirection: (int)_direction,
                        arabicOptions: 0);

            var x = _direction == PdfRunDirection.RightToLeft ? center - textLen : center + textLen;
            _pdfContentByte.AddTemplate(_template, x, pageSize.GetBottom(25));
        }

        public void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData)
        {
            _pdfContentByte = writer.DirectContent;
            _template = _pdfContentByte.CreateTemplate(50, 50);
        }
    }
}

البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شده‌اند .

توضیحات:

برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه می‌کنید:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfRpt.Core.Contracts
{
    public interface IPageHeader
    {
        PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData);

        PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData);
    }
}

RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمت‌های بعد به آن‌ها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی می‌توانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر می‌شود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده می‌شود.
در کل یا توسط کار با PdfPTable می‌توانید یک هدر غیرپیش فرض را طراحی کنید و یا می‌توانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آن‌را ملاحظه می‌کنید، بارگذاری و استفاده کنید. این قالب‌ها در مسیر Bin\Data سورس‌های پروژه قرار داده شده‌اند.

ساختار IPageFooter به صورت زیر است:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;

namespace PdfRpt.Core.Contracts
{
    public interface IPageFooter
    {
        void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData);

        void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);

        void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);
    }
}

برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونه‌ای از آن‌را در کدهای کلاس CustomFooter ملاحظه می‌نمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی می‌شود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی می‌گردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.

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

استفاده از این کلاس‌های سفارشی نیز همواره به شکل زیر خواهد بود:
readonly CustomHeader _customHeader = new CustomHeader();
//...
.PagesFooter(footer =>
{
   footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
})
.PagesHeader(header =>
{
  header.CustomHeader(_customHeader);
})
کلا در PdfReport هر جایی متدی به نام CustomXYZ را مشاهده کردید، این متد یک اینترفیس را دریافت می‌کند. به عبارتی این امکان را خواهید داشت تا از متدهای پیش فرض مهیا صرفنظر کرده و مطابق نیاز، نسبت به پیاده سازی و استفاده از وهله جدیدی از این اینترفیس تعریف شده، اقدام کنید.
مطالب
چه زمانی بهتر است از Silverlight استفاده شود؟

1- نیاز به توانایی‌های موجود در برنامه‌های Desktop را دارید اما همچنین نیاز است تا آن‌ها را تحت وب نیز ارائه دهید.
یکی از دلایل اقبال به برنامه‌های تحت وب در سازمان‌ها عدم نیاز به نصب آن‌ها و توزیع هر چه ساده‌تر اینگونه برنامه‌ها در شبکه است. تنها کافی است چند فایل را بر روی سرور به روز رسانی کنید و پس از آن تمام کلاینت‌ها از آخرین نگارش برنامه شما بهره‌مند خواهند شد (+). توزیع برنامه‌های سیلورلایت نیز به همین منوال است. علاوه بر آن استفاده از فناورهایی مانند MEF امکان ماژولار ساختن برنامه و دریافت آخرین ماژول‌های تهیه شده (فایل‌های XAP مجزای از برنامه به صورت افزونه) را بر اساس انتخاب و سطح دسترسی کاربر نیز میسر می‌سازد.

2- نیاز است تا یک برنامه‌ی گرافیکی تمام عیار را تحت وب ارائه دهید.
توانایی‌های XAML به همراه یکی از زبان‌های دات نت جهت خلق جلوه‌های بصری، پویانمایی و گرافیکی بسیار بسیار فراتر از کتابخانه‌های جاوا اسکریپتی موجود هستند و نکته‌ی مهم آن‌ها هم این است که لازم نیست حتما یک متخصص مثلا جاوا اسکریپت باشید تا بتوانید برای مثال پویانمایی را ارائه دهید. امکان استفاده از انواع و اقسام قلم‌ها و قرار دادن آن‌ها در برنامه، امکان استفاده از گرافیک برداری و غیره را نیز لحاظ کنید.

3- برنامه‌ی شما نیاز است تا از طریق وب توزیع شود اما نیاز به سطح دسترسی بیشتری نسبت به یک برنامه‌ی وب معمولی دارد.
تمام برنامه‌های توزیع شده از طریق مرورگرها محدود به سطوح دسترسی آن‌ها نیز هستند. اما امکان نصب خارج از مرورگر برنامه‌های سیلورلایت نیز وجود دارد. در این حالت می‌توان در صورت نیاز و همچنین تائید صریح کاربر، به سطوح دسترسی بیشتری دست یافت. برای مثال دسترسی به اسکنر در یک برنامه‌ی وب متداول بی‌معنا است. اما سیلورلایت 4 در حالت اجرای در خارج از مرورگر امکان تعامل با اشیاء COM را نیز دارد.

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

5- از مشکلات مدیریت حالت در برنامه‌های متداول وب به تنگ آمده‌اید.
اگر برای مثال برنامه نویس ASP.NET باشید حتما با مباحث State management آشنایی دارید (از سشن و کوکی گرفته تا ViewState (ایی که همه به نحوی قصد کوچک کردن آن‌را دارند!) و غیره). تمام این‌ها هم برای این است که بتوان تجربه‌ی کاری برنامه‌های دسکتاپ را در محیط مرورگرها شبیه سازی کرد. این مشکلات در سیلورلایت حل شده است. یک برنامه‌ی سیلورلایت State full است نه Stateless . همچنین اگر از حافظه‌ای هم استفاده می‌کند این مورد در سمت کاربر است و نه سمت سرور و نه منقضی شدن زود هنگام سشن‌ها و صدها ترفند برای مقیاس پذیری همین مساله‌ی بسیار کوچک با تعداد کاربران بالا در برنامه‌های متداول وب.
به عبارتی تصور کنید که برنامه‌ی دسکتاپ سال‌های قبل شما هم اکنون داخل مرورگر دارد اجرا می‌شود و چیزی به نام وب سرور وجود ندارد که پس از نمایش صفحه‌ی وب شما، کلیه‌ی اشیاء مرتبط با آن‌را در سمت سرور تخریب کند چون باید پاسخگوی کاربران همزمان بی‌شماری باشد و منابع سرور هم محدود است. (سیلورلایت یک فناوری سمت کاربر است. بنابراین وب سرور صرفا نقش توزیع آن‌را به عهده دارد یا حداکثر ارائه‌ی یک وب سرویس جهت تعاملات بعدی مانند کار با بانک اطلاعاتی)

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

7- نیاز است برنامه‌ی وب شما در حالت آفلاین هم کار کند.
برنامه‌های سیلورلایت تنها زمانیکه نیاز به دریافت یا ثبت اطلاعاتی از سرور داشته باشند، باید آنلاین باشند. همچنین این برنامه‌ها دسترسی به مفهوم جدیدی به نام Isolated Storage دارند که در آن می‌توان اطلاعات را به ازای هر کاربر آن هم با ضریب امنیتی بالا بر روی هارد شخص ذخیره کرد و زمان آنلاین شدن برنامه آن‌ها را به سرور انتقال داد.

8- برنامه وب شما نیاز است تا با فایل‌های مالتی مدیا تعامل داشته و آن‌ها را پخش کند.
حتی تگ Video در HTML5 نیز به پای توانایی‌های مالتی مدیا در Silverlight مانند smooth streaming, multicasting, editing, video brushes نمی‌رسد. برای مثال با استفاده از video brushes می‌توان یک فایل ویدیویی در حال پخش را بر روی یک وجه یک شیء در حال پویانمایی نقاشی و نمایش داد.

9- نیاز به پشتیبانی از multi-touch در برنامه‌ی وب شما وجود دارد.
برخلاف HTML ، تعاملات multi-touch در Silverlight میسر است.

10- نیاز به ایجاد برنامه‌های بازی تحت وب دارید.
به طور قطع می‌توان بازیی‌هایی در حد Pong را با جاوا اسکریپت هم ایجاد کرد، اما اگر نیاز به تولید بازی‌هایی جدی‌تر وجود داشت برای مثال انتقال بازی Quake به محیط وب، Silverlight در این زمینه هم حرف‌های زیادی برای گفتن دارد (+).

11- نیاز به تولید برنامه‌ی دسکتاپ چند سکویی دارید.
سیلورلایت هم اکنون تحت ویندوز، MAC OS-X ، لینوکس و ... پشتیبانی می‌شود (+). همچنین برنامه‌های سیلورلایت قابلیت اجرای در خارج از مرورگر را هم دارند.
با سیلورلایت دیگر نیازی نخواهد بود تا کاربران لینوکسی ابتدا Wine را نصب کنند تا بتوانند از یک برنامه‌ی ویندوزی که انتقال پذیر نیست در لینوکس هم بتوانند استفاده کنند؛ چون پروژه‌ی مون لایت لینوکسی برای این منظور مهیا است.

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

13- از پیچیدگی‌های پیاده سازی برنامه‌های متداول وب خسته شده‌اید.
هنوز هم با تمام پیشرفت‌های حاصل، تولید برنامه‌های وب پیشرفته مشکل است. از یک طرف ناسازگاری یک سری از مرورگرها با یک سری از قابلیت‌ها را باید در نظر داشت، تا فراگیری فریم ورک‌های Ajax و غیره تا مشکل بودن طراحی کنترل‌های جدید فراتر از آن چیزی که HTML استاندارد ارائه می‌دهد. بله، به طور قطع دانش فنی بالایی در این زمینه در طی سالیان تولید شده است، اما باز هم فراگیری و تسلط به آن‌ها زمان قابل توجهی را طلب می‌کند.
در سیلورلایت کلیه تعاملات با شبکه به صورت پیش فرض غیرهمزمان است (همان ایده‌ی اصلی Ajax) همچنین با توجه به state full بودن اینگونه برنامه‌ها، عملا برنامه نویس‌ها بدون درگیر شدن با مفاهیم اجکسی و مدیریت حالت، برنامه‌ی پیشرفته‌ی وبی را در مدت زمان کوتاهی تولید کرده‌اند و این برنامه در تمام مرورگرهایی که قابلیت بارگذاری افزونه‌ی سیلورلایت را دارند به یک شکل و کیفیت اجرا می‌شود.

14- در زمینه میزان مصرف پهنای باند ملاحظاتی ویژه‌ای وجود دارد.
یک برنامه‌ی سیلورلایت تنها یکبار باید دریافت شود. پس از آن در سمت کاربر کش خواهد شد (تا زمان به روز رسانی بعدی برنامه در سرور). همین مساله در دفعات بعدی مراجعه کاربر به سایت نقش قابل توجهی را در کاهش میزان مصرف پهنای باند (یا به قولی میزان کمتر data transfer) کلی دارد.

15- فرصت کافی برای فراگیری انبوهی از فناوری‌های مختلف را ندارید!
بله! برای ایجاد یک برنامه‌ی تحت وب که کاربر آن پس از مشاهده بگوید WOW نیاز است به HTML ، JS ، CSS ، AJAX ، یکی از فناوری‌های سمت سرور و ... مسلط بود (علاوه بر اینکه باید بدانید فلان کد JS در IE کار می‌کند اما در فایرفاکس خیر. فایرفاکس فلان قسمت CSS را پشتیبانی می‌کند اما IE خیر! و ...).
اما برای استفاده از سیلورلایت فقط کافی است به XAML و یکی از زبان‌های دات نت مانند سی شارپ یا VB.NET مسلط باشید (البته هیچ وقت از دست ASP.NET خلاص نخواهید شد! حداقل در حد راه اندازی یک وب سرویس یا مفاهیم امنیتی آن).
این مورد خصوصا برای افرادی که برنامه نویس دسکتاپ هستند اما علاقمندند تا برنامه‌ی وب نیز تولید کنند بسیار مهم است. با حداقل آموزش می‌توانند توانایی‌های خود را به وب نیز گسترش دهند. علاوه بر آن عمده‌ی دانش Silverlight شما جهت تولید برنامه‌های WPF (با توجه به اینکه Silverlight فرزند WPF محسوب می‌شود) یا Windows phone 7‌ و غیره نیز می‌تواند بکار گرفته شود.

16- نیاز به اجرای کدهای چند ریسمانی در سمت کاربر دارید.
تا این لحظه پشتیبانی رسمی از مباحث چند ریسمانی در JavaScript و استانداردهای مرتبط با آن وجود ندارد. Silverlight به اکثر امکانات Threading موجود در دات نت فریم ورک دسترسی داشته و دانش فعلی شما قابل انتقال است.


و دست آخر باید به نکته اشاره کرد که هدف از Silverlight ساخت وب سایت معمولی نیست. این نوع کارها را با همان ابزارهای متداول انجام دهید. هدف اصلی آن ساخت برنامه است (Application در مقابل Web site). مشتری‌های اصلی این نوع برنامه‌ها هم بیشتر سازمان‌ها و اینترانت‌های پر سرعت و بسته‌ی آن‌ها هستند که نه نگران حجم افزونه‌ی سیلورلایت هستند و نه مشکلی با حجم برنامه‌ی سیلورلایت شما در یک شبکه‌ی داخلی پر سرعت دارند.

مطالب
نکته‌ای مهم در طراحی قالب‌ برنامه‌های Silverlight

قالب سیلورلایتی را ایجاد کرده بودم و IE در حالت نمایش عادی این قالب 30 درصد CPU Usage ثابت داشت. علت را هم متوجه نمی‌شدم؛ چون در این حالت اصلا کدی وجود نداشت که بخواهد CPU Usage ایی را ایجاد کند. یک سری کد XAML جهت نمایش قالب در کنار هم قرار گرفته بودند و همین.
تا اینکه دیروز در وبلاگ رسمی مرتبط با کارآیی برنامه‌های Silverlight مطلبی منتشر شد که دقیقا مشکل طراحی قالب من هم همان بود:

خلاصه آن:
اگر در حالت نمایش برنامه Silverlight شما (بدون اینکه کدی در حال اجرا باشد) به صورت ثابت CPU Usage بالایی را مشاهده می‌کنید، پارامتر enableRedrawRegions تگ بارگذاری افزونه‌ی Silverlight را به true مقدار دهی کنید.
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2">
...
<param name="enableRedrawRegions" value="true"/>
...
</object>

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

چه زمانی ممکن است این حالت (ترسیم‌های مجدد بدون پایان) رخ دهد؟
عموما این مشکل در حین استفاده ناصحیح از افکت‌های پیش فرض Silverlight مانند DropShadowEffect رخ می‌دهد. برای مثال می‌خواهید قسمتی از قالب شما سایه دار باشد اما نحوه‌ی اعمال این سایه بسیار مهم است.
        <Border CornerRadius="3">
<!--High cpu usage-->
<Border.Effect>
<DropShadowEffect BlurRadius="7" Color="#FF1E2224" Opacity="3" ShadowDepth="6" Direction="200" />
</Border.Effect>
<StackPanel>
...
...

در این مثال ابتدا یک border تعریف شده و سپس سایه‌ای به آن اعمال گردیده است. سپس داخل این Border یک StackPanel قرار گرفته است به همراه یک سری از اشیاء زیر مجموعه آن. این کار غلط است! همین مورد به ظاهر ساده 30 درصد CPU Usage ثابت را در برنامه ایجاد کرده بود.

علت چیست؟
با این نحوه‌‌ی تعریف اشتباه DropShadowEffect ، هر نوع تغییر بصری در مجموعه‌ی Border و StackPanel داخل آن، سبب ترسیم مجدد کل ناحیه می‌گردد. این تغییر بصری حتی می‌تواند شامل چشمک زدن یک cursor درون یک TextBox در قسمتی از این ناحیه نیز باشد که با استفاده از ویژگی enableRedrawRegions ، این مورد را به خوبی می‌توان مشاهده نمود.

راه حل این مساله کدام است؟
از دو Border استفاده کنید. یک Border با ضخامت کم تنها برای نمایش سایه (که دارای هیچ نوع شیء فرزندی نیست) و Border و StackPanel قبلی هم به همان صورت ابتدایی (البته با حذف DropShadowEffect از آن) باقی بماند.

نظرات مطالب
متدهای کمکی مفید در پروژه های asp.net mvc
با تشکر از مبحث مهمی که ارائه کردید.
در صورتی که جهت فراخوانی اطلاعات از JQuery Ajax استفاده شود، چگونه میتوان از Helper تاریخ شمسی استفاده کرد؟
به بیان دیگر وقتی توسط Ajax اطلاعات از سمت سرور دریافت میشود، اطلاعات بازگشتی توسط متغییری مثل Result که از نوع Json است برگشت داده میشود. که در سمت کلاینت نمیتوان از (Html.FarsiDate(news.DateTimeCreated@ استفاده کرد.
در سمت سرور هم فرض کنید از کد زیر اتفاده شود، 
var data = context.Tabel1.Select(
                             p =>
                             new
                             {
                                 p.Date1,
                                 p.Comment,
                                 p.Cost,
                                 p.UserId,
                             }).FirstOrDefault(p.UserId == UserId);
data.Date1 در سمت سرور قابل تغییر نیست و با خطای Readonly متوقف میشود.
ممنون میشوم راهنمایی بفرمائید. 
نظرات مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid
- قصد پشتیبانی از wrapperهای آن‌را ندارم. لطفا خارج از موضوع سؤال نپرسید. اگر کسی دوست داشت در این زمینه مطلب منتشر کند، خوب. ولی من چنین قصدی ندارم.
- عرض کردم اگر از wrapperها استفاده کنید، به علت عدم درک زیر ساخت اصلی Kendo UI، قادر به دیباگ کار نخواهید بود.
- اگر متن را مطالعه کنید در قسمت «پیشنیاز تامین داده مخصوص Kendo UI Grid » دقیقا شکل نهایی خروجی JSON مورد نیاز ارائه شده‌است. این خروجی در سه فیلد data، total و aggregate قرار می‌گیرد. شما الان فقط قسمت data آن‌را بازگشت داده‌اید؛ بجای اصل و کل آن. نام این سه فیلد هم مهم نیست؛ اما هر چیزی که تعیین می‌شوند، باید در قسمت data source در خاصیت schema آن مانند مثالی که در مطلب جاری آمده (در قسمت «تامین داده و نمایش گرید »)، دقیقا مشخص شوند، تا Kendo UI بداند که اطلاعات مختلف را باید از چه فیلدهایی از JSON خروجی دریافت کند.
مطالب
آشنایی با آزمایش واحد (unit testing) در دات نت، قسمت 6

ادامه آشنایی با NUnit

فرض کنید یک RSS reader نوشته‌اید که فیدهای فارسی و انگلیسی را دریافت می‌کند. به صورت پیش فرض هم مشخص نیست که کدام فید اطلاعات فارسی را ارائه خواهد داد و کدامیک انگلیسی. تشخیص محتوای فارسی و از راست به چپ نشان دادن خودکار مطالب ‌آن‌ها به عهده‌ی برنامه نویس است. بهترین روش برای تشخیص این نوع الگوها، استفاده از regular expressions است.
برای مثال الگوی تشخیص اینکه آیا متن ما حاوی حروف انگلیسی است یا خیر به صورت زیر است:

[a-zA-Z]

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

[\u0600-\u06FF]
[ا-یءئ]

در مورد اینکه بازه یونیکد فارسی استاندارد از کجا شروع می‌شود می‌توان به مقاله‌ی آقای حاج‌لو مراجعه نمود (به صورت خلاصه، بازه مصوب عربی یونیکد، همان بازه یونیکد فارسی نیز می‌باشد. یا به بیان بهتر، بازه‌ی فارسی، جزئی از بازه‌ای است که عربی نام گرفته است). البته بازه‌ی مصوب دیگری هم در مورد ایران باستان وجود دارد به نام old Persian که مورد استفاده‌ی روزمره‌ای ندارد!

کلاس زیر را در مورد استفاده از این الگوها تهیه کرده‌ایم:

using System.Text.RegularExpressions;

namespace sample
{
public static class CDetectFarsi
{
public static bool ContainsFarsiData(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, "[ا-یءئ]");
}

public static bool ContainsFarsi(this string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, @"[\u0600-\u06FF]");
}
}
}

همانطور که ملاحظه می‌کنید در اینجا از extension methods سی شارپ 3 جهت توسعه کلاس پایه string استفاده شد.
اکنون می‌خواهیم بررسی کنیم آیا این الگوها مقصود ما را برآورده می‌سازند یا خیر.

using NUnit.Framework;
using sample;

namespace TestLibrary
{
[TestFixture]
public class TestFarsiClass
{
/*******************************************************************************/
[Test]
public void TestContainsFarsi1()
{
Assert.IsTrue("وحید".ContainsFarsi());
}

[Test]
public void TestContainsFarsi2()
{
Assert.IsTrue("گردان".ContainsFarsi());
}

[Test]
public void TestContainsFarsi3()
{
Assert.IsTrue("سپیدTest".ContainsFarsi());
}

[Test]
public void TestContainsFarsi4()
{
Assert.IsTrue("123بررسی456".ContainsFarsi());
}

[Test]
public void TestContainsFarsi5()
{
Assert.IsFalse("Book".ContainsFarsi());
}

[Test]
public void TestContainsFarsi6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsi());
}

[Test]
public void TestContainsFarsi7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsi()); //Russian hello!
}


/*******************************************************************************/
[Test]
public void TestContainsFarsiData1()
{
Assert.IsTrue("وحید".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData2()
{
Assert.IsTrue("گردان".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData3()
{
Assert.IsTrue("سپیدTest".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData4()
{
Assert.IsTrue("123بررسی456".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData5()
{
Assert.IsFalse("Book".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData6()
{
Assert.IsTrue("۱۳۸۷".ContainsFarsiData());
}

[Test]
public void TestContainsFarsiData7()
{
Assert.IsFalse("Здравствуйте!".ContainsFarsiData()); //Russian hello!
}
}
}

در کلاس فوق هر دو متد را با آزمایش‌های واحد مختلفی بررسی کرده‌ایم، انواع و اقسام حروف فارسی، ترکیبی از فارسی و انگلیسی، ترکیبی از فارسی و اعداد انگلیسی، عبارت کاملا انگلیسی، عدد کاملا فارسی و یک عبارت روسی! (در یک کلاس عمومی با متدهای عمومی بدون آرگومان از نوع void)
کلاس CDetectFarsi در برنامه اصلی قرار دارد و کلاس TestFarsiClass در یک پروژه class library دیگر قرار گرفته است (در این مورد و جدا سازی آزمایش‌ها از پروژه اصلی در قسمت‌های قبل بحث شد)
همچنین به ازای هر عبارت Assert یک متد ایجاد گردید تا شکست یکی، سبب اختلال در بررسی سایر موارد نشود.
نتیجه اجرای این آزمایش واحد با استفاده از امکانات مجتمع افزونه ReSharper به صورت زیر است:



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

[Test,Ignore]

نکته: مرسوم شده است که نام متدهای آزمایش واحد به صورت زیر تعریف شوند (با Test شروع شوند، در ادامه نام متدی که بررسی می‌شود ذکر گردد و در آخر ویژگی مورد بررسی عنوان شود):

Test[MethodToBeTested][SomeAttribute]

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

مطالب
آموزش فایرباگ - #6 - HTML Panel - Side Panels
در قسمت قبل توضیحاتی در مورد تب HTML ارائه کردیم.
در این قسمت توضیحات کاملی در مورد پنل‌های جانبیِ داخل پنل HTML می‌دهیم.



Side Panels

در پنل HTML درکنار ارائه امکاناتی برای مشاهده و کار با تگ‌های صفحه ، اطلاعات و امکانات دیگری هم برای تگ انتخاب شده در قسمت NodeView وجود دارد.
این امکانات در پنل هایی که سمت راست پنل اصلی قرار دارند گنجانده شده است که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView هستند.




1 - Style
در این تب استایل هایی که در حال حاظر بروی تگ انتخاب شده اعمال شده اند ، نمایش داده می‌شود.
در صورتی که موس را بروی مقادیر استایل هایی که جلوه‌ی بصری دارند بگیرید ، یک پاپ‌آپ کوچک نمایان می‌شود که مقدار را نمایش می‌دهد.


 Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب Style هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Only Show Applied Styles
    در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده می‌شوند. (استایل‌های Overwrite شده نمایش داده نمی‌شوند.)
    (این گزینه قابلیت خوبی است ، اما چندبار برای بنده پیش آمده که این مورد به اشتباه استایلی که اعمال شده بود را هم Overwrite شده در نظر گرفته بود. پس در هین طراحی استایل و کار با CSS اگر احیانا یکی از استایل هایتان وجود نداشت و از وجود آن اطمینان داشتید ، غیرفعال کردن این گزینه را امتحان کنید.)

  • Show User Agent CSS
    با فعال کردن این گزینه ، استایل هایی که توسط مرورگر اعمال شده اند هم نمایش داده می‌شوند.

  • Expand Shorthand Properties
    با فعال کردن این گزینه ، استایل هایی که بصورت کوتاه شده تعریف شده اند را بصورت گسترده و باز شده نمایش می‌دهد.
    برای مثال ، دستور margin را بصورت margin-top , margin-right , margin-bottom , margin-left نمایش می‌دهد.

  • سه گزینه ی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کننده‌ی فرمت نمایش رنگ‌ها هستند.

  • سه گزینه ی :active ، :hover و :focus هم برای تعیین کلاس کاذب برای تگ جاری کاربرد دارند.
    برای مثال شما می‌خواهید استایلی که یک لینک زمان موس برویش قرار دارد را بررسی کنید ، لینک را در NodeView انتخاب می‌کنید و سپس از گزینه‌ی :hover را فعال می‌کنید.


Panel

  • Element styles
    استایل هایی که بصورت inline (در خود تگ) تعریف شده اند هم در این قسمت نمایش داده می‌شود و نام rule آن element.style است.



  • Source Links
    در بالا-راست هر بخش ، یک لینک قرار دارد که لینک فایل استایلی است که در همان قسمت وجود دارد و عددی که در پرانتز قرار دارد ، شماره خط استایل در همان فایل است.
    اگر نام فایل با نام صفحه‌ی جاری برابر باشد ، به معنی وجود استایل در تگ <style> در صفحه‌ی جاری است و شماره‌ی بعد از # هم ایندکس تگ <style> است.
    (با کلیک بروی لینک فایل ، فایل در خط مورد نظر در پنل CSS نمایش داده می‌شود.)



  • Inherited rules
    rule‌های به ارث رسیده هم در قسمت‌های جداگانه به همراه استایل‌های به ارث رسیده نمایش داده می‌شود و تگی والد که استایل‌ها از آن به ارث رسیده اند هم در قسمت عنوان همان استایل‌ها نمایش قرار داده شده است. (با کلیک بروی آن ، در قسمت Nodeview انتخاب می‌شود.)



  • User agent rules
    استایل هایی که توسط مرورگر اعمال شده اند (User agent rules) ، با عبارت <System> در زیر لینک منبع استایل ، مشخص شده اند.



  • Overwritten styles
    استایل‌های overwrite شده ، با یک خط برویشان مشخص شده اند.



  • Inline editing
    استایل‌های نمایش داده شده در این پنل را براحتی و با کلیک کردن بروی نام یا مقدار هر یک از دستورات می‌توانید تغییر دهید.
    برای نوشتن دستورات و مقادیر آن‌ها می‌توانید از پیشنهاد‌های فایرباگ هم کمک بگیرید و با دکمه‌های Arrow Up و Arrow Down هم بین مقادیر مجاز حرکت کنید.
    دستورات یا مقادیر نا صحیح در هین تایپ ، با رنگ قرمز و مقادیر صحیح با رنگ سبز مشخص می‌شوند.
    (این امکان خیلی مفید است ، برای مثال می‌خواهید فونت‌های مختلف را برای یک استایل امتحان کنید ، دستور font-family را می‌نویسید و بعد از زدن Enter ، با دکمه های Arrow Up و Arrow Down در لحظه نتیجه‌ی اعمال فونت‌های مختلف و دردسترس را مشاهده می‌کنید و بهترین را بر می‌گزینید.
    یا برای یافتن بهترین مقدار margin ، بعد از دستور margin ، زدن کلید Enter ، وارد کردن یک عدد برای شروع ، می‌توان باز هم با دکمه های Arrow Up و Arrow Down به سرعت تغییر را در صفحه مشاهده کرد.)

  • Rendered font highlighted
    برای دستور font ، فایرباگ هوشمندانه عمل کرده و فونتی که در حال استفاده است را پررنگ می‌کند.
    این امکان برای یافتن خطاهای متداول هنگام تعریف فونت‌های غیر سیستمی ، بسیار مفید است.




 Context Menu

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


 گزینه Context توضیحات 
Copy Rule Declaration CSS selector  CSS Rule فعلی را به همراه استایل هایش در clipboard کپی می‌کند
Copy Style Declaration CSS selector  استایل‌های CSS Rule فعلی را در clipboard کپی می‌کند 
Copy Location source link  آدرس فایل تعریف CSS Rule را در clipboard کپی می‌کند
Open in New Tab source link   آدرس فایل تعریف CSS Rule را در تب جدید باز می‌کند
Edit Element Style... everywhere  امکان تعریف استایل‌های درون تگ (inline) را محیا می‌کند
Add Rule... everywhere  یک Rule جدید ایجاد می‌کند
CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد می‌دهد
Delete "<rule name>" CSS selector  CSS Rule فعلی را حذف می‌کند
New Property... CSS rule  یک استایل جدید به CSS Rule فعلی اضافه می‌کند
Edit "<property name>"... CSS property  Property فعلی را به حالت ویرایش می‌برد
راه دیگر ویرایش Property ، کلیک بروی آن است
Delete "<property name>" CSS property  Property فعلی را حذف می‌کند
Disable "<property name>" CSS property  Property فعلی را غیر فعال می‌کند
را سریع‌تر ، کلیک کردن در ناحیه‌ی پشت Property ، بروی علامت قرمز رنگ است
Refresh everywhere  محتویات پنل را بروز می‌کند
Inspect in DOM Panel
CSS rule  CSS Rule فعلی را در پنل DOM برای بررسی باز می‌کند
Inspect in CSS Panel CSS rule   CSS Rule فعلی را در پنل CSS برای بررسی باز می‌کند
<Default Editor Name> CSS rule  فایل تعریف استایل‌ها را در ادیتور تعریف شده باز می‌کند
(این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد)


2 - Computed

دراین تب نتیجه‌ی پردازش استایل‌های ارائه شده توسط کاربر ، برای تگ مشخص شده در قسمت NodeView نمایش داده می‌شود. (مقادیر استایل هایی که در نهایت بروی تگ اعمال شده اند.)


Style Tracing
برای ردیابی استایل‌ها ، استایل‌ها به ترتیب اعمال شدنشان مرتب شده اند و اولین مقدار ، مقداری است که اعمال شده است.
مقادیر Overwrite بصورت خط کشیده شده و استایل‌های Overwrite شده بصورت خاکستری-کمرنگ نمایش داده می‌شوند.
هر استایل هم مانند تب Style ، یک لینک به منبع خود دارد.


Options Menu

  • Show User Agent CSS
    در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده می‌شوند.

  • Sort alphabetically
    در صورت انتخاب ، استایل‌ها به ترتیب الفبا ، و درصورت عدم انتخاب بصورت گروه بندی نمایش داده می‌شوند.

  • Show Mozilla Specific Styles
    در صورت انتخاب ، استایل‌های مخصوص Mozilla را نمایش می‌دهد. (استایل هایی با پیشوند -moz-)

  • سه گزینه‌ی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کننده‌ی فرمت نمایش رنگ‌ها هستند.


Context Menu

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

 گزینه Context توضیحات 
Expand All Styles everywhere  CSS Rule فعلی را به همراه استایل هایش در clipboard کپی می‌کند
Collapse All Styles everywhere  استایل‌های CSS Rule فعلی را در clipboard کپی می‌کند 
Inspect in DOM panel styles   آدرس فایل تعریف CSS Rule را در تب جدید باز می‌کند
Copy Location
style source link  امکان تعریف استایل‌های درون تگ (inline) را محیا می‌کند
Open in New Tab style source link  یک Rule جدید ایجاد می‌کند
CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد می‌دهد
Inspect in CSS panel style source link  CSS Rule فعلی را حذف می‌کند
<Default Editor Name> style source link  فایل تعریف استایل‌ها را در ادیتور تعریف شده باز می‌کند
(این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد)


3 - Layout

در این تب ، مقادیر Box Model بصورت بصری نمایش می‌دهد. می‌توان با کلیک کردن بروی هریک از مقادیر ، آن را ویرایش کرد. (این تغییر بصورت inline در تگ اعمال می‌شود.)
با حرکت موس بروی قسمت‌های مختلف ، می‌توان همان قسمت‌ها را در صفحه بصورت خط کشی شده مشاهده کرد.
(البته ظاهرا در ورژن 1.10.4 که بنده استفاده می‌کنم ، عملیات ویرایش مقادیر به درستی انجام نمی‌شود.)



 Options Menu

  • Show Rulers and Guides
    در صورت انتخاب ، خط‌های راهنما را هنگام حرکت موس بروی اجزای Box Model در صفحه نمایش می‌دهد.




4 - DOM

این پنل اطلاعات DOM تگ جاری را نمایش می‌دهد.
این پنل تمام قابلیت‌های پنل DOM اصلی را دارا می‌باشد.
(در مقالات آینده با تب DOM آشنا خواهیم شد.)



مطالب
ایجاد گزارشات Crosstab در PdfReport
پیشنیازها
تهیه گزارشات Crosstab به کمک LINQ 
تهیه گزارشات Crosstab به کمک LINQ - قسمت دوم 

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

روش حل این نوع مسایل را در PdfReport در مثال‌های جدید ذیل می‌توانید مشاهده کنید:
1) DynamicCrosstab (یک گزارش Crosstab با تعداد ستون متغیر)



2) WorkedHours (یک گزارش Crosstab با تعداد ستون متغیر به علاوه تابع تجمعی سفارشی محاسبه جمع ساعات اشخاص)



3) ExtraHeadingCells (یک گزارش Crosstab به همراه ردیف‌های header اضافی و نحوه تعریف آن)



4) ExpensesCrosstab (یک گزارش Crosstab کلاسیک)



5) PdfA (یک گزارش Crosstab که به صورت استاندارد PdfA تهیه شده است. PdfA حالت خاصی از استاندارد PDF است که برای مستند سازی عموما مورد استفاده قرار می‌گیرد. رمزنگاری اطلاعات در آن ممنوع است. تصاویر بکارگرفته شده نباید شفاف باشند. قلم‌های مورد استفاده حتما باید در فایل مدفون شوند و مواردی از این دست)