پروژه‌ها
PdfReport
کتابخانه PdfReport جهت ایجاد گزارشات متنوعی با خروجی PDF کاملا سازگار با زبان فارسی تهیه شده است. استفاده از آن صرفا با کدنویسی (Code first) میسر بوده و بازه وسیعی از فناوری‌های مختلف مبتنی بر دات نت را پوشش می‌دهد؛ مانند WinForms، WPF، برنامه‌های وب و غیره و کلا هرجایی که دات نت فریم ورک 3.5 به بعد به صورت کامل در دسترس باشد.
به کمک کتابخانه PdfReport دسترسی گسترده‌ای به منابع داده‌ای مختلف خواهید یافت. منابعی که لزوما بانک اطلاعاتی نیستند؛ مانند یک لیست جنریک و یا حتی یک anonymously typed list حاصل از یک کوئری LINQ.
این کتابخانه علاوه بر تبدیل اطلاعات شما به گزارشات مبتنی بر PDF، امکان تهیه خروجی خودکار اکسل (2007 به بعد) را نیز دارد. فایل خروجی آن، به صورت پیوست درون فایل PDF تهیه شده قرار می‌گیرد و جزئی از آن می‌شود.
مسایل امنیتی مانند رمزنگاری فایل PDF حاصل و یا حتی افزودن امضای دیجیتال به فایل نهایی تولیدی نیز در آن لحاظ شده است.
کتابخانه PdfReport بر پایه کتابخانه‌های معروف سورس باز iTextSharp و EPPlus تهیه شده است. حداقل مزیت استفاده از آن، صرفه جویی در وقت شما جهت آموختن ریزه کاری‌های مرتبط با هر کدام از کتابخانه‌های یاده شده است. برای نمونه جهت فراگیری کار با iTextSharp نیاز است یک کتاب 600 صفحه‌ای به نام iText in action را مطالعه و تمرین کنید. این مورد منهای مسایل و نکات متعدد مرتبط با زبان فارسی است که در این کتاب به آن‌ها اشاره‌ای نشده است.

مطالب
Static Reflection

قابلیت Dynamic reflection یا به اختصار همان reflection متداول، از اولین نگارش‌های دات نت فریم در دسترس است و امکان دسترسی به اطلاعات مرتبط با کلاس‌ها، متدها، خواص و غیره را در زمان اجرا مهیا می‌سازد. تابحال به کمک این قابلیت، امکان تهیه‌ی ابزارهای پیشرفته‌ی زیر مهیا شده است:
انواع و اقسام
- فریم ورک‌های آزمون واحد
- code generators
- ORMs
- ابزارهای آنالیز کد
و ...


برای مثال فرض کنید که می‌خواهید برای یک کلاس به صورت خودکار، متدهای آزمون واحد تهیه کنید (تهیه یک code generator ساده). اولین نیاز این برنامه، دسترسی به امضای متدها به همراه نام آرگومان‌ها و نوع آن‌ها است. برای حل این مساله باید برای مثال یک parser زبان سی شارپ یا اگر بخواهید کامل‌تر کار کنید، به ازای تمام زبان‌های قابل استفاده در دات نت فریم ورک باید parser تهیه کنید که ... کار ساده‌ای نیست. اما با وجود reflection به سادگی می‌توان به این نوع اطلاعات دسترسی پیدا کرد و نکته‌ی مهم آن هم این است که مستقل است از نوع زبان مورد استفاده. به همین جهت است که این نوع ابزارها را در فریم ورک‌هایی که فاقد امکانات reflection هستند، کمتر می‌توان یافت. برای مثال کیفیت کتابخانه‌های آزمون واحد CPP در مقایسه با آنچه که در دات نت مهیا هستند، اصلا قابل مقایسه نیستند. برای نمونه به یکی از معظم‌ترین فریم ورک‌های آزمون واحد CPP که توسط گوگل تهیه شده مراجعه کنید : (+)
قابلیت Reflection ، مطلب جدیدی نیست و برای مثال زبان جاوا هم سال‌ها است که از آن‌ پشتیبانی می‌کند. اما نگارش سوم دات نت فریم ورک با معرفی lambda expressions ، LINQ و Expressions در یک سطح بالاتر از این Dynamic reflection متداول قرار گرفت.

تعریف Static Reflection :
استفاده از امکانات Reflection API بدون بکارگیری رشته‌ها، به کمک قابلیت اجرای به تعویق افتاده‌ی LINQ، جهت دسترسی به متادیتای المان‌های کد، مانند خواص، متدها و غیره.
برای مثال کد زیر را در نظر بگیرید:
//dynamic reflection
PropertyInfo property = typeof (MyClass).GetProperty("Name");
MethodInfo method = typeof (MyClass).GetMethod("SomeMethod");
این کد، یک نمونه از دسترسی به متادیتای خواص یا متدها را به کمک Reflection متداول نمایش می‌دهد. مهم‌ترین ایراد آن استفاده از رشته‌ها است که تحت نظر کامپایلر نیستند و تنها زمان اجرا است که مشخص می‌شود آیا MyClass واقعا خاصیتی به نام Name داشته است یا خیر.
چقدر خوب می‌شد اگر این قابلیت بجای dynamic بودن (مشخص شدن در زمان اجرا)، استاتیک می‌بود و در زمان کامپایل قابل بررسی می‌شد. این امکان به کمک lambda expressions و expression trees دات نت سه بعد، میسر شده است. کلیدهای اصلی Static Reflection کلاس‌های Func و Expression هستند. با استفاده از کلاس Func می‌توان lambda expression ایی را تعریف کرد که مقداری را بر می‌گرداند و توسط کلاس Expression می‌توان به محتوای یک delegate دسترسی یافت. ترکیب این دو، قدرت دستیابی به اطلاعاتی مانند PropertyInfo را در زمان طراحی کلاس‌ها، می‌دهد؛ با توجه به اینکه:
- کاملا توسط intellisense موجود در VS.NET پشتیبانی می‌شود.
- با استفاده از ابزارهای refactoring قابل کنترل است.
- از همه مهم‌تر، دیگری خبری از رشته‌ها نبوده و همه چیز تحت کنترل کامپایلر قرار می‌گیرد.

و شاید هیچ قابلیتی به اندازه‌ی Static Reflection در این چندسال اخیر بر روی اکوسیستم دات نت فریم ورک تاثیرگذار نبوده باشد. این روزها کمتر کتابخانه یا فریم ورکی را می‌توانید پیدا کنید که از Static Reflection استفاده نکند. سرآغاز استفاده گسترده از آن به Fluent NHibernate بر می‌گردد؛ سپس در انواع و اقسام mocking frameworks‌ ، ORMs و غیره استفاده شد و مدتی است که در ASP.NET MVC نیز مورد استفاده قرار می‌گیرد (برای مثال TextBoxFor معروف آن):
public string TextBoxFor<T>(Expression<Func<T,object>> expression);
به این ترتیب حین استفاده از آن دیگری نیازی نخواهد بود تا نام خاصیت مدل مورد نظر را به صورت رشته وارد کرد:
<%= this.TextBoxFor(model => model.FirstName); %>

