مطالب
Angular CLI - قسمت پنجم - ساخت و توزیع برنامه
ساخت و توزیع برنامه‌های Angular یکی از مهم‌ترین و بحث برانگیزترین قسمت‌های نگارش‌های جدید آن است و به ازای هر پروژه و قالبی که برای آن توسط گروه‌های مختلف ارائه شده‌است، روش‌های متفاوتی را شاهد خواهید بود. در ادامه روش توصیه شده‌ی توسط تیم Angular را که مبتنی است بر webpack و به صورت خودکار توسط Angular CLI مدیریت می‌شود، بررسی خواهیم کرد.


ساخت (Build) برنامه‌های Angular

Angular CLI کار ساخت و کامپایل برنامه را به صورت خودکار انجام داده و خروجی را در مسیری مشخص درج می‌کند. در اینجا می‌توان گزینه‌هایی را بر اساس نوع کامپایل مدنظر مانند کامپایل برای حالت توسعه و یا کامپایل برای حالت توزیع نهایی، انتخاب کرد. همچنین مباحث bundling و یکی کردن تعداد بالای ماژول‌های برنامه در آن لحاظ می‌شوند تا برنامه در حالت توزیع نهایی، سبب 100ها رفت و برگشت به سرور برای دریافت ماژول‌های مختلف آن نشود. به علاوه مباحث uglification (به نوعی obfuscation کدهای جاوا اسکریپتی نهایی) و tree-shaking (حذف کدهایی که در برنامه استفاده نشده‌اند؛ یا کدهای مرده) نیز پیاده سازی می‌شوند. با انجام tree-shaking‌، نه تنها اندازه‌ی توزیع نهایی به کاربر کاهش پیدا می‌کند، بلکه مرورگر نیز حجم کمتری از کدهای جاوااسکریپتی را باید تفسیر کند.
برای شروع می‌توان از دستور ذیل برای مشاهده‌ی تمام گزینه‌های مهیای ساخت برنامه استفاده کرد:
> ng build --help
ذکر تنهای دستور ng build‌، بدون هیچ گزینه‌ای، برای حالت «توسعه‌ی» برنامه بسیار ایده‌آل است (و دقیقا به معنای صدور دستور ng build --dev است). در این حالت خروجی کامپایل شده‌ی برنامه در پوشه‌ی dist تولید می‌شود. اگر از قسمت دوم این سری به خاطر داشته باشید، نام این پوشه‌ی خروجی، جزئی از تنظیمات فایل angular-cli.json. است:
"apps": [
{
   "outDir": "dist",
زمانیکه دستور ng build‌  صادر شود، این فایل‌ها را در پوشه‌ی dist خواهید یافت:

فایل 
توضیح 
 inline.bundle.js   WebPack runtime
از آن برای بارگذاری ماژول‌های برنامه و چسباندن قسمت‌های مختلف به یکدیگر استفاده می‌شود. 
 main.bundle.js   شامل تمام کدهای ما است. 
 polyfills.bundle.js   Polyfills - جهت پشتیبانی از مرورگرهای مختلف.
 styles.bundle.js    شامل بسته بندی تمام شیوه نامه‌های برنامه است 
vendor.bundle.js  کدهای کتابخانه‌های ثالث مورد استفاده و همچنین خود Angular، در اینجا بسته بندی می‌شوند. 
 

روشی برای بررسی محتوای bundleهای تولید شده

تولید bundleها در جهت کاهش رفت و برگشت‌های به سرور و بالا بردن کارآیی برنامه ضروری هستند؛ اما دقیقا این بسته بندی‌ها شامل چه اطلاعاتی می‌شوند؟ این اطلاعات را می‌توان از فایل‌های source map تولیدی استخراج کرد و برای این منظور می‌توان از برنامه‌ی source-map-explorer استفاده کرد.

روش نصب عمومی آن:
 > npm install -g source-map-explorer
روش اجرا:
 > source-map-explorer dist/main.bundle.js
پس از آن یک گزارش HTML ایی از محتوای bundle مدنظر تولید می‌شود.


یک مثال: ساخت برنامه‌ی مثال قسمت چهارم - تنظیمات مسیریابی در حالت dev

در ادامه، کار Build همان مثالی را که در قسمت قبل توضیح داده شد، بررسی می‌کنیم. برای این منظور از طریق خط فرمان به ریشه‌ی پوشه‌ی اصلی پروژه وارد شده و دستور ng build را صادر کنید. یک چنین خروجی را مشاهده خواهید کرد:
 D:\Prog\angular-routing>ng build
Hash: 123cae8bd8e571f44c31
Time: 33862ms
chunk {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered]
chunk {1} main.bundle.js, main.bundle.js.map (main) 14.7 kB {3} [initial] [rendered]
chunk {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]
chunk {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.34 MB [initial] [rendered]
chunk {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
و اگر فایل index.html تولیدی آن‌را بررسی کنید، تنها الحاق همین 4 فایل js تولیدی را مشاهده می‌نمائید:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>AngularRouting</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading...</app-root>
<script type="text/javascript" src="inline.bundle.js">
</script><script type="text/javascript" src="polyfills.bundle.js">
</script><script type="text/javascript" src="styles.bundle.js">
</script><script type="text/javascript" src="vendor.bundle.js">
</script><script type="text/javascript" src="main.bundle.js"></script>
</body>
</html>

یک نکته: زمانیکه دستور ng serve -o صادر می‌شود، در پشت صحنه دقیقا همین دستور ng build صادر شده و اطلاعات را درون حافظه تشکیل می‌دهد. اما اگر کار ng build را دستی انجام دهیم، اینبار ng serve -o اطلاعات را از پوشه‌ی dist دریافت می‌کند. بنابراین در حین کار با ng serve -o نیازی به build دستی پروژه نیست.

سؤال: چرا حجم فایل endor.bundle.js اینقدر بالا است و شامل چه اجزایی می‌شود؟
نکته‌ای که در اینجا وجود دارد، حجم بالای فایل vendor.bundle.js آن است که 2.34 MB می‌باشد:


چون دستور ng build بدون پارامتری ذکر شده‌است، برنامه را برای حالت توسعه Build می‌کند و به همین جهت هیچگونه بهینه سازی در این مرحله صورت نخواهد گرفت. برای بررسی محتوای این فایل می‌توان دستور ذیل را در ریشه‌ی اصلی پروژه صادر کرد:
 > source-map-explorer dist/vendor.bundle.js
پس از اجرای این دستور، بلافاصله مرورگر پیش فرض سیستم اجرا شده و گزارشی را ارائه می‌دهد.


همانطور که مشاهده می‌کنید، در حالت بهینه سازی نشده و Build برای توسعه، کامپایلر Angular حدود 41 درصد حجم فایل vendor.bundle.js را تشکیل می‌دهد. به علاوه ماژول‌ها و قسمت‌هایی را ملاحظه می‌کنید که اساسا برنامه‌ی فعلی مثال ما از آن‌ها استفاده نمی‌کند؛ مانند http، فرم‌ها و غیره.


سفارشی سازی Build برای محیط‌های مختلف

اگر به پروژه‌ی تولید شده‌ی توسط Angular CLI دقت کنید، حاوی پوشه‌ای است به نام src\environments


هدف از فایل‌های environment برای نمونه تغییر آدرس توزیع برنامه در حالت توسعه و ارائه نهایی است.
همچنین در اینجا می‌توان نحوه‌ی بهینه سازی فایل‌های تولیدی را توسط Build Targets مشخص کرد و اینکار توسط ذکر پرچم prod-- (مخفف production) صورت می‌گیرد.
در ادامه، تفاوت‌های دستورهای ng build و ng build --prod را ملاحظه می‌کنید:
- با اجرای ng build، از فایل environment.ts استفاده می‌شود؛ برخلاف حالت اجرای ng build --prod که از فایل environment.prod.ts استفاده می‌کند.
- Cache-busting در حالت ارائه‌ی نهایی، به تمام اجزای پروژه اعمال می‌شود؛ اما در حالت توسعه فقط برای تصاویر قید شده‌ی در فایل‌های css.
- فایل‌های source map فقط برای حالت توسعه تولید می‌شوند.
- در حالت توسعه، cssها داخل فایل‌های js تولیدی قرار می‌گیرند؛ اما در حالت ارائه‌ی نهایی به صورت فایل‌های css بسته بندی می‌شوند.
- در حالت توسعه برخلاف حالت ارائه‌ی نهایی، کار uglification انجام نمی‌شود.
- در حالت توسعه برخلاف حالت ارائه‌ی نهایی، کار tree-shaking یا حذف کدهای مرده و بدون ارجاع، انجام نمی‌شود.
- در حالت توسعه برخلاف حالت ارائه‌ی نهایی، کار AOT انجام نمی‌شود. در اینجا AOT به معنای Ahead of time compilation است.
- در هر دو حالت توسعه و ارائه‌ی نهایی کار bundling و دسته بندی فایل‌ها انجام خواهد شد.

به همین جهت است که ng build سریع است؛ اما حجم بالاتری را هم تولید می‌کند. چون بسیاری از بهینه سازی‌های حالت ارائه‌ی نهایی را به همراه ندارد.


دستورات build برای حالت توسعه و ارائه‌ی نهایی

برای حالت توسعه، هر 4 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build متداول‌تر است:
>ng build --target=development --environment=dev
>ng build --dev -e=dev
>ng build --dev
>ng build

برای حالت ارائه‌ی نهایی، هر 3 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build --prod متداول‌تر است:
>ng build --target=production --environment=prod
>ng build --prod -e=prod
>ng build --prod

همچنین هر کدام از این دستورات را توسط پرچم‌های ذیل نیز می‌توان سفارشی سازی کرد:

 پرچم  مخفف  توضیح
 sourcemap--  sm-  تولید سورس‌مپ
aot--    Ahead of Time compilation 
watch--  w-  تحت نظر قرار دادن فایل‌ها و ساخت مجدد
environment--  e-  محیط ساخت
 target--  t-  نوع ساخت
 dev--    مخفف نوع ساخت جهت توسعه
 prod--     مخفف نوع ساخت جهت ارائه نهایی

برای مثال در حالت prod، سورس‌مپ‌ها تولید نخواهند شد. اگر علاقمندید تا این فایل‌ها نیز تولید شوند، پرچم souremap را نیز ذکر کنید.
و یا اگر برای حالت dev می‌خواهید AOT را فعالسازی کنید، پرچم aot-- را در آنجا قید کنید.


یک مثال: ساخت برنامه‌ی مثال قسمت چهارم - تنظیمات مسیریابی در حالت prod

تا اینجا خروجی حالت dev ساخت برنامه‌ی قسمت چهارم را بررسی کردیم. در ادامه دستور ng build --prod را در ریشه‌ی پروژه صادر می‌کنیم:
 D:\Prog\angular-routing>ng build --prod
Hash: f5bd7fd555a85af8a86f
Time: 39932ms
chunk {0} polyfills.18173234f9641113b9fe.bundle.js (polyfills) 158 kB {4} [initial] [rendered]
chunk {1} main.c6958def7c5f51c45261.bundle.js (main) 50.3 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered]
chunk {3} vendor.b426ba6883193375121e.bundle.js (vendor) 1.37 MB [initial] [rendered]
chunk {4} inline.8cec210370dd3af5f1a0.bundle.js (inline) 0 bytes [entry] [rendered]


همانطور که ملاحظه می‌کنید، اینبار نه تنها حجم فایل‌ها به میزان قابل ملاحظه‌ای کاهش پیدا کرده‌اند، بلکه این نام‌ها به همراه یک سری hash هم هستند که کار cache-busting (منقضی کردن کش مرورگر، با ارائه‌ی نگارشی جدید) را انجام می‌دهند.

در ادامه اگر بخواهیم مجددا برنامه‌ی source-map-explorer را جهت بررسی محتوای فایل‌های js اجرا کنیم، به خطای عدم وجود sourcemapها خواهیم رسید (چون در حالت prod، به صورت پیش فرض غیرفعال هستند). به همین‌جهت برای این مقصود خاص نیاز است از پرچم فعالسازی موقت آن استفاده کرد:
> ng build --prod --sourcemap
> source-map-explorer dist/vendor.b426ba6883193375121e.bundle.js


همانطور که در تصویر نیز مشخص است، اینبار کامپایلر Angular به همراه تمام ماژول‌هایی که در برنامه ارجاعی به آن‌ها وجود نداشته‌است، حذف شده‌اند و کل حجم بسته‌ی Angular به 366 KB کاهش یافته‌است.


بررسی دستور ng serve

تا اینجا برای اجرای برنامه در حالت dev از دستور ng serve -o استفاده کرده‌ایم. کار ارائه‌ی برنامه توسط این دستور، از محتوای کامپایل شده‌ی درون حافظه با مدیریت webpack انجام می‌شود. به همین جهت بسیار سریع بوده و قابلیت live reload را ارائه می‌دهد (نمایش آنی تغییرات در مرورگر، با تغییر فایل‌ها).
همانند تمام دستورات دیگر، اطلاعات بیشتری را در مورد این دستور، از طریق راهنمای آن می‌توان به دست آورد:
 > ng serve --help

که شامل این موارد هستند (علاوه بر تمام مواردی را که در حالت ng build می‌توان مشخص کرد؛ مثلا ng serve --prod -o):

 پرچم مخفف
توضیح
 open-- o-
بازکردن خودکار مرورگر پیش فرض.
حالت پیش فرض آن گشودن مرورگر توسط خودتان است و سپس مراجعه‌ی دستی به آدرس برنامه. 
 port--  p-  تغییر پورت پیش فرض مانند ng server -p 8626 
 live-reload--  lr-   فعال است مگر اینکه آن‌را با false مقدار دهی کنید.
 ssl--    ارائه به صورت HTTPS
 proxy-config--  pc-  Proxy configuration file 


استخراج فایل تنظیمات webpack از Angular CLI

Angular CLI برای مدیریت build، در پشت صحنه از webpack استفاده می‌کند. فایل تنظیمات آن نیز جزئی از فایل‌های توکار این ابزار است و قرار نیست به صورت پیش فرض و مستقیم توسط پروژه‌ی جاری ویرایش شود. به همین جهت آن‌را در ساختار پروژه‌ی تولید شده، مشاهده نمی‌کنید.
اگر علاقمند به سفارشی سازی بیشتر این تنظیمات پیش فرض باشید، ابتدا باید آن‌را اصطلاحا eject کنید و سپس می‌توان آن‌را ویرایش کرد:
 > ng eject
Ejection was successful.

To run your builds, you now need to do the following commands:
- "npm run build" to build.
- "npm run test" to run unit tests.
- "npm start" to serve the app using webpack-dev-server.
- "npm run e2e" to run protractor.

Running the equivalent CLI commands will result in an error.
============================================
Some packages were added. Please run "npm install".
همانطور که مشاهده می‌کنید عنوان کرده‌است که از این پس خودتان باید بسیاری از مسایل را به صورت دستی مدیریت کنید و Angular CLI دیگر آن‌ها را به صورت خودکار مدیریت نمی‌کند و دیگر دستورات ng build و ng serve کار نخواهند کرد (این تغییرات در فایل package.json درج می‌شوند).
در این حالت است که فایل webpack.config.js به ریشه‌ی پروژه جهت سفارشی سازی شما اضافه خواهد شد. همچنین فایل‌های .angular-cli.json، package.json نیز جهت درج این تغییرات ویرایش می‌شوند.

و اگر در این لحظه پشیمان شده‌اید (!) فقط کافی است تا این مرحله‌ی جدید commit شده‌ی به مخزن کد را لغو کنید و باز هم به همان Angular CLI قبلی می‌رسید.
مطالب
پیش پردازنده ها Preprocessors
احتمالا شما با پیش پردازنده ها کم و بیش آشنایی دارید؛ برای آشنایی با پیش پردازنده‌های موجود در سی شارپ می‌توانید به این آدرس بروید.
البته این پیش پردازنده‌ها به قدرتمندی سایر پیش پردازنده هایی که در زبان‌های دیگر مانند سی یا سی پلاس پلاس دیده‌اید نیستند. مثلا نمی‌توانند مقدار دیگری جز مقدارهای بولین دریافت کنند، یا از حافظه‌ی مصرفی استفاده کنند. همچنین باید به یاد داشته باشید که حتما باید قبل از شروع کد، از پیش پردازنده‌های استفاده کنید.

برای تعریف یک سمبل symbol می‌توانید از پیش پردازنده‌ی define# استفاده و برای حذف آن هم از undef# استفاده کنید. رسم هست که سمبل‌ها با حروف بزرگ تعریف شوند.
عبارات #if,#else,#elif,#endif هم عبارات شرطی هستند که می‌توان برای چک کردن یک سمبل از آن‌ها استفاده کرد:
#define DEBUG
...
#if DEBUG
    Console.WriteLine("You have defined DEBUG symbol");
#endif
نتیجه آن را می‌توانید در تصویر زیر مشاهده کنید:

بدیهی است که همین سمبل DEBUG را undef کنید متن بالا نمایش داده نخواهد شد.
بهتر است به پیش پردازنده‌های دیگر هم نگاهی بیندازیم:
#if STANDARD
    Console.WriteLine("You have defined STANDARD symbol");
#elif PROFESSIONAL
    Console.WriteLine("You have defined PROFESSIONAL symbol");
#elif ULTIMATE
    Console.WriteLine("You have defined ULTIMATE symbol");
#endif
حتی می‌توانید از عملگرهای شرطی چون && یا || یا == یا != و... هم استفاده کنید. تکه کد زیر، از این عملگرها بهره جسته است:
#if STANDARD && EVAL
    Console.WriteLine("You have defined STANDARD and EVAL symbols");
#endif

پیش پردازنده‌های #warning و #error
در پیش پردازنده #warning می‌توانید یک پیام هشدار یا اخطار را به پنجره‌ی warning ارسال کنید؛ ولی برنامه کماکان به اجرای خود ادامه می‌دهد. اما با #error برنامه هم پیام خطا را در پنجره مربوطه نمایش می‌دهد و هم باعث halt شدن برنامه می‌شود.
#if STANDARD && EVAL
    Console.WriteLine("You have defined STANDARD and EVAL symbols");
#endif

در کد بالا #warning را با #error جابجا می‌کنیم:


#region و #endregion
از این دو عبارت در بین کدها استفاده می‌کنیم. برای بلوک بندی کد‌ها می‌توان از آن‌ها استفاده کرد. برای مثال دسته بندی کدهای نوشته شده مثل جدا کردن property‌ها یا رویدادها یا متدها و ...، با محصور شدن تکه کدهای بین این دو، یک علامت + یا - برای انجام عمل expand و collapsed ایجاد می‌شود.


#line
برای تغییر نام فایل و شماره خطوط در هنگام دیباگ (نمایش خطا و هشدارها در پنجره‌ی نمایش خطاها) به کار می‌رود.
مثلا به تکه کد زیر دقت کنید و همچنین به تصویر بعد از آن، بدون نوشتن #line  دقت کنید:
namespace CSPreProcessorDirectivesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            inta a = 100;
            Console.ReadLine();
        }
    }
}

