مطالب
آشنایی با M.A.F - قسمت دوم

قسمت قبل بیشتر آشنایی با یک سری از اصطلاحات مرتبط با فریم ورک MAF بود و همچنین نحوه‌ی کلی استفاده از آن. در این قسمت یک مثال ساده را با آن پیاده سازی خواهیم کرد و فرض قسمت دوم بر این است که افزونه‌ی Visual Studio Pipeline Builder را نیز نصب کرده‌اید.

یک نکته پیش از شروع:
- اگر افزونه‌ی Visual Studio Pipeline Builder پس از نصب به منوی Tools اضافه نشده است، یک پوشه‌ی جدید را به نام Addins در مسیر Documents\Visual Studio 2008 ایجاد کرده و سپس فایل‌های آن‌را در این مسیر کپی کنید.

ساختار اولیه یک پروژه MAF

- پروژ‌ه‌هایی که از MAF استفاده می‌کنند، نیاز به ارجاعاتی به دو اسمبلی استاندارد System.AddIn.dll و System.AddIn.Contract.dll دارند (مطابق شکل زیر):



- ساختار آغازین یک پروژه MAF از سه پروژه تشکیل می‌شود که توسط افزونه‌ی Visual Studio Pipeline Builder به 7 پروژه بسط خواهد یافت.
این سه پروژه استاندارد آغازین شامل موارد زیر هستند:



- هاست: همان برنامه‌ی اصلی که قرار است از افزونه استفاده کند.
- قرار داد: نحو‌ه‌ی تعامل هاست و افزونه در این پروژه تعریف می‌شود. (یک پروژه از نوع class library)
- افزونه: کار پیاده سازی قرار داد را عهده دار خواهد شد. (یک پروژه از نوع class library)

- همچنین مرسوم است جهت مدیریت بهتر خروجی‌های حاصل شده یک پوشه Output را نیز به این solution اضافه کنند:



اکنون با توجه به این محل خروجی، به خواص Build سه پروژه موجود مراجعه کرده و مسیر Build را اندکی اصلاح خواهیم کرد (هر سه مورد بهتر است اصلاح شوند)، برای مثال:



نکته‌ی مهم هم اینجا است که خروجی host باید به ریشه این پوشه تنظیم شود و سایر پروژه‌ها هر کدام خروجی خاص خود را در پوشه‌ای داخل این ریشه باید ایجاد کنند.



تا اینجا قالب اصلی کار آماده شده است. قرارداد ما هم به شکل زیر است (ویژگی AddInContract آن نیز نباید فراموش شود):

using System.AddIn.Pipeline;
using System.AddIn.Contract;

namespace CalculatorConract
{
[AddInContract]
public interface ICalculatorContract : IContract
{
double Operate(string operation, double a, double b);
}
}

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

ایجاد pipeline

اگر قسمت قبل را مطالعه کرده باشید، یک راه حل مبتنی بر MAF از 7 پروژه تشکیل می‌شود که عمده‌ترین خاصیت آن‌ها مقاوم کردن سیستم در مقابل تغییرات نگارش قرارداد است. در این حالت اگر قرار داد تغییر کند، نه هاست و نه افزونه‌ی قدیمی، نیازی به تغییر در کدهای خود نخواهند داشت و این پروژه‌های میانی هستند که کار وفق دادن (adapters) نهایی را برعهده می‌گیرند.


برای ایجاد خودکار View ها و همچنین Adapters ، از افزونه‌ی Visual Studio Pipeline Builder که پیشتر معرفی شد استفاده خواهیم کرد.



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


پس از ایجاد این پروژه‌ها، نیاز به اصلاحات مختصری در مورد نام اسمبلی و فضای نام هر کدام می‌باشد؛ زیرا به صورت پیش فرض هر کدام به نام template نامگذاری شده‌اند:



پیاده سازی افزونه

قالب کاری استفاده از این فریم ورک آماده است. اکنون نوبت به پیاده سازی یک افزونه می‌باشد. به پروژه AddIn مراجعه کرده و ارجاعی را به اسمبلی AddInView خواهیم افزود. به این صورت افزونه‌ی ما به صورت مستقیم با قرارداد سروکار نداشته و ارتباطات، در راستای همان pipeline تعریف شده، جهت مقاوم شدن در برابر تغییرات صورت می‌گیرد:
using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
throw new NotImplementedException();
}
}
}

در اینجا افزونه‌ی ما باید اینترفیس ICalculator مربوط به AddInView را پیاده سازی نماید که برای مثال خواهیم داشت:

using System;
using CalculatorConract.AddInViews;
using System.AddIn;

namespace CalculatorAddIn
{
[AddIn("افزونه یک", Description = "توضیحات", Publisher = "نویسنده", Version = "نگارش یک")]
public class MyCalculatorAddIn : ICalculator
{
public double Operate(string operation, double a, double b)
{
switch (operation)
{
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
default:
throw new NotSupportedException("عملیات مورد نظر توسط این افزونه پشتیبانی نمی‌شود");
}
}
}
}

همانطور که در قسمت قبل نیز ذکر شد، این کلاس باید با ویژگی AddIn مزین شود که توسط آن می‌توان توضیحاتی در مورد نام ، نویسنده و نگارش افزونه ارائه داد.


استفاده از افزونه‌ی تولید شده

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

نکته‌ی مهم!
در هر دو ارجاع به HostView و یا AddInView باید خاصیت Copy to local به false تنظیم شود، در غیر اینصورت افزونه‌ی شما بارگذاری نخواهد شد.