یک مثال ساده از تعریف و بکارگیری Static Reflection :
public PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;

if (memberExpression == null)
throw new InvalidOperationException("Not a member access.");

return memberExpression.Member as PropertyInfo;
}
همانطور که عنوان شد کلیدهای اصلی بهر‌ه‌گیری از امکانات Static reflection ، استفاده از کلاس‌های Expression و Func هستند که در آرگومان متد فوق بکارگرفته شده‌اند و در حقیقت یک expression of a delegate است که به آن Lambdas as Data نیز گفته می‌شود. این delegate پارامتری از نوع T را دریافت کرده و سپس مقداری از نوع object را بر می‌گرداند. اما زمانیکه از کلاس Expression در اینجا استفاده می‌شود، این Func دیگر اجرا نخواهد شد، بلکه از آن به عنوان قطعه‌ کدی که اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده می‌شود.
برای نمونه Fluent NHibernate‌ در پشت صحنه متد Map ، به کمک متدی شبیه به GetProperty فوق، a => a.Address1 را به رشته متناظر خاصیت Address1 تبدیل کرده و جهت تعریف نگاشت‌ها مورد استفاده قرار می‌دهد:
public class AddressMap : DomainMap<Address>
{
public AddressMap()
{
Map(a => a.Address1);
}
}

جهت اطلاع؛ قابلیت استفاده از «کد به عنوان اطلاعات» هم مفهوم جدیدی نیست و برای مثال زبان Lisp چند دهه است که آن‌را ارائه داده است!

برای مطالعه بیشتر:

مطالب
بررسی مفاهیم متغیرهای Value Type و Reference Type در سی شارپ
نوع داده(Data Type) ، متغیر‌ها(Variables) ، انواع مقداری(Value Type) ، انواع ارجاعی(Reference Type)

مقدمه :
نوع داده‌ها، اجزای اصلی سازنده‌ی یک زبان برنامه نویسی و شبیه قواعد هر زبانی هستند.
مفاهیمی که در این مطلب بررسی خواهد شد :
 • Data Type نوع داده
 • Variables  متغیرها
 • Naming Convention قرارداد‌های نامگذاری
 • Value Type/Reference Type انواع مقداری و ارجاعی
 • Stack/heap memory  حافظه پشته و هرم

نوع داده

در دنیای واقعی، برای نگهداری مواد مختلف، ظروف مختلفی با اندازه‌های مختلفی طراحی شده است. در دنیای برنامه نویسی، به تناسب اطلاعاتی که می‌خواهیم در حافظه ذخیره کنیم، باید نوع ظرف ذخیره سازی را انتخاب کنیم. نوع ظرف ذخیره سازی را در دنیای برنامه نویسی، نوع داده‌ها مشخص می‌کنند.
در دات نت، همه‌ی نوع داده‌ها (Data Type) بصورت مستقیم و یا غیر مستقیم، از کلاس System.Object مشتق شده‌اند.


متغیرها

متغیر‌ها برای ذخیره‌ی مقادیر (اطلاعات)، استفاده می‌شوند. به این مثال دقت کنید: ما یک کیف داریم که در آن یک کتاب قرار دارد. در اینجا کیف نقش متغیر و کتاب نقش مقدار (value) را ایفا می‌کند. اندازه‌ی کیف همان نوع داده (Data Type) در دنیای برنامه نویسی می‌باشد.


چک کردن سایز نوع داده (Data Type)

ما نیازی به حفظ کردن اندازه‌ی نوع داده‌ها نداریم. در سی شارپ متدی به نام () sizeof مهیا شده است که با چک کردن نوع داده، اندازه‌ی آن را بر حسب بایت نمایش می‌دهد.
به مثال زیر دقت کنید:
Console.WriteLine(sizeof(int));
Console.WriteLine(sizeof(char));
Console.WriteLine(sizeof(bool));
Console.WriteLine(sizeof(decimal));
Console.WriteLine(sizeof(float));
خروجی کد‌های بالا :
 4
2
1
16
4

نکته : متد sizeof فقط برای نمایش اندازه‌ی نوع داده‌های مقداری (value type) می‌تواند مورد استفاده قرار گیرد.


چک کردن نوع داده

ما می‌توانیم نوع داده‌ها را برای بدست آوردن کلاسی که به آن تعلق دارند، چک کنیم.
مثال :
 int a = 23;
float b = 3.14f;
Console.WriteLine(a.GetType());
Console.WriteLine(b.GetType());
خروجی کد‌های بالا : 
System.Int32
System.Single

چک کردن نوع داده‌ی دو شیء

فرض کنید 2 شیء را با نام‌های obj1 و obj2 داریم که هر دو از نوع long هستند. برای اینکه این مقایسه را انجام دهیم، از متد Object.RefrenceEqual می‌توان استفاده کرد.
مثال :
long obj1 = 356;
long obj2 = 54;
float obj3 = 234;
Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj2.GetType()));
Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj3.GetType()));
خروجی کد‌های بالا : 
True
False

تعریف یک متغیر ومقدار دهی به آن
سی شارپ یک زبان strongly typed است (البته با در نظر نگرفتن نوع dynamic آن). به این معنا که کلیه‌ی متغیر‌ها، قبل از استفاده باید تعریف و مقدار دهی شوند و بعد از تعریف متغیر، نمی‌توان نوع آن را تغییر داد. رفتار یک متغیر بر اساس نوع انتخابی ما مشخص می‌شود. بطور مثال با انتخاب نوع int تنها می‌توان اعداد صحیح را ذخیره و نگهداری کرد و برای تغییر رفتار متغیر‌ها باید آنها را تبدیل کنیم.

تعریف یک متغیر
برای استفاده از یک متغیر ابتداباید آن را تعریف کنیم :
//<data type> <variable name>;
Int a;

مقداردهی اولیه یک متغیر

مقدار دهی اولیه‌ی یک متغیر با استفاده از عملگر = و نوشتن مقدار مورد نظر برای ذخیره کردن در متغیر، در سمت راست عملگر اتفاق خواهد افتاد.
//<data type> <variable name>=value;
Int a=23;
Int a;//declare تعریف
a=23;//مقدار دهی اولیه initializing
Int a=23;//تعریف و مقدار دهی در یک خط
Int a,b,c=23;//تعریف چند متغیر و مقدار دهی در یک خط


قرار دا‌دهای نام گذاری متغیر‌ها :

در دنیای برنامه نویسی دو نوع قرار داد نام گذاری بسیار متداول وجود دارند:
 1-  camelCase : در این قرار داد، حرف اول کلمه‌ی اول، بصورت کوچک و حرف اول از کلمه‌ی دوم، بصورت بزرگ نوشته خواهد شد. برای مثال: firstName,lastName
 2- PascalCase : در این قرار داد حروف ابتدایی دو کلمه‌ی مجاور، بصورت بزرگ نوشته خواهند شد: FirstName,LastName

چند نکته :
 • نامگذاری متغیر‌ها را می‌توانید با علامت _ و یا @ شروع کنید.
 • کلمات کلیدی (key word) سی شارپ نمی‌توانند به عنوان نام متغیر مورد استفاده قرار بگیرند (مگر آنکه با @ شروع شوند).
 • در بین نام متغیر نباید فضای خالی وجود داشته باشد. کاراکتر‌های سازنده‌ی متغیر می‌توانند اعداد، حروف و زیر خط باشند.