خطای ما در خط 14 فایل program.cs رخ داده است. در تکه کد زیر پیش پردازنده #line را اضافه کردیم:
#line 400 "MyFile.cs"
inta a = 100;

همانطور که می‌بینید آدرس تکه کد یا خط بعد از آن تغییر پیدا کرد و از آنجا به بعد از 400 به بعد شمرده می‌شود.
طبق منابع نوشته شده این پیش پردازنده موقعی بیشتر سودمند هست که تکه کد، توسط ابزارهای خارجی یا سیستمی ویرایش شده باشد.
در صورتیکه از #line default استفاده کنید، از آن نقطه به بعد، نام فایل و شماره خطاها به صورت عادی اعلام می‌شوند و #line قبلی در نظر گرفته نمی‌شود تا شاید اگر دوباره به #line جدیدی برخورد کند.
#line hidden هم تکه کدهای مربوطه را از دید دیباگر مخفی می‌کند مثل موقعیکه برنامه نویس، کد به کد یا خط به خط برنامه را دیباگ می‌کند ولی از اینجا به بعد از روی  این خطوط رد می‌شود تا به یک #line دیگر برسد. منظور از رد شدن، عدم اجرای خطوط نیست؛ بلکه دیباگ خط به خط می‌باشد.

#progma
این پیش پردازنده از دو بخش نام دستور و آگومان‌ها تشکیل شده است:
#pragma pragma-name pragma-arguments
دات نت از دو نام دستور warning و checksum پشتیبانی می‌کند؛ آرگومان‌هایی که با دستور warning می‌پذیرد:
#pragma warning disable
#pragma warning restore
با آرگومان disabled تمامی هشدارهای خطوط بعد از آن نادیده گرفته شده و اعلام نمی‌شوند و از restore برای بازگشت از حالت disabled به کار می‌رود. همچنین برای غیر فعال کردن هشدار برای خط یا خطوط خاص هم میتوانید به صورت زیر بنویسید:
#pragma warning disable 414
#pragma warning disable 414, 3021