پس از افزودن ارجاعی به HostView، نمونه‌ای از استفاده از افزونه‌ی تولید شده به صورت زیر می‌تواند باشد که توضیحات مربوطه به صورت کامنت آورده شده است:

using System;
using System.AddIn.Hosting;
using CalculatorConract.HostViews;

namespace Calculator
{
class Program
{
private static ICalculator _calculator;

static void doOperation()
{
Console.WriteLine("1+2: {0}", _calculator.Operate("+", 1, 2));
}

static void Main(string[] args)
{
//مسیر پوشه ریشه مربوطه به خط لوله افزونه‌ها
string path = Environment.CurrentDirectory;

//مشخص سازی مسیر خواندن و کش کردن افزونه‌ها
AddInStore.Update(path);

//یافتن افزونه‌هایی سازگار با شرایط قرارداد پروژه
//در اینجا هیچ افزونه‌ای بارگذاری نمی‌شود
var addIns = AddInStore.FindAddIns(typeof(ICalculator), path);

//اگر افزونه‌ای یافت شد
if (addIns.Count > 0)
{
var addIn = addIns[0]; //استفاده از اولین افزونه
Console.WriteLine("1st addIn: {0}", addIn.Name);

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
_calculator = addIn.Activate<ICalculator>(AddInSecurityLevel.Intranet);

//یک نمونه از استفاده آن
doOperation();
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

چند نکته جالب توجه در مورد قابلیت‌های ارائه شده:
- مدیریت load و unload پویا
- امکان تعریف سطح دسترسی و ویژگی‌های امنیتی اجرای یک افزونه
- امکان ایزوله سازی پروسه اجرای افزونه از هاست (در ادامه توضیح داده خواهد شد)
- مقاوم بودن پروژه به نگارش‌های مختلف قرارداد


اجرای افزونه در یک پروسه مجزا

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

//فعال سازی افزونه و همچنین مشخص سازی سطح دسترسی آن
//همچنین جدا سازی پروسه اجرایی افزونه از هاست
_calculator = addIn.Activate<ICalculator>(
new AddInProcess(),
AddInSecurityLevel.Intranet);

در این حالت اگر پس از فعال شدن افزونه، یک break point قرار دهیم و به task manager ویندوز مراجعه نمائیم، پروسه‌ی مجزای افزونه قابل مشاهده است.



برای مطالعه بیشتر + ، + ، + و +

مطالب
دادن «حق فراموش شدن» به کاربران در ASP.NET Core Identity 2.1
برنامه‌هایی که بخواهند سازگار با GDPR باشند، باید اصل 17 ام آن را که حق فراموش شدن است «Right to erasure ('right to be forgotten') »، پیاده سازی کنند؛ به علاوه اشخاص باید بتوانند اطلاعات شخصی خودشان را نیز بدون درنگ از آن سایت دریافت کنند. به عبارتی اگر شخصی در سایت شما ثبت نام کرد، باید قسمتی را هم در آن درنظر بگیرید که کاربر با فشردن یک کلیک، بتواند اکانت فعلی خودش را برای همیشه محو و نابود کند؛ بدون اینکه اثری از آن باقی بماند. همچنین پیش از این عمل نیز باید بتواند با کلیک بر روی یک دکمه، کلیه اطلاعات شخصی خودش را دانلود و ذخیره کند. از زمان ASP.NET Core Identity 2.1، هر دو مورد جزئی از این فریم ورک شده‌اند:



روش پیاده سازی دریافت اطلاعات شخصی در ASP.NET Core Identity 2.1

اگر به IdentityUser نگارش‌های اخیر ASP.NET Core Identity دقت کنید، شامل ویژگی جدید PersonalData نیز هست:
public class IdentityUser<TKey> where TKey : IEquatable<TKey>
{
   [PersonalData]
   public virtual TKey Id { get; set; }
PersonalData یک نشانه‌گذار است و هدف از آن، مشخص کردن خواصی است که شخص می‌تواند درخواست دریافت اطلاعات مرتبط با آن‌ها را بدهد.
سپس زمانیکه در تصویر فوق بر روی دکمه‌ی download کلیک کرد، ابتدا توسط userManager.GetUserAsync، اطلاعات کاربر جاری از بانک اطلاعاتی دریافت شده و سپس حلقه‌ای بر روی تمام خواص شیء IdentityUser تشکیل می‌شود. در اینجا هر کدام که مزین به PersonalDataAttribute بود، مقدارش دریافت شده و به دیکشنری personalData اضافه می‌شود.
var personalData = new Dictionary<string, string>();
var personalDataProps = typeof(TUser).GetProperties().Where(
    prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
foreach (var p in personalDataProps)
{
    personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
در آخر این اطلاعات با فرمت JSON، جهت دریافت به کاربر ارائه خواهد شد:
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json");


روش پیاده سازی دادن «حق فراموش شدن» به کاربران

اگر به تصویر ارسال شده دقت کنید، یک دکمه‌ی Delete نیز در آن قرار دارد. کار آن، حذف واقعی و فیزیکی کاربر جاری و تمام اطلاعات وابسته‌ی به او از بانک اطلاعاتی است. این دکمه نیز بدون هیچ واسطی در اختیار خود کاربر قرار گرفته‌است. یعنی به این صورت نیست که ابتدا فرمی را پر کند و سپس شخص دیگری اکانت او را حذف کند.
روش پیاده سازی آن نیز به صورت زیر است:
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
    return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
    if (!await _userManager.CheckPasswordAsync(user, Input.Password))
    {
        ModelState.AddModelError(string.Empty, "Password not correct.");
        return Page();
    }
}
برای اینکه کاربر بتواند درخواست حذف اکانت خود را صادر کند، باید ابتدا کلمه‌ی عبور خود را نیز وارد کند. همانطور که مشاهده می‌کنید توسط متد CheckPasswordAsync سرویس userManager، کار صحت کلمه‌ی عبور درخواستی بررسی می‌شود. سپس سطر زیر، اکانت او را حذف می‌کند:
var result = await _userManager.DeleteAsync(user);
موجودیت کاربر با سایر موجودیت‌های دیگری که در ارتباط است، به صورت OnDelete(DeleteBehavior.Cascade) تنظیم شده‌است. یعنی اگر این کاربر حذف شود، تمام اطلاعات او در سایر جداول وابسته نیز به صورت خودکار حذف خواهند شد.
و در آخر، کوکی جاری شخص لاگین شده را نیز حذف می‌کند تا برای همیشه با او خداحافظی کند!
await _signInManager.SignOutAsync();

 

پ.ن.
ما در این سایت هیچگاه شما را فراموش نخواهیم کرد!
نظرات مطالب
تعریف قالب‌های جداول سفارشی و کار با منابع داده‌ای از نوع Anonymous در PdfReport
این کتابخانه وابسته به MVC یا WinForms و امثال آن نیست. بر اساس دیتاسورس شما کار می‌کند و سایر تنظیماتی که با کدنویسی مشخص می‌کنید.
یکبار یک قالب کلی برای آن تهیه کنید. سپس از روش تولید پویای ستون‌ها استفاده کنید:
الف) تولید پویای ستون‌ها در حالت استفاده از SQL خام
ب) تولید پویای ستون‌ها در حالت استفاده از ORMها
مطالب
استفاده از توابع Scalar بجای case
گاهی از اوقات نیاز است در کوئری‌ها از بین چندین مقدار یکی انتخاب و بجای مقدار اصلی، رشته یا عبارتی جایگزین، نوشته شود. پر استفاده‌ترین راه حل پیشنهادی، استفاده از عبارت case در داخل کوئری هست که بر اساس موارد ممکن، عبارتهای برگشتی نوشته می‌شود. این راه حل خوبی به نظر می‌رسد اما اگر تعداد گزینه‌ها زیاد شود باعث شلوغ شدن متن کوئری و اشکال در بازبینی و نگهداری آن خواهد شد.
یک راه حل دیگر استفاده از توابع نوع Scalar می‌باشد؛ به این صورت که میتوان مقدار استخراج شده از جدول را به تابع تعریف شده فرستاد و در ازاء، مقدار بازگشتی مناسبی را در خروجی مشاهده کرد. حال به یک مثال توجه کنید:
Select Case Gen when 0 then 'مرد' when 1 then 'زن' end As Gen From Table
اکنون استفاده از تابع:
CREATE FUNCTION fcGenName
(
@Gen tinyint
)
RETURNS nvarchar(20)
AS
BEGIN
-- Declare the return variable here
DECLARE @gen nvarchar(20)

-- Add the T-SQL statements to compute the return value here
set @gen = (SELECT case @Gen when 0 then 'مرد' when 1 then 'زن' end as d)

-- Return the result of the function
RETURN @gen

END

و فراخوانی تابع در متن کوئری :

Select fcGenName(Gen) From Table

نظرات مطالب
مقایسه بین حلقه های تکرار (Lambda ForEach و for و foreach)
"این آزمایشات رو اگر در هر سیستم دیگر با هر Config اجرا کنید نتیجه کلی تغییر نخواهد کرد و فقط از نظر زمان اجرا تفاوت خواهیم داشت نه در نتیجه کلی." 
این مطلب لزوما صحیح نیست. یک بنچمارک میتونه تو مجموعه سخت افزارهای مختلف، نتایج کاملا متفاوتی داشته باشه. مثلا سوالی در همین زمینه آقای شهروز جعفری تو StackOverflow پرسیدن که در جوابش دو نفر نتایج متفاوتی ارائه دادن.
معمولا برای بیان نتایج تستهای بنچمارک ابتدا مشخصات سخت افزاری ارائه میشه مخصوصا وقتیکه نتایج دقیق (و نه کلی) نشون داده میشه. مثل همین نتایج دقیق زمانهای اجرای حلقه‌ها.
نکته ای که من درکامنتم اشاره کردم صرفا درباره تست "سرعت اجرای" انواع حلقه‌ها بود که ممکنه با تست کارایی حلقه‌ها در اجرای یک کد خاص فرق داشته باشه.
نکته دیگه هم اینکه نمیدونم که آیا شما از همون متد Console.WriteLine در حلقه‌ها برای اجرای تستون استفاده کردین یا نه. فقط باید بگم که به خاطر مسائل و مشکلات مختلفی که استفاده از این متد به همراه داره، به نظر من بکارگیری اون تو این جور تست‌ها اصلا مناسب نیست و باعث دور شدن زیاد نتایج از واقعیت میشه. مثلا من تست کردم و هر دفعه یه نتیجه‌ای می‌داد که نمیشه بر اساس اون نتیجه‌گیری کرد. 

مورد دیگه ای هم که باید اضافه کنم اینه که بهتر بود شما کد کامل تست خودتون رو هم برای دانلود میذاشتین تا دیگران هم بتونن استفاده کنن. اینجوری خیلی بهتر میشه نتایج مختلف رو با هم مقایسه کرد. این مسئله برای تستای بنچمارک نسبتا رایج هست. مثل کد زیر که من آماده کردم:
static void Main(string[] args)
{
  //Action<int> func = Console.WriteLine;
  Action<int> func = number => number++;
  do
  {
    try
    {
      Console.Write("Iteration: ");
      var iterations = Convert.ToInt32(Console.ReadLine());
      Console.Write("Loop Type (for:0, foreach:1, List.ForEach:2, Array.ForEach:3): ");
      var loopType = Console.ReadLine();
      switch (loopType)
      {
        case "0":
          Console.WriteLine("FOR loop test for {0} iterations", iterations.ToString("0,0"));
          TestFor(iterations, func);
          break;
        case "1":
          Console.WriteLine("FOREACH loop test for {0} iterations", iterations.ToString("0,0"));
          TestForEach(iterations, func);
          break;
        case "2":
          Console.WriteLine("LIST.FOREACH test for {0} iterations", iterations.ToString("0,0"));
          TestListForEach(iterations, func);
          break;
        case "3":
          Console.WriteLine("ARRAY.FOREACH test for {0} iterations", iterations.ToString("0,0"));
          TestArrayForEach(iterations, func);
          break;
      }
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex);
    }
    Console.Write("Continue?(Y/N)");
    Console.WriteLine("");
  } while (Console.ReadKey(true).Key != ConsoleKey.N);

  Console.WriteLine("Press any key to exit");
  Console.ReadKey();
}


static void TestFor(int iterations, Action<int> func)
{
  StartupTest(func);

  var watch = Stopwatch.StartNew();
  for (int i = 0; i < iterations; i++)
  {
    func(i);
  }
  watch.Stop();
  ShowResults("for loop test: ", watch);
}

static void TestForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var list = Enumerable.Range(0, iterations);

  var watch = Stopwatch.StartNew();
  foreach (var item in list)
  {
    func(item);
  }
  watch.Stop();
  ShowResults("foreach loop test: ", watch);
}

static void TestListForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var list = Enumerable.Range(0, iterations).ToList();