لیستی از نام گذاری‌های مجاز:
 int abc;
long _abcd;
float @abcd;
bool main_button;
decimal piValue;
string firstName;
string first_name;
bool button55_on;
لیستی از نام گذاری‌های غیر مجاز
long _a.5bc5d;
float @ab cd;
decimal pi@Value;
//استفاده از کلمات کلیدی سی شارپ که کامپایلر آنها را مجاز نمی‌داند
bool class;
string namespace;
string string;
int static;
برای مطالعه‌ی کاملتر کلمات کلیدی سی شارپ می‌توانید اینجا را مطالعه کنید.


در ادامه کمی در مورد نوع داده‌ها بحث خواهیم کرد.
در سی شارپ دو مدل نوع داده وجود دارد:
 • انواع مقداری Value Type
 • انواع ارجاعی یا اشاره‌ای Reference Type

انواع مقداری (Value Type) :
 • انواع مقداری مستقیما حاوی داده‌ها هستند. اگر یک متغیر از نوع مقداری را به یک متغیر دیگر تخصیص دهید، مقدار آنها مستقیما کپی می‌شوند؛ برعکس نوع‌های اشاره‌ای که با نخصیص یک متغیر به یک متغیر دیگر، تنها اشاره‌گر به مقدار شیء کپی خواهد شد و نه خود شیء.
 • کلیه نوع‌های مقداری از کلاس ValueType مشتق شده‌اند.
 • در فضای stack  به آنها حافظه تخصیص داده می‌شود.
 • نمی‌توانند مقدار null  بپذیرند. البته با قابلیت nullabletype امکان تخصیص مقدار null به نوع داده‌های مقداری نیز مهیا شده است.
 • همه نوع‌های داده‌های مقداری، یک سازنده پیش فرض دارند که به صورت ضمنی کار مقدار دهی اولیه برای آنها را انجام می‌دهد. برای مطالعه بیشتر درباره مقادیر پیش فرض به اینجا مراجعه کنید.

انواع مقداری به دو دسته‌ی اصلی تقسیم می‌شود :
 • Structs
 • Enumerations

طبقه بندی Structs به صورت زیر است :
 • Numeric Type
* Integral Type : sbyte,short,ushort,int,uint,long,ulong,char
* Floating-Point Types : float,double
* Decimal : decimal
 •  Bool دو مقدار true و false
 • User Defined Struct


نوع داده نال (تهی) پذیر (nullable Type) و چگونگی تعریف آن

در ابتدای معرفی نوع داده‌های مقداری گفتیم همیشه باید وضعیت متغیر مشخص و مقدار دهی اولیه‌ی آن یا به صورت ضمنی و یا آشکار انجام شود. هیچ یک از نوع داده‌های مقداری نمی‌توانند بصورت null تعریف شوند. برای تبدیل یک نوع داده مقداری به صورتی که قابلیت ذخیره‌ی مقدار null را داشته باشد، بعد از نوشتن نوع داده، علامت سوال ؟ قرار می‌دهیم.
 < data type >? < variable name >= null; //syntax

int? a = null; //assigning null
int? b = 55; //assigning null and a value
var? c = 55 //it will give error

نکته :  var نمی‌تواند بصورت nullable تعریف شود.

برای چک کردن مقدار در انواع تهی پذیر (nullable) دو خصوصیت وجود دارد:
 • HasValue
اگر مقداری در متغیر وجود داشته باشد ارزش true  بازگردانده می‌شود؛ در غیر اینصورت ارزش false
 • Value
مقدار واقعی متغیر را باز می‌گرداند.

مثال :
 int? a = null;
int? b = 22;
Console.WriteLine(a.HasValue);
//------------
Console.WriteLine(b.HasValue);
Console.WriteLine(b.Value);
خروجی کد بالا :
 False
True
22

انواع ارجاعی Reference Type

انواع ارجاعی مستقیما حاوی اطلاعات نیستند و ارجاعی هستند به آدرسی از حافظه که حاوی اطلاعات واقعی است. به بیانی دیگر، اشاره‌گری به آدرسی از حافظه هستند.
 • انواع ارجاعی بصورت غیر مستقیم حاوی داده‌ها هستند.
 • در بخشی از حافظه که به آن heap می‌گوییم، به آنها فضا اختصاص داده می‌شود.
 • می‌توانند بصورت null (بدون مقدار) باشند.

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

 • انواع از پیش تعریف شده
  Object,string,dynamic
 • انواع تعریف شده توسط کاربر
        class,interface,delegate

نکته : آدرس مکانی از حافظه که داده‌ها در آن قرار دارند، در بخش پشته یا Stack ذخیره می‌شود و داد‌ه‌ها در فضای heap ذخیره می‌شوند.
مثال :
 test obj; //allocating reference on stack
obj= new test(55);//allocating object on heap

نکته : دو متغیر از نوع ارجاعی می‌توانند به یک آدرس از حافظه اشاره کنند. در شکل زیر این موضوع نشان داده شده است.

 
در شکل زیر طبقه بندی نوع داده‌ها در سی شارپ نشان داده شده است :


• عملیات کپی در نوع داده مقداری
وقتی از یک متغیر مقداری را به یک متغیر دیگر تخصیص می‌دهیم، یک کپی جدید از آن در فضای stack  ایجاد می‌شود. بدین معنی که محتوای دو متغیر یکسان هستند، ولی در دو بخش مجزای در حافظه‌ی Stack قرار دارند. به همین خاطر تغییر  محتوای یک متغیر، محتوای متغیر دیگر را تغییر نمی‌دهد.
مثال :
 int a = 55;//declare a and initialize
int copya = a;//copya contains the copy of value a
دیاگرام حافظه کد بالا :

 

• عملیات کپی، در نوع داده‌ی ارجاعی
وقتی یک متغیر از نوع ارجاعی را به یک متغیر دیگر تخصیص می‌دهیم، دو اشاره‌گر در فضای Stack ایجاد می‌شود که به یک مقدار واحد در حافظه‌ی heap اشاره می‌کنند. آدرس‌های ذخیره شده‌ی در stack  یکسان هستند.
مثال : در اینجا فرض بر این است کهtest یک کلاس تعریف شده‌ی توسط کاربر می‌باشد.
test obj;
obj=new test(23);
test objCopy;
objCopy = obj;

دیاگرام حافظه‌ی قطعه کد بالا به شکل زیر است :



تخصیص حافظه در بخش Stack  و Heap به متغیر‌ها

سیستم عامل و net CLR. حافظه را به دو بخش stack و heap تقسیم بندی می‌کنند.
زمانی که یک متد را فراخوانی می‌کنیم، در بخش پشته به پارامتر‌های متد فضا تخصیص داده می‌شود و بعد از پایان کار متد، فضای اشغال شده‌ی بوسیله GC یا همان Garbage collection  آزاد می‌شود.
تخصیص حافظه در Stack  بر اساس قانون LIFO انجام و به ترتیب و پشت سر هم، حافظه تخصیص داده می‌شود. دیاگرام تخصیص حافظه به stack:


تخصیص حافظه در Heap بصورت تصادفی است؛ بر عکس پشته (stack) که به ترتیب و متوالی انجام می‌شد. انواع ارجاعی در Stack  ذخیره می‌شوند؛ ولی داده‌ی واقعی در heap قرار می‌گیرد.
حافظه‌های پویا در بخش heap و حافظه‌های استاتیک در بخش stack تخصیص داده می‌شوند.
 
مطالب
SignalR
چند وقتی هست که در کنار بدنه اصلی دات‌نت فریم‌ورک چندین کتابخونه به صورت متن‌باز در حال توسعه هستند. این مورد در ASP.NET بیشتر فعاله و مثلا دو کتابخونه SignalR و WebApi توسط خود مایکروسافت توسعه داده میشه.
SignalR همونطور که در سایت بسیار خلاصه و مفید یک صفحه‌ای! خودش توضیح داده شده (^) یک کتابخونه برای توسعه برنامه‌های وب «زمان واقعی»! (real-time web) است:
Async library for .NET to help build real-time, multi-user interactive web applications.
برنامه‌های زمان واقعی به صورت خلاصه و ساده به‌صورت زیر تعریف میشن (^):
The real-time web is a set of technologies and practices that enable users to receive information as soon as it is published by its authors, rather than requiring that they or their software check a source periodically for updates.
یعنی کاربر سیستم ما بدون نیاز به ارسال درخواستی صریح! برای دریافت آخرین اطلاعات به روز شده در سرور، در برنامه کلاینتش از این تغییرات آگاه بشه. مثلا برنامه‌هایی که برای نمایش نمودارهای آماری داده‌ها استفاده میشه (بورس، قیمت ارز و طلا و ...) و یا مهمترین مثالش میتونه برنامه «چت» باشه. متاسفانه پروتوکل HTTP مورد استفاده در وب محدودیت‌هایی برای پیاده‌سازی این گونه برنامه‌ها داره. روش‌های گوناگونی برای پیاده‌سازی برنامه‌های زمان واقعی در وب وجود داره که کتابخونه SignalR فعلا از موارد زیر استفاده میکنه:
  1. تکنولوژی جدید WebSocket (^) که خوشبختانه پشتیبانی کاملی از اون در دات نت 4.5 (چهار نقطه پنج! نه چهار و نیم!) وجود داره. اما تمام مرورگرها و تمام وب سرورها از این تکنولوژی پشتیبانی نمیکنند و تنها برخی نسخه‌های جدید قابلیت استفاده از آخرین ورژن WebSocket رو دارند که میشه به کروم 16 به بالا و فایرفاکس 11 به بالا و اینترنت اکسپلورر 10 اشاره کرد (برای استفاده از این تکنولوژی در ویندوز نیاز به IIS 8.0 است که متاسفانه فقط در ویندوز 8.0 موجوده):
    Chrome 16, Firefox 11 and Internet Explorer 10 are currently the only browsers supporting the latest specification (RFC 6455).
  2.  یه روش دیگه Server-sent Events نام داره که داده‌های جدید رو به فرم رویدادهای DOM به سمت کلاینت میفرسته(^).
  3. روش دیگه‎‌ای که موجوده به Forever Frame معروفه که در این روش یک iframe مخفی درون کد html مسئول تبادل داده‌هاست. این iframe مخفی به‌صورت یک بلاک Chunked (^) به سمت کلاینت فرستاده میشه. این iframe که مسئول رندر داده‌های جدید در سمت کلاینت هست ارتباط خودش رو با سرور تا ابد! (برای همین بهش forever میگن) حفظ میکنه. هر وقت رویدادی سمت سرور رخ میده با استفاده از این روش داده‌ها به‌صورت تگ‌های script به این فریم مخفی فرستاده می‌شوند و چون مرورگرها محتوای html رو به صورت افزایشی (incrementally) رندر میکنن بنابراین این اسکریپتها به‌ترتیب زمان دریافت اجرا می‌شوند. (البته ظاهرا عبارت forever frame در صنعت عکاسی! معروف‌تره بنابراین در جستجو در زمینه این روش ممکنه کمی مشکل داشته باشین) (^).
  4. روش آخر که در کتابخونه SignalR ازش استفاده میشه long-polling نام داره. در روش polling معمولی پس از ارسال درخواست توسط کلاینت، سرور بلافاصله نتیجه حاصله رو به سمت کلاینت میفرسته و ارتباط قطع میشه. بنابراین برای داده‌های جدید درخواست جدیدی باید به سمت سرور فرستاده بشه که تکرار این روش باعث افزایش شدید بار بر روی سرور و کاهش کارآمدی اون می‌شه. اما در روش long-polling پس از برقراری ارتباط کلاینت با سرور این ارتباط تا مدت زمان معینی (که توسط یه مقدار تایم اوت مشخص میشه و مقدار پیش‌فرضش 2 دقیقه است) برقرار میمونه. بنابراین کلاینت میتونه بدون ایجاد مشکلی در کارایی، داده‌های جدید رو از سرور دریافت کنه. به این روش در برنامه‌نویسی وب اصطلاحا برنامه‌نویسی کامت (Comet Programming) میگن (^ ^).
(البته روش‌های دیگری هم برای پیاده‌سازی برنامه‌های زمان اجرا وجود داره مثل کتابخونه node.js که جستجوی بیشتر به خوانندگان واگذار میشه)
SignalR برای برقراری ارتباط ابتدا بررسی میکنه که آیا هر دو سمت سرور و کلاینت قابلیت پشتیبانی از WebSocket رو دارند. در غیراینصورت سراغ روش Server-sent Events میره. اگر باز هم موفق نشد سعی به برقراری ارتباط با روش forever frame میکنه و اگر باز هم موفق نشد در آخر سراغ long-polling میره.
با استفاده از SignalR شما میتونین از سرور، متدهایی رو در سمت کلاینت فراخونی کنین. یعنی درواقع با استفاده از کدهای سی شارپ میشه متدهای جاوااسکریپت سمت کلاینت رو صدا زد!
بطور خلاصه در این کتابخونه دو کلاس پایه وجود داره:
  1. کلاس سطح پایین PersistentConnection
  2. کلاس سطح بالای Hub