#checksum
#pragma checksum "filename" "{guid}" "checksum bytes"
از این یکی برای ذحیره هشدارها و خطاها در program database یا PDB استفاده می‌شود (برای مواقعیکه پروژه شما قرار است به یک com یا dll تبدیل شود؛ کاربردی زیادی دارد). آرگومان اول نام فایل که بعدا برای مانیتور کردن به راحتی بین کلاس‌ها تشخیص داده شود و دومی که GUID است و همین GUID را باید برای فایل مشخص کنید.
// Guid for the interface IMyInterface.
[Guid("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4")]
interface IMyInterface
{
    void MyMethod();
}

// Guid for the coclass MyTestClass.
[Guid("936DA01F-9ABD-4d9d-80C7-02AF85C822A8")]
public class MyTestClass : IMyInterface
{
    public void MyMethod() {}
}
و checksum _bytes که باید به صورت هگزادسیمال در حالت رشته‌ای نوشته شود و باید بیانگر یک عدد زوج باشد؛ در صورتیکه یک عدد فرد را مشخص کنید، کمپایلر پیش پردازنده شما را در نظر نمی‌گیرد. نهایتا به صورت زیر نوشته می‌شود:
class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "{012345678AB}" // New checksum
    }
}

منابع :
مطالب
Web.config File Transformation #1
یکی از مشکلات برنامه نویسان اختلاف بین فایل web.config تولید شده در سیستم خودشان و مقصد نهایی برنامه می‌باشد. در این مطلب به نحوه خودکار سازی تغییرات، برای توسعه بر روی مقصد نهایی برنامه می‌پردازیم. اکثر برنامه‌ها تنظیماتی در فایل web.config خود دارند که زمان عرضه برای مقصد نهایی می‌بایست تغییر کنند. پردازش خودکار این تغییرات کمک می‌کند تا از خطاهای ناشی از تغییرات دستی در زمان عرضه نهایی جلوگیری شود.