  var watch = Stopwatch.StartNew();
  list.ForEach(func);
  watch.Stop();
  ShowResults("list.ForEach test: ", watch);
}

static void TestArrayForEach(int iterations, Action<int> func)
{
  StartupTest(func);
  var array = Enumerable.Range(0, iterations).ToArray();

  var watch = Stopwatch.StartNew();
  Array.ForEach(array, func);
  watch.Stop();
  ShowResults("Array.ForEach test: ", watch);
}

static void StartupTest(Action<int> func)
{
  // clean up
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();

  // warm up
  func(0);
}
static void ShowResults(string description, Stopwatch watch)
{
  Console.Write(description);
  Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
قبل از اجرای تست بهتره برنامه رو برای نسخه Release بیلد کنیم. ساده‌ترین روشش در تصویر زیر نشون داده شده:

پس از این تغییر و بیلد پروژه نتایج رو مقایسه میکنیم. نتایج اجرای این تست در همون سیستمی که قبلا تستای StringBuilder و Microbenchmark رو انجام دادم (یعنی لپ تاپ msi GE 620 با Core i7-2630QM) بصورت زیر:

البته نتایج این تستها مطلق نیستن. نکاتی که در کامنت قبلی اشاره کردم از عوامل تاثیرگذار هستن.
موفق باشین.
نظرات مطالب
پایان پروژه ASP.NET Ajax Control Toolkit !
سلام.

درست مثل linq to sql، که اولش تبلیغ می کرد از َADO.NET بیاین سمت Linq to SQL و بعدش گفت از Linq to SQL بیاین به سمت ADO.net Entity
MFC هم همینطور محجور مانده،ASP کلاسیک

بدی محصولات ماکروسافت این هست که opensource نیست که توسط third party همیشه پشتیبانی و به مرور بهنگام بشه و خوبیش هم این هست که یک فناوری خوش دست و راحتی میده.

مهندس نصیری، نظرت درباره جاوا چی هست؟، پستی را برای مقایسه این دو می نویسید؟
مطالب دوره‌ها
مدیریت استثناءها در حین استفاده از واژه‌های کلیدی async و await
زمانیکه یک متد async، یک Task یا Task of T (نسخه‌ی جنریک Task) را باز می‌گرداند، کامپایلر سی‌شارپ به صورت خودکار تمام استثناءهای رخ داده درون متد را دریافت کرده و از آن برای تغییر حالت Task به اصطلاحا faulted state استفاده می‌کند. همچنین زمانیکه از واژه‌ی کلیدی await استفاده می‌شود، کدهایی که توسط کامپایلر تولید می‌شوند، عملا مباحث Continue موجود در TPL یا Task parallel library معرفی شده در دات نت 4 را پیاده سازی می‌کنند و نهایتا نتیجه‌ی Task را در صورت وجود، دریافت می‌کند. زمانیکه نتیجه‌ی یک Task مورد استفاده قرار می‌گیرد، اگر استثنایی وجود داشته باشد، مجددا صادر خواهد شد. برای مثال اگر خروجی یک متد async از نوع Task of T باشد، امکان استفاده از خاصیتی به نام Result نیز برای دسترسی به نتیجه‌ی آن وجود دارد:
using System.Threading.Tasks;

namespace Async05
{
    class Program
    {
        static void Main(string[] args)
        {
            var res = doSomethingAsync().Result;
        }

        static async Task<int> doSomethingAsync()
        {
            await Task.Delay(1);
            return 1;
        }
    }
}
در این مثال یکی از روش‌های استفاده از متدهای async را در یک برنامه‌ی کنسول مشاهده می‌کنید. هر چند خروجی متد doSomethingAsync از نوع Task of int است، اما مستقیما یک int بازگشت داده شده است. تبدیلات نهایی در اینجا توسط کامپایلر انجام می‌شود. همچنین نحوه‌ی استفاده از خاصیت Result را نیز در متد Main مشاهده می‌کنید.
البته باید دقت داشت، زمانیکه از خاصیت Result استفاده می‌شود، این متد همزمان عمل خواهد کرد و نه غیرهمزمان (ترد جاری را بلاک می‌کند؛ یکی از موارد مجاز استفاده از آن در متد Main برنامه‌های کنسول است). همچنین اگر در متد doSomethingAsync استثنایی رخ داده باشد، این استثناء زمان استفاده از Result، به صورت یک AggregateException مجددا صادر خواهد شد. وجود کلمه‌ی Aggregate در اینجا به علت امکان استفاده‌ی تجمعی و ترکیب چندین Task باهم و داشتن چندین شکست و استثنای ممکن است.
همچنین اگر از کلمه‌ی کلیدی await بر روی یک faulted task استفاده کنیم، AggregateException صادر نمی‌شود. در این حالت کامپایلر AggregateException را بررسی کرده و آن‌را تبدیل به یک Exception متداول و معمول کدهای دات نت می‌کند. به عبارتی سعی شده‌است در این حالت، رفتار کدهای async را شبیه به رفتار کدهای متداول همزمان شبیه سازی کنند.


یک مثال

در اینجا توسط متد getTitleAsync، اطلاعات یک صفحه‌ی وب به صورت async دریافت شده و سپس عنوان آن استخراج می‌شود. در متد showTitlesAsync نیز از آن استفاده شده و در طی یک حلقه، چندین وب سایت مورد بررسی قرار خواهند گرفت. چون متد getTitleAsync از نوع async تعریف شده‌است، فراخوان آن نیز باید async تعریف شود تا بتوان از واژه‌ی کلیدی  await برای کار با آن استفاده کرد.
نهایتا در متد Main برنامه، وظیفه‌ی غیرهمزمان showTitlesAsync اجرا شده و تا پایان عملیات آن صبر می‌شود. چون خروجی آن از نوع Task است و نه Task of T، در اینجا دیگر خاصیت Result قابل دسترسی نیست. متد Wait نیز ترد جاری را همانند خاصیت Result بلاک می‌کند.
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Async05
{
    class Program
    {
        static void Main(string[] args)
        {
            var task = showTitlesAsync(new[]
            {
                "http://www.google.com",
                "https://www.dntips.ir"
            });
            task.Wait();

            Console.WriteLine();
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }

        static async Task showTitlesAsync(IEnumerable<string> urls)
        {
            foreach (var url in urls)
            {
                var title = await getTitleAsync(url);
                Console.WriteLine(title);
            }
        }

        static async Task<string> getTitleAsync(string url)
        {
            var data = await new WebClient().DownloadStringTaskAsync(url);
            return getTitle(data);
        }

        private static string getTitle(string data)
        {
            const string patternTitle = @"(?s)<title>(.+?)</title>";
            var regex = new Regex(patternTitle);
            var mc = regex.Match(data);
            return mc.Groups.Count == 2 ? mc.Groups[1].Value.Trim() : string.Empty;
        }
    }
}
کلیه عملیات مبتنی برشبکه، همیشه مستعد به بروز خطا هستند. قطعی ارتباط یا حتی کندی آن می‌توانند سبب بروز استثناء شوند.
برنامه را در حالت عدم اتصال به اینترنت اجرا کنید. استثنای صادر شده، در متد task.Wait ظاهر می‌شود (چون متدهای async ترد جاری را خالی کرده‌اند):


و اگر در اینجا بر روی لینک View details کلیک کنیم، در inner exception حاصل، خطای واقعی قابل مشاهده است:


همانطور که ملاحظه می‌کنید، استثنای صادر شده از نوع System.AggregateException است. به این معنا که می‌تواند حاوی چندین استثناء باشد که در اینجا تعداد آن‌ها با عدد یک مشخص شده‌است. بنابراین در این حالات، بررسی inner exception را فراموش نکنید.

در ادامه داخل حلقه‌ی foreach متد showTitlesAsync، یک try/catch قرار می‌دهیم:
        static async Task showTitlesAsync(IEnumerable<string> urls)
        {
            foreach (var url in urls)
            {
                try
                {
                    var title = await getTitleAsync(url);
                    Console.WriteLine(title);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
        }
اینبار اگر برنامه را اجرا کنیم، خروجی ذیل را در صفحه می‌توان مشاهده کرد:
 System.Net.WebException: The remote server returned an error: (502) Bad Gateway.
System.Net.WebException: The remote server returned an error: (502) Bad Gateway.

Press any key to exit...
در اینجا دیگر خبری از AggregateException نبوده و استثنای واقعی رخ داده در متد await شده بازگشت داده شده‌است. کار واژه‌ی کلیدی await در اینجا، بررسی استثنای رخ داده در متد async فراخوانی شده و بازگشت آن به جریان متداول متد جاری است؛ تا نتیجه‌ی عملیات همانند یک کد کامل همزمان به نظر برسد. به این ترتیب کامپایلر توانسته است رفتار بروز استثناءها را در کدهای همزمان و غیرهمزمان یک دست کند. دقیقا مانند حالتی که یک متد معمولی در این بین فراخوانی شده و استثنایی در آن رخ داده‌است.


مدیریت تمام inner exceptionهای رخ داده در پردازش‌های موازی

همانطور که عنوان شد، await تنها یک استثنای حاصل از Task در حال اجرا را به کد فراخوان بازگشت می‌دهد. در این حالت اگر این Task، چندین شکست را گزارش دهد، چطور باید برای دریافت تمام آن‌ها اقدام کرد؟ برای مثال استفاده از Task.WhenAll می‌تواند شامل چندین استثنای حاصل از چندین Task باشد، ولی await تنها اولین استثنای دریافتی را بازگشت می‌دهد. اما اگر از خاصیتی مانند Result یا متد Wait استفاده شود، یک AggregateException حاصل تمام استثناءها را دریافت خواهیم کرد. بنابراین هرچند await تنها اولین استثنای دریافتی را بازگشت می‌دهد، اما می‌توان به Taskهای مرتبط مراجعه کرد و سپس بررسی نمود که آیا استثناهای دیگری نیز وجود دارند یا خیر؟
برای نمونه در مثال فوق، حلقه‌ی foreach تشکیل شده آنچنان بهینه نیست. از این جهت که هر بار تنها یک سایت را بررسی می‌کند، بجای اینکه مانند مرورگرها چندین ترد را به یک یا چند سایت باز کرده و نتایج را دریافت کند.
البته انجام کارها به صورت موازی همیشه ایده‌ی خوبی نیست ولی حداقل در این حالت خاص که با یک یا چند سرور راه دور کار می‌کنیم، درخواست‌های همزمان دریافت اطلاعات، سبب کارآیی بهتر برنامه و بالا رفتن سرعت اجرای آن می‌شوند. اما مثلا در حالتیکه با سخت دیسک سیستم کار می‌کنیم، اجرای موازی کارها نه تنها کمکی نخواهد کرد، بلکه سبب خواهد شد تا مدام drive head در مکان‌های مختلفی مشغول به حرکت شده و در نتیجه کارآیی آن کاهش یابد.
برای ترکیب چندین Task، ویژگی خاصی به زبان سی‌شارپ اضافه نشده‌، زیرا نیازی نبوده است. برای این حالت تنها کافی است از متد Task.WhenAll، برای ساخت یک Task مرکب استفاده کرد. سپس می‌توان واژه‌ی کلیدی await را بر روی این Task مرکب فراخوانی کرد.
همچنین می‌توان از متد ContinueWith یک Task مرکب نیز برای جلوگیری از بازگشت صرفا اولین استثنای رخ داده توسط کامپایلر، استفاده کرد. در این حالت امکان دسترسی به خاصیت Result آن به سادگی میسر می‌شود که حاوی AggregateException کاملی است.


اعتبارسنجی آرگومان‌های ارسالی به یک متد async

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


از دست دادن استثناءها

فرض کنید مانند مثال قسمت قبل، دو وظیفه‌ی async آغاز شده و نتیجه‌ی آن‌ها پس از await هر یک، با هم جمع زده می‌شوند. در این حالت اگر کل عملیات را داخل یک قطعه کد try/catch قرار دهیم، اولین await ایی که یک استثناء را صادر کند، صرفنظر از وضعیت await دوم، سبب اجرای بدنه‌ی catch می‌شود. همچنین انجام این عملیات بدین شکل بهینه نیست. زیرا ابتدا باید صبر کرد تا اولین Task تمام شود و سپس دومین Task شروع گردد و به این ترتیب پردازش موازی Taskها را از دست خواهیم داد. در یک چنین حالتی بهتر است از متد await Task.WhenAll استفاده شود. در اینجا دو Task مورد نیاز، تبدیل به یک Task مرکب می‌شوند. این Task مرکب تنها زمانی خاتمه می‌یابد که هر دوی Task اضافه شده به آن، خاتمه یافته باشند. به این ترتیب علاوه بر اجرای موازی Taskها، امکان دریافت استثناءهای هر کدام را نیز به صورت تجمعی خواهیم داشت.
مشکل! همانطور که پیشتر نیز عنوان شد، استفاده از await در اینجا سبب می‌شود تا کامپایلر تنها اولین استثنای دریافتی را بازگشت دهد و نه یک AggregateException نهایی را. روش حل آن‌را نیز عنوان کردیم. در این حالت بهتر است از متد ContinueWith و سپس استفاده از خاصیت Result آن برای دریافت کلیه استثناءها کمک گرفت.
حالت دوم از دست دادن استثناءها زمانی‌است که یک متد async void را ایجاد می‌کنید. در این حالات بهتر است از یک Task بجای بازگشت void استفاده شود. تنها علت وجودی async voidها، استفاده از آن‌ها در روال‌های رویدادگردان UI است (در سایر حالات code smell درنظر گرفته می‌شود).
public async Task<double> GetSum2Async()
        {
            try
            {
                var task1 = GetNumberAsync();
                var task2 = GetNumberAsync();

                var compositeTask = Task.WhenAll(task1, task2);
                await compositeTask.ContinueWith(x => { });

                return compositeTask.Result[0] + compositeTask.Result[1];
            }
            catch (Exception ex)
            {
                //todo: log ex
                throw;
            }
        }
در مثال فوق، نحوه‌ی ترکیب دو Task را توسط Task.WhenAll جهت اجرای موازی و سپس اعمال نکته‌ی یک ContinueWith خالی و در ادامه استفاده از Result نهایی را جهت دریافت تمامی استثناءهای حاصل، مشاهده می‌کنید.
در این مثال دیگر مانند مثال قسمت قبل
        public async Task<double> GetSumAsync()
        {
            var leftOperand = await GetNumberAsync();
            var rightOperand = await GetNumberAsync();

            return leftOperand + rightOperand;
        }
هر بار صبر نشده‌است تا یک Task تمام شود و سپس Task بعدی شروع گردد.
با کمک متد Task.WhenAll ترکیب آن‌ها ایجاد و سپس با فراخوانی await، سبب اجرای موازی چندین Task با هم شده‌ایم.


مدیریت خطاهای مدیریت نشده

ابتدا مثال زیر را در نظر بگیرید:
using System;
using System.Threading.Tasks;

namespace Async01
{
    class Program
    {
        static void Main(string[] args)
        {
            Test2();
            Test();
            Console.ReadLine();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

        public static async Task Test()
        {
            throw new Exception();
        }

        public static async void Test2()
        {
            throw new Exception();
        }
    }
}
در این مثال دو متد که یکی async Task و دیگری async void است، تعریف شده‌اند.
اگر برنامه را کامپایل کنید، کامپایلر بر روی سطر فراخوانی متد Test اخطار زیر را صادر می‌کند. البته برنامه بدون مشکل کامپایل خواهد شد.
 Warning  1  Because this call is not awaited, execution of the current method continues before the call is completed.
Consider applying the 'await' operator to the result of the call.
اما چنین اخطاری در مورد async void صادر نمی‌شود. بنابراین ممکن است جایی در کدها، فراخوانی await فراموش شود. اگر خروجی متد شما ازنوع Task و مشتقات آن باشد، کامپایلر حتما اخطاری را جهت رفع آن گوشزد خواهد کرد؛ اما نه در مورد متدهای void که صرفا جهت کاربردهای UI و روال‌های رخدادگردان آن طراحی شده‌اند.
همچنین اگر برنامه را اجرا کنید استثنای صادر شده در متد async void سبب کرش برنامه می‌شود؛ اما نه استثنای صادر شده در متد async Task. متدهای async void چون دارای Synchronization Context نیستند، استثنای صادره را به Thread pool برنامه صادر می‌کنند. به همین جهت در همان لحظه نیز سبب کرش برنامه خواهند شد. اما در حالت async Task به این نوع استثناءها اصطلاحا Unobserved Task Exception گفته شده و سبب بروز  faulted state در Task تعریف شده می‌گردند.
برای مدیریت آن‌ها در سطح برنامه باید در ابتدای کار و در متد Main، توسط TaskScheduler.UnobservedTaskException روال رخدادگردانی را برای مدیریت اینگونه استثناءها تدارک دید. زمانیکه GC شروع به آزاد سازی منابع می‌کند، این استثناءها نیز درنظر گرفته شده و سبب کرش برنامه خواهند شد. با استفاده از متد SetObserved همانند قطعه کد زیر، می‌توان از کرش برنامه جلوگیری کرد:
using System;
using System.Threading.Tasks;

namespace Async01
{
    class Program
    {
        static void Main(string[] args)
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

            //Test2();
            Test();
            Console.ReadLine();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

        private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            e.SetObserved();
            Console.WriteLine(e.Exception);
        }

        public static async Task Test()
        {
            throw new Exception();
        }

        public static async void Test2()
        {
            throw new Exception();
        }
    }
}
البته لازم به ذکر است که این رفتار در دات نت 4.5 به این شکل تغییر کرده است تا کار با متدهای async ساده‌تر شود. در دات نت 4، یک چنین استثناءهای مدیریت نشده‌ای،‌بلافاصله سبب بروز استثناء و کرش برنامه می‌شدند.
به عبارتی رفتار قطعه کد زیر در دات نت 4 و 4.5 متفاوت است:
Task.Factory.StartNew(() => { throw new Exception(); });

Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
در دات نت 4  اگر این برنامه را خارج از VS.NET اجرا کنیم، برنامه کرش می‌کند؛ اما در دات نت 4.5 خیر و آن‌ها به UnobservedTaskException یاد شده هدایت خواهند شد. اگر می‌خواهید این رفتار را به همان حالت دات نت 4 تغییر دهید، تنظیم زیر را به فایل config برنامه اضافه کنید:
 <configuration>
    <runtime>
      <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>


یک نکته‌ی تکمیلی: ممکن است عبارات lambda مورد استفاده، از نوع async void باشد.

همانطور که عنوان شد باید از async void منهای مواردی که کار مدیریت رویدادهای عناصر UI را انجام می‌دهند (مانند برنامه‌های ویندوز 8)، اجتناب کرد. چون پایان کار آن‌ها را نمی‌توان تشخیص داد و همچنین کامپایلر نیز اخطاری را در مورد استفاده ناصحیح از آن‌ها بدون await تولید نمی‌کند (چون نوع void اصطلاحا awaitable نیست). به علاوه بروز استثناء در آن‌ها، بلافاصله سبب خاتمه برنامه می‌شود. بنابراین اگر جایی در برنامه متد async void وجود دارد، قرار دادن try/catch داخل بدنه‌ی آن ضروری است.
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    try
    {
        ClickMeButton.Tapped += async (sender, args) =>
        {
             throw new Exception();        

        };
    }
    catch (Exception ex)
    {
        // This won’t catch exceptions!
        TextBlock1.Text = ex.Message;
    }
}
در این مثال خاص ویندوز 8، شاید به نظر برسد که try/catch تعریف شده سبب مهار استثنای صادر شده می‌شود؛ اما خیر!
 public delegate void TappedEventHandler(object sender, TappedRoutedEventArgs e);
امضای متد TappedEventHandler از نوع delegate void است. بنابراین try/catch را باید داخل بدنه‌ی روال رویدادگردان تعریف شده قرار داد و نه خارج از آن.
مطالب
خلاصه‌ای در مورد SQL Server CE

SQL Server CE برای اولین بار جهت استفاده در SmartPhones طراحی شد؛ جزو خانواده‌ی Embedded databases قرار می‌گیرد و این مزایا را دارد:
- نیازی به نصب ندارد و از چند DLL تشکیل شده است (برای مثال جهت استفاده در کارهای تک کاربره‌ی قابل حمل ایده‌آل است).
- رایگان است (جهت استفاده در کارهای تجاری و غیرتجاری).
- حجم کمی دارد (جمعا کمتر از دو مگابایت).
- پروایدر ADO.NET آن موجود است (توسط فضای نام System.Data.SqlServerCe که به کمک اسمبلی System.Data.SqlServerCe.dll قرار گرفته در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Desktop ارائه می‌شود).
- با کمک ORM هایی مانند Entity framework و یا NHibernate نیز می‌توان با آن کار کرد.
- نسخه‌ی 4 نهایی آن که قرار است در زمان ارائه‌ی SP1 مربوط به VS.NET 2010 ارائه شود، جهت استفاده در برنامه‌های ASP.NET (برنامه‌های چند کاربره) ایی که تعداد کاربر کمی دارند، بهینه سازی شده و این مورد یک مزیت مهم نسبت به SQLite است که اساسا با تردهای همزمان جهت کار با بانک اطلاعاتی مشکل دارد.
- امکان گذاشتن کلمه‌ی عبور بر روی بانک اطلاعاتی آن وجود دارد که سبب رمزنگاری خودکار آن نیز خواهد شد (این مورد به صورت پیش فرض در SQLite پیش بینی نشده و جزو مواردی که است که باید برای آن هزینه کرد). الگوریتم رمزنگاری آن به صورت رسمی معرفی نشده، ولی به احتمال زیاد AES می‌باشد.
- از ADO.NET Sync Framework پشتیبانی می‌کند.

ملاحظات:
- به آن می‌توان به صورت نسخه‌ی تعدیل شده‌ی SQL Server 2000 با توانایی‌های کاهش یافته نگاه کرد. در آن خبری از رویه‌های ذخیره شده، View ها ، Full text search ، CLR Procs، CLR Triggers و غیره نیست (سطح توقع را باید در حد همان 2 مگابایت پایین نگه داشت!). لیست کامل : (+)
- Management studio مربوط به SQL Server 2005 به هیچ عنوان از آن پشتیبانی نمی‌کند و تنها نسخه‌ی 2008 است که نگارش 3 و نیم آن‌را پشتیبانی می‌کند آن هم نه با توانایی‌هایی که جهت کار با SQL Server اصلی وجود دارد. مثلا امکان rename یک فیلد را ندارد و باید برای اینکار کوئری نوشت. خوشبختانه یک سری پروژه‌ی رایگان در سایت CodePlex این نقایص را پوشش داده‌اند؛ برای مثال : ExportSqlCe
- از آنجائیکه DLL های SQL CE از نوع Native هستند، باید دقت داشت که حین استفاده از آن‌ها در دات نت فریم ورک اگر platform target قسمت build برنامه بر روی ALL CPU تنظیم شده باشد، برنامه به احتمال زیاد در سیستم‌های 64 بیتی کرش خواهد کرد (اگر در حین توسعه برنامه از DLL‌های بومی 32 بیتی آن استفاده شده باشد). بنابراین نیاز است DLL های 64 بیتی را به صورت جداگانه جهت سیستم‌های 64 بیتی ارائه داد. اطلاعات بیشتر: (+) و (+) و (+)
- Entity framework یک سری از قابلیت‌های این بانک اطلاعاتی را پشتیبانی نمی‌کند. برای مثال اگر یک primary key از نوع identity را تعریف کردید، برنامه کار نخواهد کرد! لیست مواردی را که پشتیبانی نمی‌شوند، در این آدرس می‌توان مشاهده کرد.

و اخبار مرتبط با SQL CE را در این بلاگ می‌توانید دنبال کنید.

نظرات مطالب
استفاده از خواص راهبری در Entity framework بجای Join نویسی
معادل سؤال شما با توجه به مدل‌های فوق به صورت زیر است:
می‌خواهیم لیست افرادی را بدست بیاوریم که در شهر x متولد شده‌اند.
روش اول: اگر شماره شهر را داریم:
var cityId = 1;
var list = context.People.Where(x => x.BornInCityId == cityId).ToList();
روش دوم: اگر نام شهر را داریم:
var cityName = "city-1";
var list2 = context.People.Where(x => x.BornInCity.Name == cityName).ToList();
در روش اول از نکته تعریف کلید خارجی استفاده شده.
در روش دوم از نکته استفاده از خواص راهبری، استفاده شده.