علت این نامگذاری به این دلیله که کلاس سطح پایین پیاده‌سازی پیچیده‌تر و تنظیمات بیشتری نیاز داره اما امکانات بیشتری هم در اختیار برنامه‌نویس قرار می‌ده.
خوب پس از این مقدمه نسبتا طولانی برای دیدن یک مثال ساده میتونین با استفاده از نوگت (Nuget) مثال زیر رو نصب و اجرا کنین (اگه تا حالا از نوگت استفاده نکردین قویا پیشنهاد میکنم که کار رو با دریافتش از اینجا آغاز کنین) :
PM> Install-Package SignalR.Sample
پس از کامل شدن نصب این مثال اون رو اجرا کنین. این یک مثال فرضی ساده از برنامه نمایش ارزش آنلاین سهام برخی شرکتهاست. میتونین این برنامه رو همزمان در چند مرورگر اجرا کنین و نتیجه رو مشاهده کنین.
حالا میریم سراغ یک مثال ساده. میخوایم یک برنامه چت ساده بنویسیم. ابتدا یک برنامه وب اپلیکیشن خالی رو ایجاد کرده و با استفاده از دستور زیر در خط فرمان نوگت، کتابخونه SignalR رو نصب کنین:
PM> Install-Package SignalR
پس از کامل شدن نصب این کتابخونه، ریفرنس‌های زیر به برنامه اضافه میشن:
Microsoft.Web.Infrastructure
Newtonsoft.Json
SignalR
SignalR.Hosting.AspNet
SignalR.Hosting.Common
برای کسب اطلاعات مختصر و مفید از تمام اجزای این کتابخونه به اینجا مراجعه کنین.
همچنین اسکریپت‌های زیر به پوشه Scripts اضافه میشن (این نسخه‌ها مربوط به زمان نگارش این مطلب است):
jquery-1.6.4.js
jquery.signalR-0.5.1.js
بعد یک کلاس با نام SimpleChat به برنامه اضافه و محتوای زیر رو در اون وارد کنین:
using SignalR.Hubs;
namespace SimpleChatWithSignalR
{
  public class SimpleChat : Hub
  {
    public void SendMessage(string message)
    {
      Clients.reciveMessage(message);
    }
  }
} 
دقت کنین که این کلاس از کلاس Hub مشتق شده و همچنین خاصیت Clients از نوع dynamic است. (در مورد جزئیات این کتابخونه در قسمت‌های بعدی توضیحات مفصل‌تری داده میشه)
سپس یک فرم به برنامه اضافه کرده و محتوای زیر رو در اون اضافه کنین:
<input type="text" id="msg" />
<input type="button" value="Send" id="send" /><br />
<textarea id='messages' readonly="true" style="height: 200px; width: 200px;"></textarea>
<script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.signalR-0.5.1.min.js" type="text/javascript"></script>
<script src="signalr/hubs" type="text/javascript"></script>
<script type="text/javascript">
  var chat = $.connection.simpleChat;
  chat.reciveMessage = function (msg) {
   $('#messages').val($('#messages').val() + "-" + msg + "\r\n"); 
  };
  $.connection.hub.start();
  $('#send').click(function () {
    chat.sendMessage($('#msg').val());
  });
</script>
همونطور که میبینین برنامه چت ما آماده شد! حالا برنامه رو اجرا کنین و با استفاده از دو مرورگر مختلف نتیجه رو مشاهده کنین.
نکته کلیدی کار SignalR در خط زیر نهفته است:
<script src="signalr/hubs" type="text/javascript"></script>
اگر محتوای آدرس فوق رو دریافت کنین می‌بینین که موتور این کتابخانه تمامی متدهای موردنیاز در سمت کلاینت رو با استفاده از کدهای جاوااسکریپت تولید کرده. البته در این کد تولیدی از نامگذاری camel Casing استفاده میشه، بنابراین متد SendMessage در سمت سرور به‌صورت sendMessage در سمت کلاینت در دسترسه.
امیدوارم تا اینجا تونسته باشم علاقه شما به استفاده از این کتابخونه رو جلب کرده باشم. در قسمت‌های بعد موارد پیشرفته‌تر این کتابخونه معرفی میشه.
اگه علاقه‌مند باشین میتونین از این ویکی اطلاعات بیشتری بدست بیارین.


به روز رسانی
در دوره‌ای به نام SignalR در سایت، به روز شده‌ای این مباحث را می‌توانید مطالعه کنید.
مسیرراه‌ها
ASP.NET Core
ASP.NET Core 1.0
ASP.NET Core 2.0
روش ارتقاء
ASP.NET Core Identity 

کار با Areas در ASP.NET Core
کار با کوکی‌ها در ASP.NET Core
بررسی روش آپلود فایل‌ها در ASP.NET Core
ارسال ایمیل در ASP.NET Core
نوشتن Middleware سفارشی در ASP.NET Core
نوشتن TagHelperهای سفارشی برای ASP.NET Core
بررسی تغییرات Reflection در NET Core.
استفاده‌ی گسترده از DateTimeOffset در NET Core.
بررسی روش دسترسی به HttpContext در ASP.NET Core
توزیع پروژه‌های ASP.NET Core 1.1 بدون ارائه فایل‌های View آن
تغییرات رمزنگاری اطلاعات در NET Core.
ساخت بسته‌های نیوگت مخصوص NET Core.
تهیه قالب برای ارسال ایمیل‌ها در ASP.NET Core توسط Razor Viewها
روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET Core
بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core
سفارشی سازی صفحه‌ی اول برنامه‌های Angular CLI توسط ASP.NET Core
تغییرات Encoding در NET Core.
تغییرات متدهای بازگشت فایل‌ها به سمت کلاینت در ASP.NET Core
پیاده سازی برنامه‌های چند مستاجری در ASP.NET Core
مقدمه‌ای بر تزریق وابستگی‌ها درASP.NET Core 
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
اجرای سرویسهای NodeJS در ASP.NET Core
بررسی خطاهای ممکن در حین راه اندازی اولیه برنامه‌های ASP.NET Core در IIS 
تست کردن متدهای یک Controller به کمک PowerShell
کار با Visual Studio در ASP.NET Core

مطالب
معرفی Lex.Db
Lex.Db یک بانک اطلاعاتی درون پروسه‌ای (مدفون شده یا embedded) بسیار سریع نوشته شده با سی‌شارپ است. این بانک اطلاعاتی کم حجم، سورس باز بوده و مجوز استفاده از آن LGPL است. به این معنا که استفاده از اسمبلی‌های آن در هر نوع پروژه‌ای آزاد است.
نکته مهم آن سازگاری با برنامه‌های دات نت 4 به بعد، همچنین برنامه‌های ویندوز 8، سیلورلایت 5، ویندوز فون 8 و همچنین اندروید (از طریق Mono) است. به علاوه چون با دات نت تهیه شده است، دیگر نیازی نیست دو نگارش 32 بیتی و 64 بیتی آن توزیع شوند و به این ترتیب مشکلات توزیع بانک‌های اطلاعاتی native مانند SQLite را ندارد ( و مطابق ادعای نویسنده آلمانی آن، از SQLite سریعتر است).
API این بانک اطلاعاتی، هر دو نوع متدهای synchronous  و  asynchronous را شامل می‌شود؛ به همین جهت با برنامه‌های ویندوز 8 و سیلورلایت نیز سازگاری دارد.
Lex.Db از برنامه‌های چندریسمانی و همچنین استفاده از یک بانک اطلاعاتی آن توسط چندین پروسه همزمان نیز پشتیبانی می‌کند.
در ادامه مروری خواهیم داشت بر نحوه استفاده از آن در حالت طراحی رابطه‌ای؛ از این جهت که فعلا به ظاهر این بانک اطلاعاتی روابط را پشتیبانی نمی‌کند، اما در عمل پیاده سازی آن مشکل نیست.

دریافت Lex.Db