فایل‌های پیش فرض انتقالی
در پنجره Solution Explorer فایل web.config را بوسیله ایکون کنار آن باز کنید تا دو فایل پیش فرض web.Debug.config و web.Release.config که برای build configuration‌های برنامه ایجاد شده‌اند، مشاهده نمایید.

شما می‌توانید برای ایجاد build configuration دیگری، با راست کلیک کردن بر روی فایل web.config و انتخاب گزینه Add Config Transforms یک فایل دیگر را ایجاد نمایید. البته اگر این گزینه غیر فعال است، ابتدا می‌بایست یک build configuration جدید را ایجاد نمایید.

غیرفعال کردن حالت اشکال زدایی (debug)
در زمان عرضه نهایی برنامه دیگر لازم نیست تا امکان اشکال زدایی فعال باشد؛ به همین خاطر در فایل web.Release.config در تگ compilation مانند کد زیر از دستور RemoveAttributes استفاده می‌کنیم تا آن خاصیت را حذف نماید. 
<compilation xdt:Transform="RemoveAttributes(debug)" />

محدود کردن صفحات خطای برنامه فقط برای برنامه نویس
یکی دیگر از تنظیمات، عدم نمایش خطاهای برنامه به کاربر نهایی و انتقال آن به یک صفحه رخداد خطا می‌باشد:
<customErrors mode="Off" defaultRedirect="~/GenericErrorPage.aspx">
  <error statusCode="404" redirect="~/GenericErrorPage.aspx" />
</customErrors>
اما در زمان کد نویسی در محیط ویژوال استادیو می‌خواهیم mode را بر روی Off تنظیم نماییم تا خطا نمایش داده شود:
<customErrors mode="RemoteOnly" xdt:Transform="Replace" defaultRedirect="~/GenericErrorPage.aspx">
  <error statusCode="404" redirect="~/GenericErrorPage.aspx" />
</customErrors>
برای این کار از دستور Replace استفاده می‌کنیم تا تگ customErrors با مد RemoteOnly را جایگزین مد Off در زمان Release نماید.

تنظیم رشته اتصال پایگاه داده اصلی
مهمترین قسمت فایل web.config تگ connection string می‌باشد که باید به رشته اتصال پایگاه داده نهایی برنامه، تغییر یابد.
<connectionStrings>
      <add name="TestContext" 
        connectionString="Data Source=Server1;Password=****;User ID=sa; Initial Catalog=Test;Integrated Security=True" 
        xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    </connectionStrings>
برای این کار از دستور SetAttributes مانند بالا استفاده می‌کنیم. اما چون ممکن است چندین connection string داشته باشیم، از دستور Match با مقدار name استفاده می‌کنیم، تا فقط رشته اتصالی که نام آن برابر رشته جاری می‌باشد را تغییر دهد که در اینجا TestContext را جستجو می‌نماید.

پیش نمایش connection string نهایی
برای مشاهده پیش نمایش نهایی web.config، بر روی فایل web.Release.config در پنجره Solution Explorer راست کلیک کنید و گزینه Preview Transform را انتخاب کنید.

هر دو فایل web.config اصلی و نهایی در کنار هم آورده می‌شود و اختلاف بین این دو را که برجسته‌تر شده است، می‌توانید مشاهده نمایید.

ادامه دارد...
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


استفاده از XML Comments برای بهبود کیفیت مستندات API

نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary>
/// Get an author by his/her id
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <returns>An ActionResult of type Author</returns>
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
                    // ... 
                   );

                var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                setupAction.IncludeXmlComments(xmlCommentsFullPath);
            });
        }
در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




افزودن توضیحات به Response

تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author with Id, FirstName and LastName fields
    /// </summary>
    public class Author
    {
        /// <summary>
        /// The id of the author
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// The first name of the author
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        public string LastName { get; set; }
    }
}
مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>
سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
  // ...
                   );
                var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
            });
        }
با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:



بهبود مستندات به کمک Data Annotations

اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
using System.ComponentModel.DataAnnotations;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author for update with FirstName and LastName fields
    /// </summary>
    public class AuthorForUpdate
    {
        /// <summary>
        /// The first name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string LastName { get; set; }
    }
}
پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
و دیگری HttpPatch:
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
            Guid authorId,
            JsonPatchDocument<AuthorForUpdate> patchDocument)
این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary>
/// Partially update an author
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <param name="patchDocument">The set of operations to apply to the author</param>
/// <returns>An ActionResult of type Author</returns>
/// <remarks>
/// Sample request (this request updates the author's first name) \
/// PATCH /authors/id \
/// [ \
///     { \
///       "op": "replace", \
///       "path": "/firstname", \
///       "value": "new first name" \
///       } \
/// ] \
/// </remarks>
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
    Guid authorId,
    JsonPatchDocument<AuthorForUpdate> patchDocument)
در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



روش کنترل warningهای کامنت‌های تکمیل نشده

با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningsAsErrors>NU1605;</WarningsAsErrors>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>
توضیحات:
- اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
پاسخ به بازخورد‌های پروژه‌ها
کندی ایجاد فایل pdf
این اندازه گیری را به صورت زیر مجددا انجام دهید:
- یک فایل Global.asax به سرویس تهیه شده اضافه کنید. در روال protected void Application_Start آن، یک گزارش خالی رو تهیه کنید. هدف از اینکار وادار کردن فعال شدن JIT، جهت کامپایل زود هنگام iTextSharp است. JIT در هنگام آغاز یک برنامه دات نتی، قسمت‌های اعظم و مورد نیاز کدهای میانی را کامپایل می‌کند اما نه همه‌ی آن‌را. مابقی در زمان نیاز کامپایل خواهند شد. با این روش می‌تونید JIT رو وادار کنید زودتر کارش را به اتمام برساند. (همین مساله در زمان تهیه اولین گزارش به شدت خودش را نشان می‌دهد)
- تهیه دیتاسورس را از متد تهیه گزارش خارج کنید. این هم بالاخره زمانی را صرف می‌کند. اما بر اساس آن نباید سرعت کار با کتابخانه تهیه گزارش را محاسبه کرد.
- سه بار اندازه گیری کنید. بعد میانگین بگیرید.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 1 - NET Core. چیست؟
NET Core. چیست؟

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