برای دریافت Lex.Db، دستور ذیل را در خط فرمان پاورشل نیوگت وارد نمائید:
 PM> Install-Package Lex.Db
بسته به نوع پروژه شما (دات نت یا WinRT یا ...)، اسمبلی متناسبی به پروژه اضافه خواهد شد.


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

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

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public int? CustomerFK { get; set; }
        public int[] ProductsFK { get; set; }
    }
مدل‌های برنامه آزمایشی مطلب جاری را در اینجا ملاحظه می‌کنید. برای طراحی روابط یک به صفر یا یک و همچنین یک به چند، تنها کافی است کلیدهای اصلی یا آرایه‌ای از کلیدهای اصلی مرتبط را در اینجا ذخیره کنیم، که نمونه‌ای از آن‌را در کلاس Order ملاحظه می‌کنید.


آغاز بانک اطلاعاتی

    public static class Database
    {
        public static DbInstance Instance { get; private set; }

        public static DbTable<Product> Products { get; private set; }
        public static DbTable<Order> Orders { get; private set; }
        public static DbTable<Customer> Customers { get; private set; }

        /// <summary>
        /// سازنده استاتیکی که در طول عمر برنامه فقط یکبار اجرا می‌شود
        /// </summary>
        static Database()
        {
            createDb();
            getTables();
        }

        private static void getTables()
        {
            Products = Instance.Table<Product>();
            Customers = Instance.Table<Customer>();
            Orders = Instance.Table<Order>();
        }

        private static void createDb()
        {
            Instance = new DbInstance(Path.Combine(Environment.CurrentDirectory, "LexDbTests"));

            Instance.Map<Product>()
                    .WithIndex("NameIdx", x => x.Name)
                    .Automap(i => i.Id, true);

            Instance.Map<Order>()
                    .Automap(i => i.Id, true);

            Instance.Map<Customer>()
                    .WithIndex("NameIdx", x => x.Name)
                    .WithIndex("CityIdx", x => x.City)
                    .Automap(i => i.Id, true);

            Instance.Initialize();
        }
    }
کلاس دیتابیس و سازنده آن، استاتیک تعریف شده‌اند؛ تا در طول عمر برنامه تنها یکبار وهله سازی شوند. new DbInstance یک وهله جدید از بانک اطلاعاتی را آغاز می‌کند. سازنده آن، مسیر پوشه‌ای که فایل‌های این بانک اطلاعاتی در آن ذخیره خواهند شد را دریافت می‌کند. Lex.Db به ازای هر کلاس مدلی که به آن معرفی شود، دو فایل data و index را ایجاد می‌کند.
سپس توسط وهله‌ای از بانک اطلاعاتی که ایجاد کردیم، کار معرفی خواص مدل‌های برنامه توسط متد Map و Automap انجام می‌شود. متد Automap خاصیت primary key کلاس را دریافت کرده و همچنین پارامتر دوم آن مشخص می‌کند که آیا این کلید اصلی به صورت خودکار ایجاد شود یا خیر. به علاوه در همینجا می‌توان روی فیلدهای مختلف، ایندکس نیز ایجاد کرد. متد WithIndex یک نام دلخواه را دریافت کرده و سپس خاصیتی را که باید بر روی آن ایندکس ایجاد شود، دریافت می‌کند.
در نهایت متد Initialize باید فراخوانی گردد. البته اگر برنامه شما WinRT است، این متد Initialize Async خواهد بود.
جداول نیز بر اساس مدل‌های برنامه از طریق متد Instance.Table در دسترس قرار گرفته‌اند.

افزودن اطلاعات به بانک اطلاعاتی
        private static void addData()
        {
            var customer1 = new Customer { Name = "customer1", City = "City1" };
            var customer2 = new Customer { Name = "customer2", City = "City2" };
            Database.Instance.Save(customer1, customer2); // automatic Id assignment after Save

            var product1 = new Product { Name = "product1" };
            var product2 = new Product { Name = "product2" };
            Database.Instance.Save(product1, product2); // automatic Id assignment after Save

            var order1 = new Order { CustomerFK = customer1.Id, ProductsFK = new[] { product1.Id } };
            var order2 = new Order { CustomerFK = customer2.Id, ProductsFK = new[] { product1.Id, product2.Id } };
            Database.Instance.Save(order1, order2); // automatic Id assignment after Save
        }
اکنون که کار آغاز بانک اطلاعاتی صورت گرفت، برای افزودن اطلاعات از متد Database.Instance.Save می‌توان استفاده کرد (در برنامه‌های WinRT از  متد Save Async استفاده کنید).
در اینجا نیازی به ذکر Id نمونه‌های ساخته شده نیست؛ از این جهت که در حین عملیات Save، به صورت خودکار انتساب خواهند یافت.
همچنین نحوه مقدار دهی کلیدهای خارجی نیز با استفاده از همین کلیدهای اصلی آماده شده است.


واکشی تمام اطلاعات

        private static void loadAll()
        {
            var orders = Database.Orders.LoadAll();
            foreach (var order in orders)
            {
                // نحوه دریافت اطلاعات مشتری بر اساس کلید خارجی ثبت شده
                var orderCustomer = Database.Customers.LoadByKey(order.CustomerFK.Value);
                Console.WriteLine("Order Id: {0}, Customer: {1} ({2}) {3}", order.Id, orderCustomer.Name, orderCustomer.Id, orderCustomer.City);

                // نحوه بازیابی لیستی از اشیاء مرتبط از طریق آرایه‌ای از کلیدهای خارجی ثبت شده
                var orderProducts = Database.Products.LoadByKeys(order.ProductsFK);
                foreach (var product in orderProducts)
                {
                    Console.WriteLine("  Product Id: {0}, Name: {1}", product.Id, product.Name);
                }
            }
        }
بانک اطلاعاتی آغاز شد؛ تعدادی رکورد نیز در آن ثبت گردید. اکنون برای بازیابی اطلاعات می‌توان از متدهای در دسترس جداول کلاس Database استفاده کرد. برای مثال متد LoadAll تمام رکوردهای یک جدول را واکشی می‌کند (در برنامه‌های WinRT این متد LoadAll Async خواهد بود).
سپس با استفاده از متدهای LoadByKey و LoadByKeys، به سادگی می‌توان اشیاء مرتبط با هر سفارش را نیز واکشی کرد.


استفاده از ایندکس‌ها برای کوئری گرفتن

        private static void queryingByAnIndex()
        {
            var name = "customer1";
            var customersList = Database.Customers
                                        .IndexQueryByKey("NameIdx", name)
                                        .ToList();
            foreach (var person in customersList)
            {
                Console.WriteLine(person.Name);
            }
        }
در ابتدای بحث، توسط متد WithIndex، تعدادی ایندکس را نیز تعریف کردیم. اکنون توسط این ایندکس‌ها و متد IndexQueryByKey، می‌توان کوئری‌هایی بسیار سریع را تهیه کرد.
            // Using Take and Skip
            var list1 = Database.Orders.Query<int>() // primary idx
                                       .Take(1).Skip(2).ToList();

            // Querying Between Ranges 
            var list2 = Database.Customers
                                .IndexQuery<string>("NameIdx")
                                .GreaterThan("a", orEqual: true).LessThan("d").ToList();
همچنین در اینجا متدهایی مانند Take و Skip و یا جستجو در یک بازه توسط متدهای GreaterThan و LessThan نیز پشتیبانی می‌شوند.


حذف رکوردها
        private static void deletingRecords()
        {
            Database.Customers.DeleteByKey(key: 1);

            var customers = Database.Customers.LoadByKeys(new[] { 1, 2 });
            Database.Customers.Delete(customers);
        }
برای حذف رکوردها از متدهای DeleteByKey و یا Delete می‌توان استفاده کرد. متد Delete می‌تواند آرایه‌ای از اشیاء را نیز قبول کند.
و اگر خواستید کل بانک اطلاعاتی را خالی کنید، متد Database.Instance.Purge اینکار را انجام خواهد داد.


کدهای کامل این مثال را از اینجا نیز می‌توانید دریافت کنید:
Program-LexDb.cs
 
مطالب
PowerShell 7.x - قسمت یازدهم - یک مثال
به عنوان یک تمرین میخواهیم یک پروژه گیت ساده را به صورت زیر visualise کنیم: 


برای تولید چنین نموداری از میتوانیم Mermaid استفاده کنیم. یکی از انواع دیاگرام‌هایی را که پشتیبانی میکند، Gitgraph میباشد. دیاگرام‌ها در Mermaid با کمک یک DSL ساخته میشوند. سینکس آن نیز خیلی ساده است؛ ابتدا نوع دیاگرامی را که میخواهیم ترسیم کنیم، تعیین میکنیم و سپس محتویات را براساس نوع دیاگرام، تعیین میکنیم. به عنوان مثال برای Gitgraph سینتکس آن به این صورت است: 

gitGraph
   commit
   commit
   branch develop
   checkout develop
   commit
   commit
   checkout main
   merge develop
   commit
   commit

در ساختار فوق ابتدا دو کامیت بر روی برنچ اصلی (main) انجام شده‌است؛ سپس یک برنچ جدید را با نام develop، ایجاده کرده‌ایم و بلافاصله به آن checkout کرده‌ایم. در ادامه تعدادی کامیت را بر روی این برنچ انجام داده و در نهایت برنچ موردنظر را بر روی main، مرج کرده‌ایم. در نهایت نیز دو کامیت دیگر را بر روی main ایجاد کرده‌ایم. تعریف فوق، منجر به ساخت چنین نموداری خواهد شد: 


از ادیتور آنلاین Mermaid نیز میتوانید برای تست سینکس استفاده کنید. در ادامه میخواهیم با کمک PowerShell، از روی یک پروژه‌ی گیت، DSL موردنیاز برای ساخت دیاگرام را ایجاد کنیم. برای اینکار ابتدا توسط تابع زیر یک پروژه‌ی گیت را با تعدادی فایل نمونه ایجاد خواهیم کرد: 

Function New-RandomRepo {
    Function RandomFiles($branch = "main") {
        1..3 | ForEach-Object {
            New-Item -ItemType File -Name "file_$($_).txt"
            Set-Content -Path "file_$($_).txt" -Value "This is file $($_) on branch $branch"
            git add .
            git commit -m "Commit $($_) on branch $branch"
        }
    }
    Set-Location ~/Desktop
    New-Item -ItemType Directory "random_git_repo"
    Set-Location "random_git_repo"
    git init -b main
    Write-Output "This is the main branch" | Set-Content -Path "main.txt"
    git add .
    git commit -m "Initial commit"
    1..3 | ForEach-Object { 
        git checkout -b "branch_$($_)"
        RandomFiles "branch_$($_)"
        git checkout main
    }
}

در ادامه تابع New-GitRepoDiagram را برای تولید ساختار مورد نیاز نوشته‌ایم: 

Function New-GitRepoDiagram {
    $commitIds = @()
    $branches = git branch | ForEach-Object {
        $default = $false
        $activeBranch = git symbolic-ref --short HEAD
        $currentBranch = ($_.Replace("* ", " ")).Trim()
        if ($currentBranch -eq $activeBranch) {
            $default = $true
        }
        @{
            name         = $currentBranch
            isMainBranch = $default
        } | ConvertTo-Json
    } | ConvertFrom-Json
    
    $defaultBranch = $branches | Where-Object { $_.isMainBranch -eq $true } | Select-Object -ExpandProperty name
    $mermaidFile = "%%{init: { 'gitGraph': {'mainBranchName': '$defaultBranch' } } }%%" + [Environment]::NewLine
    $mermaidFile += 'gitGraph' + [Environment]::NewLine

    foreach ($branch in ($branches | Sort-Object -Property isMainBranch -Descending)) {
        $name = $branch.name
        $notIncludeTheMainCommits = $name -ne $defaultBranch ? "--not $(git merge-base $defaultBranch $name)" : ""
        $notIncludeTheMainCommits
        $logs = git log --pretty=format:'{"commit": "%h", "author": "%an", "message": "%s"}' --reverse $name | ConvertFrom-Json
        if ($name -ne $defaultBranch) {
            $mermaidFile += '   branch "$name"'.Replace('$name', $name) + [Environment]::NewLine
        }
        foreach ($log in $logs) {
            $commit = $log.commit
            if ($commitIds -contains $commit) {
                continue
            }
            $commitToAdd = '   commit id: "$commit"'.Replace('$commit', $commit) + [Environment]::NewLine
            $mermaidFile += $commitToAdd
            $commitIds += $commit
        }
        if ($name -ne $defaultBranch) {
            $mermaidFile += '   checkout main' + [Environment]::NewLine
        }
    }
    Write-Host $mermaidFile
}

توضیحات:

  • در تابع فوق، ابتدا یک آرایه‌ی خالی برای ذخیره‌ی کامیت آی‌دی‌ها، اضافه شده؛ از این آرایه برای جلوگیری از اضافه شدن کامیت تکراری در هر برنچ استفاده شده‌است.
  • سپس با کمک دستور git branch، لیست تمام برنچ‌ها، دریافت شده‌است (همانطور که در قسمت قبل بررسی شد +).
  • برای هر برنچ، تابع تعیین میکند که آیا برنچ جاری است یا خیر. اینکار با کمک دستور git symbolic-ref انجام شده‌است.
  • در ادامه متغیر mermaidFile$ برای ایجاد یک گراف جدید Git مقداردهی میشود. در اینجا نام برنچ اصلی برابر با نام برنچ پیش‌فرض، تنظیم می‌شود.
  • سپس لیست کامیت‌های هر برنچ را با کمک دستور git log (همان سینتکسی که در قسمت‌های قبل بررسی شد +) استخراج میکنیم.
  • و در نهایت به ازای هر کامیت، یک commit id تولید کرده‌ایم.

با فراخوانی تابع فوق، اینچنین ساختاری برایمان تولید خواهد شد: 