مهم‌ترین قسمت‌های این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام می‌دهد، BCL که کلاس‌های پایه‌ای را جهت کار با  IO، Text و غیره، فراهم می‌کنند، هستند؛ به علاوه کتابخانه‌هایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار می‌کنند.
هرچند تعدادی از توسعه دهنده‌های دات نت تنها با Full framework کار می‌کنند، اما در طی سال‌های اخیر انشعابات بسیار دیگری از آن به وجود آمده‌اند؛ مانند دات نت‌های ویژه‌ی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعه‌ی برنامه‌های iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورک‌ها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرم‌های دیگر قابل انتقال نیستند.

با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرم‌های یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:


هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرم‌های مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائه‌ی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانه‌ی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرم‌های مختلف هستید.

برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی می‌کند).


هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرم‌هایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده می‌کنند و نحوه‌ی توزیع آن‌ها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت می‌کنید که از طریق نیوگت سفارش داده‌اید.
به این ترتیب نه تنها کار توزیع برنامه‌های مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامه‌ها نخواهد داشت و مشکلات جانبی را به وجود نمی‌آورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانی‌ها را دریافت کنید. اینبار به روز رسانی بسته‌های نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارش‌های قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی می‌شود.

البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائه‌ی آن، PCLها منسوخ شده درنظر گرفته می‌شوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرم‌های دیگر، به صورت یکجا انتخاب می‌شوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانه‌ی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامه‌ای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامه‌های NETStandard همان برنامه‌های مبتنی بر NET Core. هستند.


ASP.NET Core چیست؟

در زمان نگارش این مطلب، دو گزینه‌ی برنامه‌های وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفاده‌ی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایده‌ی کاملا ماژولار بودن، تهیه شده‌است و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیء‌گرا مانند تزریق وابستگی‌ها، به صورت توکار و از ابتدا پشتیبانی می‌شوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمی‌کند. البته در این بین عدم پشتیبانی از «وب فرم‌ها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائه‌ی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.


اکنون وضعیت  ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟

ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه می‌دهد و دیگر خبری از ارائه‌ی مجزای این‌ها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شده‌است. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی این‌ها را پوشش می‌دهد.

در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شده‌است و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافته‌اند.


آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوری‌های «قدیمی» خودش را بر روی CodePlex نگهداری می‌کند و تمام فناوری‌های «جدید» را به GitHub منتقل کرده‌است. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامه‌ی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگ‌ها و مشکلات گزارش شده باقی می‌مانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex می‌توانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شد‌ه‌است که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ داده‌است. نگارش‌های 5 و 2 آن‌ها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه می‌یابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژه‌ی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.


اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند

برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحله‌ی نهایی تکمیل قابلیت‌های آن نرسیده‌است. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشده‌است.



همانطور که ملاحظه می‌کنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.


فناورهای منسوخ شده‌ی در NET Core.

یکسری از فناوری‌ها و کتابخانه‌ها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوری‌های خاتمه یافته‌ی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا می‌شود و تولید برنامه‌های native و static linking آن‌ها مانند برنامه‌های ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمی‌شوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شده‌است که استفاده‌ی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز می‌گرداند و نه اطلاعات بیشتری را مانند  GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی می‌کند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامه‌ها توصیه شده‌است که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامه‌ها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفاده‌ای از آن نمی‌شود.
- binary serialization: البته کتابخانه‌هایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.


فناور‌هایی که به زودی به NET Core. منتقل می‌شوند

یکسری از فناوری‌ها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml


مراحل نصب ASP.NET Core 1.0

پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارش‌های آزمایشی قبلی را حذف و یا بازنویسی نمی‌کند و همین مساله ممکن است سبب بروز تداخل‌هایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارش‌های RC یا بتا را پیشتر نصب کرده‌اید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بسته‌های قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.


بررسی شماره نگارش 1.0 RTM

پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
 C:\Users\Vahid>dotnet --version
1.0.0-preview2-003121
همانطور که مشاهده می‌کنید، نگارش ذکر شده هنوز در مرحله‌ی preview است و صرفا مرتبط است به tooling و یا ابزارهای مرتبط با آن.
اگر یک پروژه‌ی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
 .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template


در اینجا فایل جدیدی را به نام global.json مشاهده می‌کنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشه‌های اصلی پروژه:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003121"
  }
}
مطالب
آموزش زبان Rust - قسمت 2 - نصب Rust و ایجاد یک پروژه‌ی جدید
نصب Rust

برای نصب rust، متناسب با سیستم عامل خود، ابتدا وارد سایت rustup  شوید و فایل دانلود متناسب با سیستم عامل مورد نظرتان را دانلود و نصب کنید.

Cargo  چیست و چه کاربردی دارد؟

Cargo همراه با زبان برنامه نویسی Rust گنجانده شده‌، همزمان نصب می‌شود و برای ایجاد، ساخت و مدیریت پروژه‌های Rust استفاده می‌گردد. این یک رابط سطح بالا برای کار با کدهای Rust را ارائه می‌دهد که شروع به کار با Rust و مدیریت پروژه‌های خود را برای توسعه دهندگان آسان‌تر می‌کند.
Cargo سیستم ساخت و package manager مخصوص زبان برنامه نویسی Rust است. ابزاری است که به توسعه دهندگان Rust کمک می‌کند تا پروژه‌های خود را با خودکارسازی کارهایی مانند کامپایل کد، مدیریت وابستگی‌ها، اجرای آزمایش‌ها و ایجاد بسته‌های قابل توزیع، مدیریت کنند.

برخی از ویژگی‌های Cargo

Dependency management: برنامه Cargo می‌تواند به‌طور خودکار وابستگی‌های پروژه‌های Rust را دانلود کرده، بسازد و مدیریت کند. این باعث می‌شود توسعه دهندگان به راحتی کتابخانه‌ها و ماژول‌های جدیدی را به پروژه‌های خود اضافه کنند.

Building and testing: برنامه Cargo می‌تواند پروژه‌های Rust را بسازد و test‌ها را به صورت خودکار اجرا کند. همچنین گزینه‌هایی را برای ساختن ساخت‌های بهینه یا اشکال زدایی فراهم می‌کند.

Packaging: برنامه Cargo می‌تواند بسته‌های قابل توزیعی را مانند tarballs یا بسته‌های باینری را برای پروژه‌های Rust ایجاد کند.

Customization: برنامه Cargo به توسعه دهندگان اجازه می‌دهد تا فرآیند ساخت برنامه‌ی خود را با تعیین گزینه‌های ساخت مختلف، در فایل پیکربندی Cargo.toml سفارشی کنند. به‌طور کلی Cargo توسعه و مدیریت پروژه‌های Rust را با ارائه یک ابزار کارآمد برای خودکارسازی بسیاری از وظایف توسعه رایج، ساده می‌کند.


ایجاد اولین پروژه‌ی Rust

بعد از نصب rust برای ایجاد یک پروژه جدید، terminal سیستم عامل را باز کنید و دستور زیر را در آن وارد کنید:
cargo new  project_name
هنگامیکه یک پروژه‌ی جدید Rust را با استفاده از نام پروژه‌ی مورد نظر ایجاد می‌کنید، یک دایرکتوری با نام داده شده، حاوی فایل‌ها و دایرکتوری‌های زیر ایجاد می‌گردد:
Cargo.toml : این فایل manifest پروژه است که در آن نام پروژه، نسخه، وابستگی‌ها و سایر ابرداده‌ها را مشخص می‌کنید. فایل Cargo.toml حاوی یک metadata درباره پروژه است؛ مانند نام، نسخه، نویسندگان و وابستگی‌های آن. در اینجا مثالی از شکل ظاهری یک فایل Cargo.toml آورده شده است:
[package]
name = "my-project"
version = "0.1.0"
authors = ["John Doe <johndoe@example.com>"]

[dependencies]
serde = "1.0"
serde_json = "1.0"
بخش [package] حاوی یک metadata در مورد خود پروژه، از جمله نام، نسخه، نویسندگان و جزئیات دیگر است.
بخش [dependencies] وابستگی‌های پروژه را فهرست می‌کند. در این مثال، پروژه به پکیج‌های serde و serde_json بستگی دارد که برای serialization و deserialization داده‌ها استفاده می‌شوند.
src directory: این دایرکتوری حاوی کد منبع پروژه شما است. به طور پیش فرض، شامل یک فایل main.rs است که نقطه ورود برنامه شما است.
target directory: این فهرست شامل فایل‌های باینری کامپایل شده تولید شده توسط کامپایلر Rust می‌باشد.

هنگامی که cargo build یا cargo run را اجرا می‌کنید، Cargo به طور خودکار یک دایرکتوری target/debug را برای ذخیره‌ی فایل‌های باینری کامپایل شده ایجاد می‌کند. اگر cargo build --release را اجرا کنید، Cargo بجای آن، یک دایرکتوری target/release را ایجاد می‌کند. بعلاوه، اگر هنگام ایجاد پروژه‌ی خود از نسخه‌ی خاصی از Rust (مانند نسخه‌ی 2018) استفاده کنید، Cargo یک فیلد نسخه را در فایل Cargo.toml شما برای تعیین نسخه، اضافه می‌کند. 
مطالب
تعامل MATLAB (متلب) با دات نت - قسمت دوم
در قسمت قبل در مورد استفاده دات نت در متلب توضیح داده شد. در این قسمت به نحوه استفاده توابع متلب در دات نت بصورت ساده می‌پردازیم.
فرض کنید تیم برنامه‌نویس متلب و تیم برنامه‌نویس دات نت در تعامل با یکدیگر هستند. وظیفه تیم برنامه نویسی متلب به شرح زیر می‌باشد :

1- نوشتن توابع در متلب و تست کردن آنها جهت توسعه و ارائه مناسب به تیم مقابل
2- درست کردن کامپوننت دات نت در متلب با استفاده از محیط  Deployment Tool GUI  (با اجرای دستور deploytool در متلب)
3- استفاده از یک پکیج بسته‌بندی شده از فایل‌های قابل ارائه به تیم مقابل (اختیاری)
4- کپی پکیج در محل از قبل تعیین شده توسط دو تیم یا ارائه آن به تیم مقابل جهت استفاده

برای مثال M فایل (اصطلاح فایل‌ها در متلب همانند کلاس در دات نت) makesquare.m را که در مسیر
matlabroot\toolbox\dotnetbuilder\Examples\VS8\NET\MagicSquareExample\MagicSquareComp
است را در نظر بگیرید :  
function y = makesquare(x)
%MAKESQUARE Magic square of size x.
%   Y = MAKESQUARE(X) returns a magic square of size x.
%   This file is used as an example for the MATLAB 
%   Builder NE product.

%   Copyright 2001-2012 The MathWorks, Inc.

y = magic(x);
تابع majic یک ماتریس در ابعاد x در x درست می‌کند که درایه‌های آن اعداد صحیح از 1 تا X^2 بوده و مجموع سطر و ستون‌های آن با هم برابر است. X باید بزرگتر یا مساوی 3 باشد.
در صورتی که x  برابر 5 انتخاب شود خروجی متلب بصورت زیر خواهد بود :
17 24  1  8 15
23  5  7 14 16
 4  6 13 20 22
10 12 19 21  3
11 18 25  2  9
در قسمت تهیه یک کامپوننت دات نت اطلاعات زیر را در نظر بگیرید :
 
 makeSqr  
 Project Name 
 MLTestClass 
Class Name 
 makesquare.m  File to compile 
سپس برای درست کردن کامپوننت در محیط  Deployment Tool GUI برنامه متلب را اجرا کرده و در پنجره command دستور deploytool  را اجرا کنید تا پنجره زیر باز شود :

نام و مسیر پروژه را تعیین کنید سپس از منوی کشویی نوع پروژه، که دات نت اسمبلی باشد را انتخاب کنید. پنجره‌ای در به شکل زیر مشاهده خواهد شد : 

در تب build اگر قصد استفاده از اپلیکیشن COM را دارید و یا فایل‌هایی جهت تکمیل پروژه قصد پیوست دارید را در قسمت پایین Add files را انتخاب کنید. و اگر قصد استفاده از اپلیکیشن دات نت را دارید قسمت بالایی Add classes را انتخاب کنید و نام کلاس را وارد کنید.

سپس برای کلاس مورد نظر فایل‌های متلبی که قصد کامپایل کردن آنها را دارید از قسمت Add files پیوست کنید. در صورتیکه قصد اضافه کردن کلاس اضافی را داشتید مجددا مراحل را طی کنید. در انتها دکمه build را زده تا عملیات کامپایل آغاز شود. اما برای استفاده تیم برنامه‌نویسی دات نت احتیاج به کامپایلر متلب می‌باشد که این مهم در پکیجی که به این تیم ارائه خواهد شد مد نظر قرار خواهد گرفت.در قسمت تب Package گزینه Add MCR را انتخاب نمائید :

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

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

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

 Documentation files   componentName.xml 
 Program Database File, which contains debugging information   componentName.pdb (if Debug optionis selected)
 Component assembly file   componentName.dll 
 MCR Installer (if not already installed on the target machine).   MCR Installer 

بعد از طی مراحل فوق نوبت به تیم برنامه‌نویسی دات نت می‌رسد. بعد از دریافت پکیج از تیم برنامه‌نویسی متلب در صورتیکه بر روی سیستم هدف کامپایلر متلب و یا خود متلب نصب نیست باید از داخل پکیج این کامپایلر نصب شود.
دقت داشته باشید که ورژن کامپایلر بر روی سیستم باید با ورژن پکیج دریافتی یکی باشد.
در VS یک پروژه کنسول ایجاد کنید و از فولدر پکیج پروژه دریافتی در زیرفولدر distrib فایل makeSqr.dll را به رفرنس برنامه VS اضافه کنید.
در ادامه از مسیر نصب کامپایلر فایل MWArray.dll را هم به رفرنس پروژه اضافه کنید. این فایل جهت تبادل داده اپلیکیشن با کامپایلر متلب مورد استفاده قرار می‌گیرد.

installation_folder\toolbox\dotnetbuilder\bin\architecture\framework_version
اسمبلی‌های زیر را به کلاس Program  برنامه اضافه کنید :
using System;
using MathWorks.MATLAB.NET.Arrays;
using MyComponentName;
سپس کدهای زیر را به کلاس فوق اضافه نمائید :
static void Main(string[] args)
        {
            MLTestClass obj = new MLTestClass();
            MWArray[] result = obj.makesquare(1, 5);

            MWNumericArray output = (MWNumericArray)result[0];
            Console.WriteLine(output);

        }
توضیحات کدهای فوق :
1- MWNumericArray یک اینترفیس جهت تعیین و نمایش نوع آرایه‌های عددی در متلب است.
2- MWArray یک کلاس abstract جهت دسترسی، فرمت‌دهی و مدیریت آرایه‌های متلب می‌باشد.
3- عدد 1 مشخص کننده تعداد خروجی تابع متلب و عدد 5 ورودی تابع می‌باشد.