%%{init: { 'gitGraph': {'mainBranchName': 'main' } } }%%
gitGraph
   commit id: "765100f"
   branch "branch_1"
   commit id: "c88c441"
   commit id: "44149d9"
   commit id: "a660fe3"
   checkout main
   branch "branch_2"
   commit id: "2dcb572"
   commit id: "b043ad1"
   commit id: "92cafc0"
   checkout main
   branch "branch_3"
   commit id: "559e381"
   commit id: "f72957f"
   commit id: "c066e72"
   checkout main

خروجی فوق دقیقاً دیاگرامی است که در ابتدای مطلب نشان داده شد:

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


مدل لغو اعمال

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


یک مثال استفاده از CancellationToken

کدهای زیر، یک فایل حجیم را از مکانی به مکانی دیگر کپی می‌کنند. برای این منظور از متد CopyToAsync که در دات نت 4.5 اضافه شده‌است، استفاده کرده‌ایم؛ زیرا از مکانیزم لغو عملیات پشتیبانی می‌کند.
using System;
using System.IO;
using System.Threading;

namespace Async08
{
    class Program
    {
        static void Main(string[] args)
        {
            var source = @"c:\dir\file.bin";
            var target = @"d:\dir\file.bin";
            using (var inStream = File.OpenRead(source))
            {
                using (var outStream = File.OpenWrite(target))
                {
                    using (var cts = new CancellationTokenSource())
                    {
                        var task = inStream.CopyToAsync(outStream, bufferSize: 4059, cancellationToken: cts.Token);
                        Console.WriteLine("Press 'c' to cancel.");
                        var key = Console.ReadKey().KeyChar;
                        if (key == 'c')
                        {
                            Console.WriteLine("Cancelling");
                            cts.Cancel();
                        }
                        Console.WriteLine("Wating...");
                        task.ContinueWith(t => { }).Wait();
                        Console.WriteLine("Status: {0}", task.Status);
                    }
                }
            }
        }
    }
}
کار با تعریف CancellationTokenSource شروع می‌شود. چون از نوع IDisposable است، نیاز است توسط عبارت using، جهت پاکسازی منابع آن، محصور گردد. سپس در اینجا اگر کاربر کلید c را فشار دهد، متد لغو توکن تعریف شده فراخوانی خواهد شد. این توکن نیز به عنوان آرگومان به متد CopyToAsync ارسال شده‌است.
علت استفاده از ContinueWith در اینجا این است که اگر یک task لغو شود، فراخوانی متد Wait بر روی آن سبب بروز استثناء می‌گردد. به همین جهت توسط ContinueWith یک Task خالی ایجاد شده و سپس بر روی آن Wait فراخوانی گردیده‌است.
همچنین باید دقت داشت که سازنده‌ی CancellationTokenSource امکان دریافت زمان timeout عملیات را نیز دارد. به علاوه متد CancelAfter نیز برای آن طراحی شده‌است. نمونه‌ی دیگری از تنظیم timeout را در قسمت قبل با معرفی متد Task.Delay و استفاده از آن با Task.WhenAny مشاهده کردید.


لغو ظاهری وظایفی که لغو پذیر نیستند

فرض کنید متدی به نام GetBitmapAsync با پارامتر cancellationToken طراحی نشده‌است. در این حالت کاربر قصد دارد با کلیک بر روی دکمه‌ی لغو، عملیات را خاتمه دهد. یک روش حل این مساله، استفاده از متد ذیل است:
    public static class CancellationTokenExtensions
    {
        public static async Task UntilCompletionOrCancellation(Task asyncOp, CancellationToken ct)
        {
            var tcs = new TaskCompletionSource<bool>();
            using (ct.Register(() => tcs.TrySetResult(true)))
            {
                await Task.WhenAny(asyncOp, tcs.Task);
            }
        }
    }
در اینجا از روش Task.WhenAny استفاده شده‌است که در آن دو task ترکیب شده‌اند. Task اول همان وظیفه‌ای اصلی است و task دوم، از یک TaskCompletionSource حاصل شده‌است. اگر کاربر دستور لغو را صادر کند، callback ثبت شده توسط این توکن، اجرا خواهد شد. بنابراین در اینجا TrySetResult به true تنظیم شده و یکی از دو Task معرفی شده در WhenAny خاتمه می‌یابد.
این مورد هر چند task اول را واقعا لغو نمی‌کند، اما سبب خواهد شد تا کدهای پس از await UntilCompletionOrCancellation اجرا شوند.


طراحی متدهای غیرهمزمان لغو پذیر

کلاس زیر را در نظر بگیرید:
    public class CancellationTokenTest
    {
        public static void Run()
        {
            var cts = new CancellationTokenSource();
            Task.Run(async () => await test(), cts.Token);
            Console.ReadLine();
            cts.Cancel();
            Console.WriteLine("Cancel...");
            Console.ReadLine();
        }

        private static async Task test()
        {
            while (true)
            {
                await Task.Delay(1000);
                Console.WriteLine("Test...");
            }
        }
    }
در اینجا cancellationToken متد Task.Run تنظیم شده‌است. همچنین پس از فراخوانی آن، اگر کاربر کلیدی را فشار دهد، متد Cancel این توکن فراخوانی خواهد شد. اما .... خروجی برنامه به صورت زیر است:
Test...
Test...
Test...
 
Cancel...
Test...
Test...
Test...
Test...
بله. وظیفه‌ی شروع شده، لغو شده‌است اما متد test آن هنوز مشغول به کار است.
روش اول حل این مشکل، معرفی پارامتر CancellationToken به متد test و سپس بررسی مداوم خاصیت IsCancellationRequested آن می‌باشد:
public class CancellationTokenTest
    {
        public static void Run()
        {
            var cts = new CancellationTokenSource();
            Task.Run(async () => await test(cts.Token), cts.Token);
            Console.ReadLine();
            cts.Cancel();
            Console.WriteLine("Cancel...");
            Console.ReadLine();
        }

        private static async Task test(CancellationToken ct)
        {
            while (true)
            {
                await Task.Delay(1000, ct);
                Console.WriteLine("Test...");

                if (ct.IsCancellationRequested)
                {
                    break;
                }
            }
            Console.WriteLine("Test cancelled");
        }
    }
در اینجا اگر متد cts.Cancel فراخوانی شود، مقدار خاصیت ct.IsCancellationRequested مساوی true شده و حلقه خاتمه می‌یابد.
روش دوم لغو عملیات، استفاده از متد Register است. هر زمان که توکن لغو شود، callback آن فراخوانی خواهد شد:
        private static async Task test2(CancellationToken ct)
        {
            bool isRunning = true;

            ct.Register(() =>
            {
                isRunning = false;
                Console.WriteLine("Query cancelled");
            });

            while (isRunning)
            {
                await Task.Delay(1000, ct);
                Console.WriteLine("Test...");
            }
            Console.WriteLine("Test cancelled");
        }
این روش خصوصا برای حالت‌هایی مفید است که در آن‌ها از متدهایی استفاده می‌شود که خودشان امکان لغو شدن را نیز دارند. به این ترتیب دیگر نیازی نیست مدام بررسی کرد که آیا مقدار IsCancellationRequested مساوی true شده‌است یا خیر. هر زمان که callback ثبت شده در متد Register فراخوانی شد، یعنی عملیات باید خاتمه یابد.