خروجی برنامه همانند خروجی متلب بصورت زیر خواهد بود :

نکته:
ورژن فریمورک دات نت در هنگام کامپایل با ورژن Mwarray.dll باید یکی باشد.

مطالب
BloggerToCHM

این آخرین مطلب ارسالی من در سال 87 خواهد بود. پیشاپیش فرا رسیدن سال جدید را خدمت شما تبریک عرض کرده و برای همه‌ی شما آرزوی سالی خوب و با برکت را دارم.

برنامه‌ی کوچکی را تهیه کرده‌ام که با دریافت لینک یک وبلاگ بلاگری، تمامی مطالب آن‌را (اعم از پست‌ها، کامنت‌ها و تصاویر) دریافت کرده و سپس حاصل را به صورت خودکار به یک فایل chm تبدیل می‌کند.
دریافت برنامه



پیش‌نیاز اجرا:
- نصب دات نت فریم ورک 2 یا بالاتر (دات نت فریم ورک 3 و نیم، سرویس پک یک توصیه می‌شود زیرا حاوی سرویس پک 2 دات نت فریم ورک 2 نیز هست و این سرویس پک به صورت جدا ارائه نشده است)
- نصب برنامه‌ی معروف و رایگان html help work shop (که از کامپایلر آن برای تولید فایل نهایی chm استفاده می‌شود)

طرز استفاده از برنامه هم بسیار ساده‌است.
پس از نصب پیشنیازهای ذکر شده، و نصب برنامه، یک shortcut روی دسکتاپ شما ایجاد می‌شود که به کمک آن می‌توان برنامه را اجرا نمود.
سپس از منوی فایل، گزینه‌ی new blog را انتخاب کرده و آدرس اصلی یک وبلاگ بلاگری را وارد کنید. همچنین یک نام گروه دلخواه را نیز برای آن وارد نمائید و در آخر کلیک بر روی دکمه‌ی add . لازم به ذکر است که حتما هنگام ثبت بلاگ‌ها نیاز به اتصال به اینترنت می‌باشد، زیرا باید بتوان آمار اولیه‌ی وبلاگ را دریافت نمود و همچنین مطمئن شد که این وبلاگ بلاگری است و فرمت مربوطه را دارد.
پس از ثبت یک بلاگ، یا می‌توان بر روی آن کلیک راست کرد و گزینه‌ی start processing را انتخاب نمود و یا وبلاگ‌های مورد نظر را تیک زد و سپس از منوی process گزینه‌ی start را انتخاب کرد تا عملیات دریافت اطلاعات وبلاگ‌های مورد نظر به ترتیب انجام شود.
در برنامه قسمت db to chm منظور حالت آفلاین است. وبلاگی را دریافت نموده‌اید اما می‌خواهید مجددا فایل chm آن‌را تهیه کنید. به این صورت اطلاعات از دیتابیس برنامه دریافت خواهد شد بجای دریافت از اینترنت.

نمونه‌ی فایل تولیدی
دریافت خلاصه‌ی وبلاگ جاری


اگر به هر دلیلی از طرح و رنگ پیش فرض فایل نهایی راضی نبودید، به پوشه‌ی template برنامه مراجعه کرده و فایل‌های htm و css مورد استفاده را ویرایش کنید و طرح و رنگ دلخواه خود را اعمال نمائید. فقط دقت داشته باشید که در این فایل‌های htm ، هرجایی کلمه‌ای با $ شروع شده بود یعنی قسمتی است که محتوای نهایی در آن‌جا قرار می‌گیرد و این نام نباید تغییری کند (محل آن مهم نیست، نام آن مهم است). همچنین بدیهی است که نام سلکتورهای فایل css مورد استفاده هم نباید تغییر کند.

موفق باشید.


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

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

دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی می‌کرده‌است. همچنین این مورد یکی از ویژگی‌های Win32 نیز می‌باشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام می‌شود.
        public string TestNoneAsync()
        {
            var webClient = new WebClient();
            return webClient.DownloadString("http://www.google.com");
        }
در این مثال متداول، متد DownloadString به صورت همزمان یا synchronous عمل می‌کند. به این معنا که تا پایان عملیات دریافت اطلاعات از وب، منتظر مانده و ترد جاری را قفل می‌کند. مشکل از جایی آغاز می‌شود که مدت زمان دریافت اطلاعات، طولانی باشد. چون این عملیات در ترد UI در حال انجام است، کل رابط کاربری برنامه تا پایان عملیات نیز قفل شده و دیگر پاسخگوی سایر اعمال رسیده نخواهد بود. در این حالت عموما ویندوز در نوار عنوان برنامه، واژه‌های Not responding را نمایش می‌دهد.
این مورد همچنین در برنامه‌های سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخ‌دهی سرور نیز کاهش می‌یابد. بنابراین در این نوع موارد، برنامه‌های چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامه‌های سمت سرور ندارند. زیرا در آن‌ها می‌توان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه می‌شود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز می‌باشد. این متد به محض فراخوانی، ترد جاری را آزاد می‌کند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجه‌ی دریافت اطلاعات از وب نمی‌شود. به این نوع API، یک Asynchronous API گفته می‌شود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمی‌شود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف می‌کند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آن‌را پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شده‌است. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی می‌گردد.
        public void TestAsync()
        {
            var webClient = new WebClient();
            webClient.DownloadStringAsync(new Uri("http://www.google.com"));
            webClient.DownloadStringCompleted += webClientDownloadStringCompleted;
        }

        void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // use e.Result
        }
در اینجا همچنین توسط آرگومان DownloadStringCompletedEventArgs، موفقیت یا شکست عملیات نیز گزارش می‌شود و مقدار e.Result حاصل عملیات است.

مشکل!
ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار ساده‌تر است از نمونه‌ی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شده‌است و نتیجه‌ی نهایی، درون یک روال رخدادگردان بدست می‌آید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته می‌شود. EAP در دات نت 2 معرفی شد. روال‌های رخدادگردان در این حالت، در ترد اصلی برنامه اجرا می‌شوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه می‌یابد.


آشنایی با Synchronization Context

ابتدا یک برنامه‌ی WinForms ساده را آغاز کرده و یک دکمه‌ی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آن‌را به نحو ذیل تغییر دهید:
using System;
using System.Linq;
using System.Net;
using System.Windows.Forms;

namespace Async02
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    txtResults.Text = headersText;
                }, null);
        }

        private string formatHeaders(WebHeaderCollection headers)
        {
            var headerString = headers.Keys.Cast<string>()
                                      .Select(header => string.Format("{0}:{1}", header, headers[header]));
            return string.Join(Environment.NewLine, headerString.ToArray());
        }
    }
}
در اینجا از روش دیگری برای دریافت اطلاعات از وب استفاده کرده‌ایم. با استفاده از امکانات HttpWebRequest، کوئری‌های پیشرفته‌تری را می‌توان تهیه کرد. برای مثال می‌توان نوع متد را به HEAD تنظیم نمود؛ تا صرفا مقادیر هدر آدرس درخواستی از سرور، دریافت شوند.
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شده‌است. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجه‌ی عملیات به دست می‌آید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
 An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
علت اینجا است که asyncResult دریافتی، در تردی دیگر نسبت به ترد اصلی برنامه که UI را اداره می‌کند، اجرا می‌شود. یکی از راه حل‌های این مشکل و انتقال اطلاعات به ترد اصلی برنامه، استفاده از Synchronization Context است:
        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var sync = SynchronizationContext.Current;
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    sync.Post(delegate { txtResults.Text = headersText; }, null);
                }, null);
        }
SynchronizationContext.Current در اینجا چون در ابتدای متد دریافت اطلاعات اجرا می‌شود، به ترد UI، یا ترد اصلی برنامه اشاره می‌کند. به همین جهت این زمینه را نباید داخل Async callback دریافت کرد؛ زیرا ترد جاری آن، ترد UI مدنظر ما نیست. سپس همانطور که ملاحظه می‌کنید، توسط متد Post آن می‌توان اطلاعات را در زمینه‌ی تردی که SynchronizationContext به آن اشاره می‌کند اجرا کرد.


برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل  Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینه‌ی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا می‌شود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کرده‌است، اما باز هم کد ساده‌ای به نظر نمی‌رسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هسته‌ی زبان اضافه شده‌است.


Syntax ابتدایی یک متد Async

در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System;
using System.Threading.Tasks;

namespace Async01
{
    public class AsyncExample
    {
        public async Task DoWorkAsync(int parameter)
        {
            await Task.Delay(parameter);
            Console.WriteLine(parameter);
        }
    }
}
شیوه‌ی نگارش آن بر اساس راهنمای نوشتن برنامه‌های Async یا Task asynchronous programming model یا به اختصار TAP است:
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونه‌ی جنریک آن‌را. البته کامپایلر، async void را نیز پشتیبانی می‌کند ولی در قسمت‌های بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکل‌زا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آ‌ن‌ها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژه‌ی کلیدی async نیز استفاده می‌گردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر می‌گوید، عبارت پس از من، یک وظیفه‌ی غیرهمزمان است و ادامه‌ی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.

در متد DoWorkAsync، ابتدا به اندازه‌‌ای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا می‌شود.


یک اشتباه عمومی! استفاده از واژه‌های کلیدی async و await متد شما را async نمی‌کنند.

برخلاف تصور ابتدایی از بکارگیری واژه‌های کلیدی async و await، این کلمات نحوه‌ی اجرای متد شما را async نمی‌کنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید می‌باشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
        public async Task<double> GetNumberAsync()
        {
            var generator = new Random();
            await Task.Delay(generator.Next(1000));

            return generator.NextDouble();
        }
در این متد با استفاده از Task.Delay، انجام یک عملیات طولانی شبیه سازی شده‌است؛ مثلا دریافت یک عدد یا نتیجه از یک وب سرویس. سپس در نهایت، عددی را بازگشت داده است. برای بازگشت یک خروجی double، در اینجا از نمونه‌ی جنریک Task استفاده شده‌است.
در ادامه برای استفاده از آن خواهیم داشت:
        public async Task<double> GetSumAsync()
        {
            var leftOperand = await GetNumberAsync();
            var rightOperand = await GetNumberAsync();

            return leftOperand + rightOperand;
        }
خروجی این متد تنها زمانی بازگشت داده می‌شود که نتایج leftOperand و rightOperand از وب سرویس فرضی، دریافت شده باشند و در اختیار مصرف کننده قرارگیرند. بنابراین همانطور که ملاحظه می‌کنید از واژه‌ی کلیدی await جهت تشکیل یک عملیات غیرهمزمان و مدیریت ساده‌تر کدهای نهایی، شبیه به کدهای معمولی همزمان استفاده شده‌است.
در کدهای همزمان متداول، سطر اول ابتدا انجام می‌شود و بعد سطر دوم و الی آخر. با استفاده از واژه‌ی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده می‌شد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجه‌ی آن در یک روال رخ‌داد گردان جایی در کدهای برنامه دریافت می‌شد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژه‌ی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه می‌دهد. به این ترتیب بهتر می‌توان بر روی منطق و الگوریتم‌های مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.

امکان استفاده از واژه‌ی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شده‌است:
        public async Task<double> GetProductOfSumAsync()
        {
            var leftOperand = GetSumAsync();
            var rightOperand = GetSumAsync();

            return await leftOperand * await rightOperand;
        }
اگر await را از این مثال حذف کنیم، خطای کامپایل زیر را دریافت خواهیم کرد:
 Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
خروجی متد GetSumAsync صرفا یک Task است و نه یک عدد. پس از استفاده از await، عملیات آن انجام شده و بازگشت داده می‌شود.


اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونه‌ی async سی‌شارپ 5 کنیم، می‌توان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
        public async Task<string> DownloadAsync()
        {
            var webClient = new WebClient();
            return await webClient.DownloadStringTaskAsync("http://www.google.com");
        }
نکته‌ی مهم این کد علاوه بر ساده سازی اعمال غیر همزمان، برای استفاده از نتیجه‌ی نهایی آن، نیازی به SynchronizationContext معرفی شده در تاریخچه‌ی ابتدای بحث نیست. نتیجه‌ی دریافتی از آن در ترد اصلی برنامه تحویل داده شده و به سادگی قابل استفاده است.


سؤال: آیا استفاده از await نیز ترد جاری را قفل می‌کند؟

اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر می‌رسد که در اینجا نیز ترد اصلی، همانند قبل قفل شده‌است.
        public void TestDownloadAsync()
        {
            Debug.WriteLine("Before DownloadAsync");
            DownloadAsync();
            Debug.WriteLine("After DownloadAsync");
        }
اگر این متد را اجرا کنید (در آن await بکار نرفته)، بلافاصله خروجی ذیل را مشاهده خواهید کرد:
 Before DownloadAsync
After DownloadAsync
به این معنا که در اصل، همانند سایر روش‌های async موجود از دات نت یک، در اینجا نیز فراخوانی متد async ترد اصلی را بلافاصله آزاد می‌کند و ترد آن‌را قفل نخواهد کرد. استفاده از await نیز عملکرد کدها را تغییر نمی‌دهد. تنها کامپایلر در پشت صحنه همان کدهای لازم جهت مدیریت روال‌های رخدادگردان و callbackها را تولید می‌کند، به نحوی که صرفا نحوه‌ی کدنویسی ما همزمان به نظر می‌رسد، اما در پشت صحنه، نحوه‌ی اجرای آن غیرهمزمان است.


برنامه‌های Async و نگارش‌های مختلف دات نت

شاید در ابتدا به نظر برسد که قابلیت‌های جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژه‌های کلیدی را پشتیبانی کند، امکان استفاده از آن‌ها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامه‌های دات نت 4 البته):
 PM> Install-Package Microsoft.Bcl.Async
این روال متداول VS.NET بوده است تا به امروز. برای مثال اگر VS 2010 را نصب کنید و سپس یک برنامه‌ی دات نت 3.5 را ایجاد کنید، امکان استفاده‌ی کامل از تمام امکانات سی‌شارپ 4، مانند آرگومان‌های نامدار و یا مقادیر پیش فرض آرگومان‌ها را در یک برنامه‌ی دات نت 3.5 نیز خواهید داشت. همین نکته در مورد async نیز صادق است. VS 2012 (یا نگارش‌های جدیدتر) را نصب کنید و سپس یک پروژه‌ی دات نت 4 را آغاز کنید. امکان استفاده از async و await را خواهید داشت. البته در این حالت دسترسی به متدهای الحاقی جدید را مانند DownloadStringTaskAsync نخواهید داشت. برای رفع این مشکل باید بسته‌ی  Microsoft.Bcl.Async را نیز توسط نیوگت نصب کنید.