نظرات مطالب
EF Code First #12
بحث ASP.NET متفاوت است با Windows Forms یا WPF. در برنامه‌های وب یک زیر ساخت آماده برای آغاز و پایان درخواست‌ها و مدیریت خودکار این مسایل وجود دارد. معادل آن مثلا در یک برنامه مبتنی بر MVVM، مدیریت طول عمر یک context در طول عمر ViewModel برنامه است. علت این مساله هم به stateless بودن برنامه‌های وب و state-full بودن برنامه‌های ویندوزی بر می‌گردد. در یک برنامه وب در پایان درخواست، تمام اشیاء یک فرم در سمت سرور تخریب می‌شوند. اما در یک برنامه ویندوزی تا زمانیکه یک فرم باز است، اشیاء آن تخریب نخواهند شد. بنابراین مدیریت context در برنامه‌های ویندوزی «دستی» است. در زمان شروع فرم context شروع خواهد شد، زمان تخریب/بستن آن، با بستن یا dispose یک context، خودبخود اتصالات هم قطع خواهند شد.
در متد Program.Main هم می‌تونید تنظیمات اولیه ObjectFactory رو قرار بدید (شبیه به Application_Start برنامه‌های وب).
بنابراین در برنامه‌های وب «context/session per http request» داریم؛ در برنامه‌های ویندوزی «context per operation or per form». یعنی می‌تونید بسته به معماری برنامه ویندوزی خود، context را در سطح یک فرم تعریف کنید و مدیریت؛ و یا در سطح یک عملیات کوتاه مانند یک کلیک. تمام مباحث ObjectFactory.GetInstance، uow.SaveChanges و یا Dispose آن هم دستی است و زیر ساختی برای مدیریت خودکار آن‌ها همانند برنامه‌های مثلا ASP.NET MVC وجود ندارد. حداکثر اینکه یک سری base class را شبیه به مثال Web forms زده شده تهیه کنید، تا میزان کدهای تکراری را کاهش بدید. 
مطالب دوره‌ها
کار با RavenDB از طریق REST API آن
در این قسمت قصد داریم برخلاف رویه معمول کار با RavenDB که از طریق کتابخانه‌های کلاینت آن انجام می‌شود، با استفاده از REST API آن، ساز و کار درونی آن‌را بیشتر بررسی کنیم.


REST چیست؟

برای درک ساختار پشت صحنه RavenDB نیاز است با مفهوم REST آشنا باشیم؛ زیرا سرور این بانک اطلاعاتی، خود را به صورت یک RESTful web service در اختیار مصرف کنندگان قرار می‌دهد.
REST مخفف representational state transfer است و این روزها هر زمانیکه صحبت از آن به میان می‌آید منظور یک RESTful web service است که با استفاده از تعدادی HTTP Verb استاندارد می‌توان با آن کار کرد؛ مانند GET، POST، PUT و DELETE. با استفاده از GET‌، یک منبع ذخیره شده بازگشت داده می‌شود. با استفاده از فعل PUT، اطلاعاتی به منابع موجود اضافه و یا جایگزین می‌شوند. POST نیز مانند PUT است با این تفاوت که نوع اطلاعات ارسالی آن اهمیتی نداشته و تفسیر آن به سرور واگذار می‌شود. از DELETE نیز برای حذف یک منبع استفاده می‌گردد.


چند مثال
فرض کنید REST API برنامه‌ای از طریق آدرس http://myapp.com/api/questions در اختیار شما قرار گرفته است. در این آدرس، به questions منابع یا Resource گفته می‌شود. اگر دستور GET پروتکل HTTP بر روی این آدرس اجرا شود، انتظار ما این است که لیست تمام سؤالات بازگشت داده شود و اگر از دستور POST استفاده شود، باید یک سؤال جدید به مجموعه منابع موجود اضافه گردد.
اکنون آدرس http://myapp.com/api/questions/1 را درنظر بگیرید. در اینجا عدد یک معادل Id اولین سؤال ثبت شده است. بر اساس این آدرس خاص، اینبار اگر دستور GET صادر شود، تنها اطلاعات سؤال یک بازگشت داده خواهد شد و یا اگر از دستور PUT استفاده شود، اطلاعات سؤال یک با مقدار جدید ارسالی جایگزین می‌شود و یا با فراخوانی دستور DELETE، سؤال شماره یک حذف خواهد گردید.


کار با دستور GET

در ادامه، به مثال قسمت قبل مراجعه کرده و تنها سرور RavenDB را اجرا نمائید (برنامه Raven.Server.exe)، تا در ادامه بتوانیم دستورات HTTP را بر روی آن امتحان کنیم. همچنین نیاز به برنامه معروف فیدلر نیز خواهیم داشت. از این برنامه برای ساخت دستورات HTTP استفاده خواهد شد.
پس از دریافت و نصب فیدلر، برگه Composer آن‌را گشوده و http://localhost:8080/docs/questions/1 را در حالت GET اجرا کنید:


در این حالت دستور بر روی بانک اطلاعاتی اجرا شده و خروجی را در برگه Inspectors آن می‌توان مشاهده کرد:



به علاوه در اینجا یک سری هدر اضافی (یا متادیتا) را هم می‌توان مشاهده کرد که RavenDB جهت سهولت کار کلاینت خود ارسال کرده است:


یک نکته: اگر آدرس http://localhost:8080/docs/questions را اجرا کنید، به معنای درخواست دریافت تمام سؤالات است. اما RavenDB به صورت پیش فرض طوری طراحی شده‌است که تمام اطلاعات را بازگشت ندهد و شعار آن Safe by default است. به این ترتیب مشکلات مصرف حافظه بیش از حد، پیش از بکارگیری یک سیستم در محیط کاری واقعی، توسط برنامه نویس یافت شده و مجبور خواهد شد تا برای نمایش تعداد زیادی رکورد، حتما صفحه بندی اطلاعات را پیاده سازی کرده و هربار تعداد معقولی از رکوردها را واکشی نماید.


کار با دستور PUT

اینبار نوع دستور را به PUT و آدرس را به http://localhost:8080/docs/questions/1 تنظیم می‌کنیم. همچنین در قسمت Request body، مقداری را که قرار است در سؤال یک درج شود، با فرمت JSON وارد می‌کنیم.
برای آزمایش صحت عملکرد آن، مرحله کار با دستور GET را یکبار دیگر تکرار نمائید:


همانطور که مشاهده می‌کنید، تغییر ما در عنوان سؤال یک، با موفقیت اعمال شده است.


کار با دستور POST

در حین کار با دستور PUT، نیاز است حتما Id سؤال مورد نظر برای به روز رسانی (و یا حتی ایجاد نمونه جدید، در صورت عدم وجود) ذکر شود. اگر نیاز است اطلاعاتی به سیستم اضافه شوند و Id آن توسط RavenDB انتساب داده شود، بجای دستور PUT از دستور POST استفاده خواهیم کرد.


مطابق تصویر، اطلاعات شیء مدنظر را با فرمت JSON به آدرس http://localhost:8080/docs/ ارسال خواهیم کرد. در این حالت اگر به برگه‌ی Inspectors مراجعه نمائیم، یک چنین خروجی JSON ایی دریافت می‌گردد:


Key در اینجا شماره منحصربفرد سند ایجاد شده است و برای دریافت آن تنها کافی است که دستور GET را بر روی آدرس زیر که نمایانگر Key دریافتی است، اجرا کنیم:
http://localhost:8080/docs/e0a92054-9003-4dda-84e2-93e83b359102


کار با دستور DELETE

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


بازگشت چندین سند از بانک اطلاعاتی RavenDB

برای نمونه، در فراخوانی‌های Ajaxایی نیاز است چندین رکورد با هم بازگشت داده شوند. برای این منظور باید یک درخواست Post ویژه را مهیا کرد:



در اینجا آدرس ارسال اطلاعات، آدرس خاص http://localhost:8080/queries است. اطلاعات ارسالی به آن، آرایه‌ای از Idهای اسنادی است که به اطلاعات آن‌ها نیاز داریم.


بنابراین برای کار با RavenDB در برنامه‌های وب و خصوصا کدهای سمت کلاینت آن، نیازی به کلاینت یا کتابخانه ویژه‌ای نیست و تنها کافی است یک درخواست Ajax از نوع post را به آدرس کوئری‌های سرور RavenDB ارسال کنیم تا نتیجه نهایی را به شکل JSON دریافت نمائیم.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
AngularJS یک فریم ورک جاوا اسکریپتی، برای ساخت برنامه‌های تک صفحه‌ای سمت کاربر، توسط HTML ،CSS و جاوا اسکریپت است. این فریم ورک، حاوی اجزایی برای data binding، طراحی ماژولار، کار با سرویس‌های سمت سرور وب و امثال آن است. Angular 2 بازنویسی کامل نگارش 1 آن است و اهداف زیر را دنبال می‌کند:
- سرعت بالاتر بارگذاری و اجرای کدها
- استفاده از آخرین فناوری‌های وب مانند ES 6 و وب کامپوننت‌ها با پشتیبانی تا IE 9.
- سطح API آن با طراحی جدید آن، به شدت کاهش یافته و خلاصه شده‌است. همین امر یادگیری آن‌را نیز ساده‌تر می‌کند.

برای یادگیری Angular 2 نیازی به فراگیری Angular 1 نیست. در اینجا با آشنایی با TypeScript، به این نتیجه خواهید رسید که برنامه‌های Angular 2 چیزی بیشتر از یک مثال عملی TypeScript نیستند. زبان TypeScript، زبان اول و توصیه شده‌ی کار با Angular 2 است و مزیت آن دسترسی به تمام قابلیت‌های ES 6 است؛ با این تفاوت که کامپایلر TypeScript قادر است آن‌ها را به ES 5 یا نگارش فعلی جاوا اسکریپت که توسط تمام مرورگرها پشتیبانی می‌شود، ترجمه و تبدیل کند. به این نحو به یک طراحی شیءگرا، مدرن و با قابلیت نگهداری بالا خواهید رسید که با تمام مرورگرهای جدید نیز سازگار است.
بنابراین پیشنیاز‌های اصلی کار با Angular 2، فراگیری ES 6 و TypeScript هستند که دو سری ویژه و مختص به آن‌ها در سایت جاری تهیه شده‌اند:
«مبانی ES 6»
«مبانی TypeScript»

در این قسمت قصد داریم پیشنیازهای دریافت و نصب اجزاء و وابستگی‌های AngularJS 2 را به همراه TypeScript، در ویژوال استودیو 2015 بررسی کنیم. البته استفاده از ویژوال استودیو در اینجا ضروری نیست و اساسا AngularJS وابستگی به ویژوال استودیو ندارد. اگر به دنبال پشتیبانی بهتری از TypeScript هستید، VSCode رایگان، سورس باز و چند سکویی، گزینه‌ی بسیار بهتری است نسبت به ویژوال استودیوی کامل. البته این مورد تعجبی هم ندارد؛ از این جهت که VSCode با TypeScript نوشته شده‌است.


اهمیت آشنایی با npm

اگر در طی سال‌های اخیر با ویژوال استودیو کار کرده باشید، به طور قطع با NuGet نیز آشنا هستید. NuGet به عنوان یک package manager دات نتی شناخته می‌شود و کار آن دریافت وابستگی‌های یک پروژه، از مخزنی مشخص و نصب و به روز رسانی خودکار آن‌ها است. اما این روزها خارج از محیط توسعه‌ی مایکروسافت، مدیرهای بسته‌های دیگری مانند Bower نیز برای نصب و به روز رسانی بسته‌های front end مانند کتابخانه‌های CSS ایی و جاوا اسکریپتی، بسیار رواج پیدا کرده‌اند. Bower یکی از ابزارهای NodeJS است که توسط NPM یا NodeJS Package Manager نصب می‌شود. به این معنا که استفاده از Bower به معنای استفاده از NPM است. پیش از این از NPM صرفا جهت نصب بسته‌های مرتبط با NodeJS استفاده می‌شد، اما در طی یکسال اخیر، استفاده از NPM نیز برای مدیریت بسته‌های front end رواج پیدا کرده‌است و در صدر مدیر بسته‌های نصب کتابخانه‌های front end قرار دارد. همچنین در این حالت شما تنها نیاز به یک ابزار و یک فایل تنظیمات آن خواهید داشت، بجای استفاده از چندین ابزار و چندین فایل تنظیمات جداگانه. به علاوه این روزها انجام کارهای جدی جاوا اسکریپتی بدون دانش NPM دیگر میسر نیست. بهترین ابزارها، کامپایلرها، فشرده کننده‌های front end و امثال آن‌ها، تحت NodeJS اجرا می‌شوند. برای کار با AngularJS 2.0 و دریافت وابستگی‌های آن نیز استفاده از npm، روش پیش فرض و توصیه شده‌است.


کار با npm جهت دریافت وابستگی‌های کتابخانه‌های front end

برای کار با npm یا می‌توان از دستورات خط فرمان آن به صورت مستقیم استفاده کرد و یا از امکانات یکپارچگی آن با ویژوال استودیو نیز بهره گرفت که ما در ادامه از این روش استفاده خواهیم کرد. البته توانمندی‌های خط فرمان npm فراتر است از امکانات توکار VS.NET، اما در اینجا نیازی به آن‌ها نیست و هدف صرفا دریافت و به روز رسانی وابستگی‌های مرتبط است.
ویژوال استودیوی 2015 به همراه ابزارهای NodeJS ارائه می‌شود. اما مشکل اینجا است که این ابزارها هم اکنون قدیمی شده‌اند و تطابقی با آخرین نگارش ابزارهای NodeJS ندارند. برای نمونه حین نصب وابستگی‌‌های AngularJS 2.0 که یکی از آن‌ها RxJS است، یک چنین خروجی را در پنجره‌ی output ویژوال استودیو مشاهده می‌کنید:
 npm WARN engine rxjs@5.0.0-beta.6: wanted: {"npm":">=2.0.0"} (current: {"node":"v0.10.31","npm":"1.4.9"})
به این معنا که نگارش‌های اخیر RxJS نیاز به npm با نگارشی بیشتر از 2 را دارند؛ اما نگارش npm پیش فرض ویژوال استودیو 1.4.9 است (البته باید دقت داشت که من به روز رسانی دوم VS 2015 را هم نصب کرده‌ام). برای رهایی از این مشکل، تنها کافی است تا به ویژوال استودیو اعلام کنیم از نسخه‌ی اصلی خط فرمان npm، بجای نسخه‌ی پیش فرض خودش استفاده کند:


همانطور که در تصویر نیز ملاحظه می‌کنید، به مسیر Tools->Options->Projects and Solutions->External Web Tools مراجعه کرده و متغیر محیطی PATH ویندوز را به ابتدای لیست منتقل کنید. به این صورت ابزارهای به روز شده‌ی خط فرمان، مقدم خواهند شد بر ابزارهای قدیمی به همراه نگارش فعلی ویژوال استودیو.

البته فرض بر این است که آخرین نگارش nodejs را از آدرس https://nodejs.org/en دریافت و نصب کرده‌اید. با نصب آن، برنامه npm، در خط فرمان ویندوز به صورت مستقیم قابل استفاده خواهد بود؛ از این جهت که مسیر آن به PATH ویندوز اضافه شده‌است:



افزودن فایل project.json به پروژه

تا اینجا فرض بر این است که nodejs را از سایت آن دریافت و نصب کرده‌اید. همچنین متغیر PATH را در External Web Tools ویژوال استودیو به ابتدای لیست آن منتقل کرده‌اید تا همواره از آخرین نگارش npm نصب شده‌ی در سیستم، استفاده شود.
پس از آن همانند نیوگت که از فایل xml ایی به نام packages.config برای ثبت آخرین تغییرات خودش استفاده می‌کند، npm نیز از فایلی به نام package.json برای ذکر وابستگی‌های مورد نیاز پروژه بهره می‌برد. برای افزودن آن، از منوی Project گزینه‌ی Add new item را انتخاب کرده و سپس npm را در آن جستجو کنید:


در اینجا نام پیش فرض package.json را پذیرفته و این فایل را به پروژه و ریشه‌ی آن اضافه کنید.
اگر گزینه‌ی فوق را در لیست Add new item مشاهده نمی‌کنید، مهم نیست. یک فایل متنی را به نام package.json به ریشه‌ی پروژه‌ی جاری اضافه کنید.
در ادامه محتوای این فایل را به نحو ذیل تغییر دهید:
{
    "name": "asp-net-mvc5x-angular2x",
    "version": "1.0.0",
    "author": "DNT",
    "description": "",
    "scripts": {},
    "license": "Apache-2.0",
    "dependencies": {
        "jquery": "2.2.3",
        "angular2": "2.0.0-beta.15",
        "systemjs": "^0.19.26",
        "es6-promise": "^3.1.2",
        "es6-shim": "^0.35.0",
        "reflect-metadata": "0.1.2",
        "rxjs": "5.0.0-beta.2",
        "zone.js": "^0.6.12",
        "bootstrap": "^3.3.6"
    },
    "devDependencies": {
        "typescript": "^1.8.9",
        "typings": "^0.8.1"
    },
    "repository": {
    }
}
این فایل تنظیمات، سبب بارگذاری خودکار وابستگی‌های مورد نیاز AngularJS 2.0 در ویژوال استودیو می‌شود.

چند نکته:
- هر زمانیکه این فایل را ذخیره کنید، بلافاصله کار دریافت این بسته‌ها از اینترنت آغاز خواهد شد. جزئیات آن‌را می‌توان در پنجره‌ی output ویژوال استودیو مشاهده کرد (و حتما این‌کار را جهت دیباگ کار انجام دهید. بسیاری از مشکلات و خطاها، در اینجا لاگ می‌شوند). بنابراین اگر قصد دارید کار همگام سازی تغییرات را انجام دهید، فقط کافی است دکمه‌های ctrl+s یا save را بر روی این فایل اعمال کنید.
- کاری که دکمه‌ی ctrl+s در این فایل انجام می‌دهد، اعمال خودکار دستور npm install بر روی پوشه‌ای است که package.json در آن قرار دارد. بنابراین برای دریافت این بسته‌ها، روش خط فرمان آن، ابتدا وارد شدن به ریشه‌ی پروژه‌ی جاری و سپس صدور دستور npm install است (که نیازی به آن نیست و ویژوال استودیو این‌کار را به صورت خودکار انجام می‌دهد).
- بسته‌های دریافت شده، در پوشه‌ای به نام node_modules در ریشه‌ی پروژه ذخیره می‌شوند:


برای مشاهده‌ی آن‌ها می‌توان بر روی دکمه‌ی show all files در solution explorer کلیک نمائید.

- درون فایل package.json، پشتیبانی کاملی از intellisense وجود دارد. برای مثال اگر علاقمند بودید تا آخرین نگارش AngularJS را مشاهده کنید، پس از خاصیت angular2 در تنظیمات فوق، " را تایپ کنید تا منوی تکمیل کننده‌ای ظاهر شود:


بدیهی است این منو جهت دریافت آخرین اطلاعات، نیاز به اتصال به اینترنت را دارد.

البته همیشه آخرین شماره نگارش AngularJS 2.0 را در این آدرس نیز می‌توانید مشاهده کنید: CHANGELOG.md

- در این فایل شماره نگارش آغاز شده‌ای با ^ مانند "3.1.2^" به این معنا است که اجازه‌ی نصب نگارش‌های جدیدتری از 3.1.2 نیز داده شده‌است.


به روز رسانی کامپایلر TypeScript

نیاز است یکبار مطلب «مبانی TypeScript؛ تنظیمات TypeScript در ویژوال استودیو» را جهت آشنایی با نحوه‌ی به روز رسانی اجزای مرتبط با TypeScript مطالعه کنید.


افزودن فایل tsconfig.json به پروژه

مرحله‌ی بعد شروع به کار با AngularJS و TypeScript، انجام تنظیمات کامپایلر TypeScript است. برای این منظور از منوی پروژه، گزینه‌ی Add new item، عبارت typescript را جستجو کرده و در لیست ظاهر شده، گزینه‌ی TypeScript JSON Configuration File را با نام پیش فرض آن انتخاب کنید:


اگر این گزینه‌ی ویژه را در لیست add new items ندارید، مهم نیست. یک فایل متنی را به نام tsconfig.json به ریشه‌ی پروژه‌ی جاری اضافه کنید.
پس از افزودن فایل tsconfig.json به ریشه‌ی سایت، تنظیمات آن‌را به نحو ذیل تغییر دهید:
{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es5",
        "module": "system",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "noEmitOnError": true,
        "removeComments": false,
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    },
    "exclude": [
        "node_modules",
        "wwwroot",
        "typings/main",
        "typings/main.d.ts"
    ]
}
در مورد جزئیات این تنظیمات در سری «مبانی TypeScript» و «تنظیمات کامپایلر TypeScript» بیشتر بحث شده‌است. فایل ویژه‌ی tsconfig.json تنظیمات پیش فرض ویژوال استودیو را جهت کار با TypeScript بازنویسی می‌کند. بنابراین زمانیکه از این فایل استفاده می‌شود، دیگر نیازی نیست به گزینه‌ی مختلف پروژه، جهت تنظیم TypeScript مراجعه کرد.
برای نمونه خاصیت compileOnSave سبب خواهد شد تا پس از ذخیره سازی یک فایل ts، بلافاصله فایل js معادل آْن تولید شود. نوع ماژول‌ها در اینجا به system.js تنظیم شده‌است و خروجی کدهای js آن از نوع ES 5 درنظر گرفته شده‌است. همچنین فعال سازی decorators نیز در اینجا صورت گرفته‌است. از این جهت که AngularJS2 استفاده‌ی گسترده‌ای را از این مفهوم، انجام می‌دهد.
در قسمت excludes به کامپایلر TypeScript اعلام شده‌است تا از یک سری از مسیرها مانند پوشه‌ی node_modules که پیشتر آن‌را تنظیم و دریافت کردیم، صرفنظر کند.


خلاصه‌ی بحث

تا اینجا با نحوه‌ی نصب پیشنیازهای AngularJS 2 و همچنین TypeScript آشنا شدیم. به صورت خلاصه:
- nmp اصلی را از سایت nodejs دریافت و نصب کنید.
- متغیر PATH را در ویژوال استودیو، به ابتدای لیست ابزارهای خارجی وب آن منتقل کنید تا از npm اصلی استفاده کند.
- فایل project.json را با محتوای ذکر شده، به ریشه‌ی پروژه اضافه کنید. سپس یکبار آن‌را save کنید تا وابستگی‌ها را از اینترنت دریافت کند.
- اطمینان حاصل کنید که آخرین کامپایلر TypeScript را نصب کرده‌اید.
- فایل tsconfig.json را با محتوای ذکر شده، به ریشه‌ی پروژه اضافه کنید.
مطالب
سایت‌های مهمی که از ASP.NET MVC استفاده می‌کنند

عموما استفاده وسیع از نگارش‌های مختلف ASP.NET مربوط به اینترانت‌های شرکت‌های خصوصی و دولتی است. برنامه‌هایی که هیچ وقت رنگ آسمان را هم نخواهند دید و کسی از آمار یا وجود آن‌ها مطلع نخواهد شد. اما در این بین هستند سایت‌های عمومی که از این فناوری‌ها استفاده می‌کنند. مهم‌ترین و پرترافیک‌ترین سایت‌هایی که در حال حاضر از ASP.NET MVC کمک می‌گیرند شامل موارد زیر هستند:
جالب اینجا است که اخیرا سایت msnbc استفاده وسیعی از RavenDB را هم شروع کرده است.

سایر منابع:

سؤال: چگونه تشخیص دهیم یک سایت از ASP.NET MVC استفاده می‌کند؟

ابتدا افزونه Server Spy را نصب کنید. این افزونه می‌تواند وب سروری را که یک سایت هم اکنون مورد استفاده قرار داده، تشخیص دهد. اگر IIS بود، یعنی این سایت از یکی از مشتقات ASP یا ASP.NET استفاده می‌کند. اگر پسوند صفحات به asp ختم شده بود، ASP‌ کلاسیک دهه نود است. در غیراینصورت یا Web forms است یا MVC. در این حالت به سورس صفحه مراجعه کنید. اگر از ViewState خبری نبود یعنی ASP.NET MVC است.
البته این روش در 90 درصد موارد جواب می‌دهد. می‌شود هدر ارسالی وب سرور را کلا تغییر داد. یعنی ضرورتی ندارد که یک سایت استفاده کننده از IIS حتما اعلام کند که از این وب سرور خاص استفاده می‌کند. یا در ASP.NET Web forms می‌شود ViewState را با ترفندهایی حذف کرد. اما ... این مسایل همه گیر نیست و روش‌ ذکر شده شناسایی، در اکثر موارد جواب می‌دهد.

مطالب
آموزش Coded UI Test #1
اکثر برنامه نویسان با مباحث Unit Testing  آشنایی دارند و بعضی برنامه نویسان هم، از این مباحث در پروژه‌های خود استفاده می‌کنند. ساختار الگو‌های MVC و MVVM به گونه ای است که به راحتی می‌توان برای این گونه پروژه‌ها Unit Test بنویسیم. در پروژه‌های MVC به دلیل عدم وابستگی بین View و Controller به طور مستقیم، امکان نوشتن Unit Test برای Controller امکان پذیر است و از طرفی در الگوی MVVM به دلیل منطق وجود ViewModel می‌توان برای اینگونه پروژه‌ها نیز Unit Test نوشت. اما ساختار سایر پروژه‌ها به گونه ای است که نوشتن Unit Test برای آن‌ها مشکل و در بعضی مواقع غیر ممکن می‌شود. برای مثال در پروژهای Desktop نظیر Windows Application و حتی وب به صورت Asp.Net Web Forms به دلیل وابستگی مستقیم کنترل‌های UI به منطق اجرای برنامه، طراحی و نوشتن Unit Test بسیار مشکل و در برخی موارد بیهوده است. در VS.Net ابزاری وجود دارد به نام Coded UI Test که برای تست این گونه پروژه‌ها طراحی شده است و همان طور که از نامش پیداست صرفا برای تست کنترل‌های UI و رویداد‌های کنترل‌ها و تست درستی برنامه با توجه به داده‌های ورودی به کار می‌رود. یکی از مزیت‌های اصلی آن تسریع عملیات تست در حجم بالا است و زمان ایجاد unit  test را به حداقل می‌رساند. مزیت دوم آن امکان ایجاد unit test برای پروژه‌های که در مراحل پایانی تولید هستند ولی هنوز اطمینانی به عملکرد صحیح برنامه در حالات مختلف نیست.
 در این پست قصد دارم روش استفاده از این گونه پروژه‌های تست را با ذکر یک مثال بررسی کنیم و در پست‌های بعدی به بررسی امکانات دیگر خواهیم پرداخت.

نکته : فقط در Vs.Net با نسخه‌های Ultimate و Premium می‌تونید از Code UI Test استفاده کنید که البته به دلیل اینکه در ایران پیدا کردن نسخه‌های دیگر Vs.Net به غیر از Ultimate سخت‌تر است به طور قطع این محدودیت برای برنامه نویسان ما وجود نخواهد داشت. برای اینکه از نسخه Vs.Net خود اطمینان حاصل کنید از منوی Help گزینه About Microsoft Visual Studio رو انتخاب کنید. پنجره ای به شکل زیر مشاهده خواهید کرد که در آن مشخصات کامل Vs.Net ذکر شده است.

در این مرحله قصد داریم برای فرم زیر Unit Test طراحی کنیم. پروژه به صورت زیر است:

کاملا واضح است که در این فرم دو عدد به عنوان ورودی دریافت می‌شود و بعد از کلیک بر روی CalculateSum نتیجه در textbox سوم نمایش داده می‌شود. برای تست عملکرد صحیح فرم بالا ایتدا به Solution مورد نظر از منوی test Project یک Coded UI Test Project اضافه می‌کنیم. به دلیل اینکه این قبلا در این Solution پروژه تست از نوع Coded UI Test نبود بلافاصله یک پنجره نمایش داده می‌شود. مطمئن شوید گزینه اول انتخاب شده و بعد بر روی Ok کلیک کنید.(گزینه اول به معنی است که قصد داریم عملیات مورد نظر بر روی UI را رکورد کنیم و گزینه دوم به معنی است که قصد داریم از عملیات رکورد شده قبلی استفاده کنیم).  یک کلاس به نام CodeUITest1 به همراه یک متد تست به نام CodedUITestMethod1 ساخته می‌شود. اولین چیزی که جلب توجه می‌کند این است که این کلاس به جای TestClassAttribute دارای نشان CodeUITestAttrbiute است. در گوشه سمت راست Vs.Net خود یک پنجره کوچک به نام UI Map Test Builder مانند شکل زیر خواهید دید.

دکمه قرمز رنگ به نام Record Button است و عملیات تست را رکورد خواهد کرد. دکمه دایره ای به رنگ مشکی برای تعیین Assertion به کار می‌رود. و در نهایت گزینه آخر کد‌های مورد نظر مراحل قبل را به صورت خودکار تولید خواهد کرد.

#روش کار

روش کار به این صورت است که ابتدا شما مراحل تست خود را شبیه سازی خواهید کرد و بعد از آن Test Builder مراحل تست شما را به صورت کامل به صورت کد‌های قابل فهم تولید خواهد کرد. (دقیقا شبیه به ایجاد UnitTest به روش Arrange/Act/Assert است با این تفاوت که این مراحل توسط UI Map رکورد شده و نیازی به کد نویسی ندارد). در پایان باید یک Data Driven Coded UI Test طراحی کنید تا بتوانید از این مراحل رکورد استفاده نمایید. 

#چگونگی شبیه سازی :

پروژه را اجرا نمایید. زمانی که فرم مورد نظر  ظاهر شد بر روی گزینه Record در TestBuilder کلیک کنید. عملیات ذخیره سازی شروع شده است. در نتیجه به فرم مربوطه رفته و در Textbox اول مقدار 10 و در textbox دوم مقدار 5 را وارد نمایید. با کلیک بر روی دکمه CalculateSum مقدار 15 نمایش داده خواهد شد. از برنامه خارج شوید و بعد بر روی گزینه Generate Code در TestBuilder کلیک کنید با از کلید‌های ترکیبی Alt + G استفاده نمایید.(اگر در این مرحله، از برنامه خارج نشده باشید با خطا مواجه خواهید شد.) در پنجره نمایش داده شده یک نام به متد اختصاص دهید. عملیات تولید کد شروع خواهد شد. بعد کدی مشابه زیر را در متد مربوطه مشاهده خواهید کرد.

   [TestMethod]
        public void CodedUITestMethod1()
        {            
            this.UIMap.CalculateSum();
            this.UIMap.txtSecondValueMustBe10();
        }
بخشی از سورس کد تولید شده برای متد CalculateSum به شکل زیر است:
        public void CodedUITestMethod1 ()
        {
            #region Variable Declarations
            WinEdit uITxtFirstNumberEdit = this.UIدوعددصحیحواردنماییدWindow.UITxtFirstNumberWindow.UITxtFirstNumberEdit;
            WinEdit uITxtSecondNumberEdit = this.UIدوعددصحیحواردنماییدWindow.UITxtSecondNumberWindow.UITxtSecondNumberEdit;
            WinButton uICalculateSumButton = this.UIدوعددصحیحواردنماییدWindow.UICalculateSumWindow.UICalculateSumButton;
            #endregion

            // Type '10' in 'txtFirstNumber' text box
            uITxtFirstNumberEdit.Text = this.CalculateSumParams.UITxtFirstNumberEditText;

            // Type '{Tab}' in 'txtFirstNumber' text box
            Keyboard.SendKeys(uITxtFirstNumberEdit, this.CalculateSumParams.UITxtFirstNumberEditSendKeys, ModifierKeys.None);

            // Type '10' in 'txtSecondNumber' text box
            uITxtSecondNumberEdit.Text = this.CalculateSumParams.UITxtSecondNumberEditText;

            // Click 'Calculate Sum' button
            Mouse.Click(uICalculateSumButton, new Point(83, 12));

            // Type '10' in 'txtFirstNumber' text box
            uITxtFirstNumberEdit.Text = this.CalculateSumParams.UITxtFirstNumberEditText1;

            // Type '{Tab}' in 'txtFirstNumber' text box
            Keyboard.SendKeys(uITxtFirstNumberEdit, this.CalculateSumParams.UITxtFirstNumberEditSendKeys1, ModifierKeys.None);

            // Type '10' in 'txtSecondNumber' text box
            uITxtSecondNumberEdit.Text = this.CalculateSumParams.UITxtSecondNumberEditText1;

            // Type '{Tab}' in 'txtSecondNumber' text box
            Keyboard.SendKeys(uITxtSecondNumberEdit, this.CalculateSumParams.UITxtSecondNumberEditSendKeys, ModifierKeys.None);

            // Click 'Calculate Sum' button
            Mouse.Click(uICalculateSumButton, new Point(49, 11));

            // Type '10' in 'txtFirstNumber' text box
            uITxtFirstNumberEdit.Text = this.CalculateSumParams.UITxtFirstNumberEditText2;

            // Type '{Tab}' in 'txtFirstNumber' text box
            Keyboard.SendKeys(uITxtFirstNumberEdit, this.CalculateSumParams.UITxtFirstNumberEditSendKeys2, ModifierKeys.None);

            // Type '5' in 'txtSecondNumber' text box
            uITxtSecondNumberEdit.Text = this.CalculateSumParams.UITxtSecondNumberEditText2;

            // Type '{Tab}' in 'txtSecondNumber' text box
            Keyboard.SendKeys(uITxtSecondNumberEdit, this.CalculateSumParams.UITxtSecondNumberEditSendKeys1, ModifierKeys.None);

            // Click 'Calculate Sum' button
            Mouse.Click(uICalculateSumButton, new Point(74, 16));
        }
همان طور که می‌بینید تمام مراحل تست شما رکورد شده است و به صورت کد قابل فهم بالا ایجاد شده است.
چگونگی ایجاد Assertion
اگر به کد متد تست CodedUITestMethod1 در بالا دقت کنید یک متد به صورت this.UIMap.txtSecondValueMustBe10 فراخوانی شده است. این در واقع یک Assertion  است که در هنگام عملیات رکورد ایجاد کردم و به این معنی است که مقدار TextBox دوم حتما باید 10 باشد. حال روش تولید Assertion‌ها را بررسی خواهیم کرد.
بعد از شروع شدن مرحله رکورد اگر قصد دارید برای یک کنترل خاص Assert بنویسید، دکمه assertion (به رنگ مشکی و به صورت دایره است) را بر روی کنترل مورد نظر drag&drop کنید. یک border آبی برای کنترل مورد نظر ایجاد خواهد شد:

به محض اتمام عملیات drag&drop منوی زیر ظاهر خواهد شد:

از گزینه Add Assertion استفاده کنید و برای کنترل مورد نظر یک assert بنویسید. در شکل زیر یک assert برای textbox دوم نوشتم به صورتی که مقدار آن باید با 5 برابر باشد.


از گزینه آخر برای نمایش پیغام مورد نظر خودتون در هنگامی که aseert با شکست مواجه می‌شود استفاده کنید.
کد تولید شده زیر برای عملیات assert بالا است:
  public void txtSecondValueMustBe10()
        {
            #region Variable Declarations
            WinEdit uITxtSecondNumberEdit = this.UIدوعددصحیحواردنماییدWindow.UITxtSecondNumberWindow.UITxtSecondNumberEdit;
            #endregion

            // Verify that the 'ControlType' property of 'txtSecondNumber' text box equals '10'
            Assert.AreEqual(this.txtSecondValueMustBe10ExpectedValues.UITxtSecondNumberEditControlType, uITxtSecondNumberEdit.ControlType.ToString());
        }

مرحله اول انجام شد. برای تست این مراحل باید یک Data DrivenTest بسازید که در پست بعدی به صورت کامل شرح داده خواهد شد.
نظرات مطالب
ASP.NET MVC #6
این بحث رو من جاهای دیگه هم مطرح کردم ولی جواب مناسبی نگرفتم. ببینید توی web form طراح میتونه بیاد UI رو بوسیله کنترل ها طراحی کنه ، بوسیله css به ظاهر اونها برسه و ... بدون اینکه هیچ دانشی از سی شارپ یا Asp.net داشته باشه. بعدش هم برنامه نویس کارش رو انجام بده. البته در این مرحله چون توی کد برنامه نیاز به ارتباط با عناصر صفحه وجود داره وابستگی پیش میاد.
ولی به نظر من توی mvc قضیه برعکسه. وقتی قرار واسط کاربر طراحی بشه ، و از روش ترجیح داده شده strongly type view استفاده بشه ( که واقعا جالبه و کار باهاش راحت) ، طراح حتی باید مدل ها رو بشناسه، یا حتی در روش باز هم توصیه شده ، کلاس های سی شارپی به عنوان viewModel تعریف کنه و view رو با اونها تشکیل بده. در حالیکه در مرحله کدنویسی دیگه به عناصر صفحه وابستگی وجود نداره.
منظورم اینه که توی هرکدوم به نظرم وابستگی وجود داره، ولی تو مراحل متفاوت.اینطوره؟
مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت پنجم
مقدمات ساخت بلاگ مبتنی بر ember.js در قسمت قبل به پایان رسید. در این قسمت صرفا قصد داریم بجای استفاده از HTML 5 local storage از یک REST web service مانند یک ASP.NET Web API Controller و یا یک ASP.NET MVC Controller استفاده کنیم و اطلاعات نهایی را به سرور ارسال و یا از آن دریافت کنیم.


تنظیم Ember data برای کار با سرور

Ember data به صورت پیش فرض و در پشت صحنه با استفاده از Ajax برای کار با یک REST Web Service طراحی شده‌است و کلیه تبادلات آن نیز با فرمت JSON انجام می‌شود. بنابراین تمام کدهای سمت کاربر قسمت قبل نیز در این حالت کار خواهند کرد. تنها کاری که باید انجام شود، حذف تنظیمات ابتدایی آن برای کار با HTML 5 local storage است.
برای این منظور ابتدا فایل index.html را گشوده و سپس مدخل localstorage_adapter.js را از آن حذف کنید:
 <!--<script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script>-->
همچنین دیگر نیازی به store.js نیز نمی‌باشد:
 <!--<script src="Scripts/App/store.js" type="text/javascript"></script>-->

اکنون برنامه را اجرا کنید، چنین پیام خطایی را مشاهده خواهید کرد:

همانطور که عنوان شد، ember data به صورت پیش فرض با سرور کار می‌کند و در اینجا به صورت خودکار، یک درخواست Get را به آدرس http://localhost:25918/posts جهت دریافت آخرین مطالب ثبت شده، ارسال کرده‌است و چون هنوز وب سرویسی در برنامه تعریف نشده، با خطای 404 و یا یافت نشد، مواجه شده‌است.
این درخواست نیز بر اساس تعاریف موجود در فایل Scripts\Routes\posts.js، به سرور ارسال شده‌است:
Blogger.PostsRoute = Ember.Route.extend({
    model: function () {
        return this.store.find('post');
    }
});
Ember data شبیه به یک ORM عمل می‌کند. تنظیمات ابتدایی آن‌را تغییر دهید، بدون نیازی به تغییر در کدهای اصلی برنامه، می‌تواند با یک منبع داده جدید کار کند.


تغییر تنظیمات پیش فرض آغازین Ember data

آدرس درخواستی http://localhost:25918/posts به این معنا است که کلیه درخواست‌ها، به همان آدرس و پورت ریشه‌ی اصلی سایت ارسال می‌شوند. اما اگر یک ASP.NET Web API Controller را تعریف کنیم، نیاز است این درخواست‌ها، برای مثال به آدرس api/posts ارسال شوند؛ بجای /posts.
برای این منظور پوشه‌ی جدید Scripts\Adapters را ایجاد کرده و فایل web_api_adapter.js را با این محتوا به آن اضافه کنید:
 DS.RESTAdapter.reopen({
      namespace: 'api'
});
سپس تعریف مدخل آن‌را نیز به فایل index.html اضافه نمائید:
 <script src="Scripts/Adapters/web_api_adapter.js" type="text/javascript"></script>
تعریف فضای نام در اینجا سبب خواهد شد تا درخواست‌های جدید به آدرس api/posts ارسال شوند.


تغییر تنظیمات پیش فرض ASP.NET Web API

در سمت سرور، بنابر اصول نامگذاری خواص، نام‌ها با حروف بزرگ شروع می‌شوند:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}
اما در سمت کاربر و کدهای اسکریپتی، عکس آن صادق است. به همین جهت نیاز است که CamelCasePropertyNamesContractResolver را در JSON.NET تنظیم کرد تا به صورت خودکار اطلاعات ارسالی به کلاینت‌ها را به صورت camel case تولید کند:
using System;
using System.Web.Http;
using System.Web.Routing;
using Newtonsoft.Json.Serialization;
 
namespace EmberJS03
{
    public class Global : System.Web.HttpApplication
    {
 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}",
               defaults: new { id = RouteParameter.Optional }
               );
 
            var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}


نحوه‌ی صحیح بازگشت اطلاعات از یک ASP.NET Web API جهت استفاده در Ember data

با تنظیمات فوق، اگر کنترلر جدیدی را به صورت ذیل جهت بازگشت لیست مطالب تهیه کنیم:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public IEnumerable<Post> Get()
        {
            return DataSource.PostsList;
        } 
    }
}
با یک چنین خطایی در سمت کاربر مواجه خواهیم شد:
  WARNING: Encountered "0" in payload, but no model was found for model name "0" (resolved model name using DS.RESTSerializer.typeForRoot("0"))
این خطا از آنجا ناشی می‌شود که Ember data، اطلاعات دریافتی از سرور را بر اساس قرارداد JSON API دریافت می‌کند. برای حل این مشکل راه‌حل‌های زیادی مطرح شده‌اند که تعدادی از آن‌ها را در لینک‌های زیر می‌توانید مطالعه کنید:
http://jsonapi.codeplex.com
https://github.com/xqiu/MVCSPAWithEmberjs
https://github.com/rmichela/EmberDataAdapter
https://github.com/MilkyWayJoe/Ember-WebAPI-Adapter
http://blog.yodersolutions.com/using-ember-data-with-asp-net-web-api
http://emadibrahim.com/2014/04/09/emberjs-and-asp-net-web-api-and-json-serialization

و خلاصه‌ی آن‌ها به این صورت است:
خروجی JSON تولیدی توسط ASP.NET Web API چنین شکلی را دارد:
[
  {
    Id: 1,
    Title: 'First Post'
  }, {
    Id: 2,
    Title: 'Second Post'
  }
]
اما Ember data نیاز به یک چنین خروجی دارد:
{
  posts: [{
    id: 1,
    title: 'First Post'
  }, {
    id: 2,
    title: 'Second Post'
  }]
}
به عبارتی آرایه‌ی مطالب را از ریشه‌ی posts باید دریافت کند (مطابق فرمت JSON API). برای انجام اینکار یا از لینک‌های معرفی شده استفاده کنید و یا راه حل ساده‌ی ذیل هم پاسخگو است:
using System.Web.Http;
using EmberJS03.Models;
 
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public object Get()
        {
            return new { posts = DataSource.PostsList };
        }
    }
}
در اینجا ریشه‌ی posts را توسط یک anonymous object ایجاد کرده‌ایم.
اکنون اگر برنامه را اجرا کنید، در صفحه‌ی اول آن، لیست عناوین مطالب را مشاهده خواهید کرد.


تاثیر قرارداد JSON API در حین ارسال اطلاعات به سرور توسط Ember data

در تکمیل کنترلرهای Web API مورد نیاز (کنترلرهای مطالب و نظرات)، نیاز به متدهای Post، Update و Delete هم خواهد بود. دقیقا فرامین ارسالی توسط Ember data توسط همین HTTP Verbs به سمت سرور ارسال می‌شوند. در این حالت اگر متد Post کنترلر نظرات را به این شکل طراحی کنیم:
 public HttpResponseMessage Post(Comment comment)
کار نخواهد کرد؛ چون مطابق فرمت JSON API ارسالی توسط Ember data، یک چنین شیء JSON ایی را دریافت خواهیم کرد:
{"comment":{"text":"data...","post":"3"}}
بنابراین Ember data چه در حین دریافت اطلاعات از سرور و چه در زمان ارسال اطلاعات به آن، اشیاء جاوا اسکریپتی را در یک ریشه‌ی هم نام آن شیء قرار می‌دهد.
برای پردازش آن، یا باید از راه حل‌های ثالث مطرح شده در ابتدای بحث استفاده کنید و یا می‌توان مطابق کدهای ذیل، کل اطلاعات JSON ارسالی را توسط کتابخانه‌ی JSON.NET نیز پردازش کرد:
namespace EmberJS03.Controllers
{
    public class CommentsController : ApiController
    {
        public HttpResponseMessage Post(HttpRequestMessage requestMessage)
        {
            var jsonContent = requestMessage.Content.ReadAsStringAsync().Result;
            // {"comment":{"text":"data...","post":"3"}}
            var jObj = JObject.Parse(jsonContent);
            var comment = jObj.SelectToken("comment", false).ToObject<Comment>();


            var id = 1;
            var lastItem = DataSource.CommentsList.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            comment.Id = id;
            DataSource.CommentsList.Add(comment);

            // ارسال آی دی با فرمت خاص مهم است
            return Request.CreateResponse(HttpStatusCode.Created, new { comment = comment });
        }
    }
}
در اینجا توسط requestMessage به محتوای ارسال شده‌ی به سرور که همان شیء JSON ارسالی است، دسترسی خواهیم داشت. سپس متد JObject.Parse، آن‌را به صورت عمومی تبدیل به یک شیء JSON می‌کند و نهایتا با استفاده از متد SelectToken آن می‌توان ریشه‌ی comment و یا در کنترلر مطالب، ریشه‌ی post را انتخاب و سپس تبدیل به شیء Comment و یا Post کرد.
همچنین فرمت return نهایی هم مهم است. در این حالت خروجی ارسالی به سمت کاربر، باید مجددا با فرمت JSON API باشد؛ یعنی باید comment اصلاح شده را به همراه ریشه‌ی comment ارسال کرد. در اینجا نیز anonymous object تهیه شده، چنین کاری را انجام می‌دهد.


Lazy loading در Ember data

تا اینجا اگر برنامه را اجرا کنید، لیست مطالب صفحه‌ی اول را مشاهده خواهید کرد، اما لیست نظرات آن‌ها را خیر؛ از این جهت که ضرورتی نداشت تا در بار اول ارسال لیست مطالب به سمت کاربر، تمام نظرات متناظر با آن‌ها را هم ارسال کرد. بهتر است زمانیکه کاربر یک مطلب خاص را مشاهده می‌کند، نظرات خاص آن‌را به سمت کاربر ارسال کنیم.
در تعاریف سمت کاربر Ember data، پارامتر دوم رابطه‌ی hasMany که با async:true مشخص شده‌است، دقیقا معنای lazy loading را دارد.
Blogger.Post = DS.Model.extend({
   title: DS.attr(),
   body: DS.attr(),
   comments: DS.hasMany('comment', { async: true } /* lazy loading */)
});
در سمت سرور، دو راه برای فعال سازی این lazy loading تعریف شده در سمت کاربر وجود دارد:
الف) Idهای نظرات هر مطلب را به صورت یک آرایه، در بار اول ارسال لیست نظرات به سمت کاربر، تهیه و ارسال کنیم:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
 
        // lazy loading via an array of IDs
        public int[] Comments { set; get; } 
    }
}
در اینجا خاصیت Comments، تنها کافی است لیستی از Idهای نظرات مرتبط با مطلب جاری باشد. در این حالت در سمت کاربر اگر مطلب خاصی جهت مشاهده‌ی جزئیات آن انتخاب شود، به ازای هر Id ذکر شده، یکبار دستور Get صادر خواهد شد.
ب) این روش به علت تعداد رفت و برگشت بیش از حد به سرور، کارآیی آنچنانی ندارد. بهتر است جهت مشاهده‌ی جزئیات یک مطلب، تنها یکبار درخواست Get کلیه نظرات آن صادر شود.
برای اینکار باید مدل برنامه را به شکل زیر تغییر دهیم:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
 
        // load related models via URLs instead of an array of IDs
        // ref. https://github.com/emberjs/data/pull/1371
        public object Links { set; get; }
 
        public Post()
        {
            Links = new { comments = "comments" }; // api/posts/id/comments
        }
    }
}
در اینجا یک خاصیت جدید به نام Links ارائه شده‌است. نام Links در Ember data استاندارد است و از آن برای دریافت کلیه اطلاعات لینک شده‌ی به یک مطلب استفاده می‌شود. با تعریف این خاصیت به نحوی که ملاحظه می‌کنید، اینبار Ember data تنها یکبار درخواست ویژه‌ای را با فرمت api/posts/id/comments، به سمت سرور ارسال می‌کند. برای مدیریت آن، قالب مسیریابی پیش فرض {api/{controller}/{id را می‌توان به صورت {api/{controller}/{id}/{name اصلاح کرد:
namespace EmberJS03
{
    public class Global : System.Web.HttpApplication
    {
 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}/{name}",
               defaults: new { id = RouteParameter.Optional, name = RouteParameter.Optional }
               );
 
            var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}
اکنون دیگر درخواست جدید api/posts/3/comments با پیام 404 یا یافت نشد مواجه نمی‌شود.
در این حالت در طی یک درخواست می‌توان کلیه نظرات را به سمت کاربر ارسال کرد. در اینجا نیز ذکر ریشه‌ی comments همانند ریشه posts، الزامی است:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        //GET api/posts/id
        public object Get(int id)
        {
            return
                new
                {
                    posts = DataSource.PostsList.FirstOrDefault(post => post.Id == id),
                    comments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList()
                };
        }
    }
}


پردازش‌های async و متد transitionToRoute در Ember.js

اگر متد حذف مطالب را نیز به کنترلر Posts اضافه کنیم:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = DataSource.PostsList.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            DataSource.PostsList.Remove(item);

            //حذف کامنت‌های مرتبط
            var relatedComments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList();
            relatedComments.ForEach(comment => DataSource.CommentsList.Remove(comment));

            return Request.CreateResponse(HttpStatusCode.OK, new { post = item });
        }
    }
}
قسمت سمت سرور کار تکمیل شده‌است. اما در سمت کاربر، چنین خطایی را دریافت خواهیم کرد:
 Attempted to handle event `pushedData` on  while in state root.deleted.inFlight.
منظور از حالت inFlight در اینجا این است که هنوز کار حذف سمت سرور تمام نشده‌است که متد transitionToRoute را صادر کرده‌اید. برای اصلاح آن، فایل Scripts\Controllers\post.js را باز کرده و پس از متد destroyRecord، متد then را قرار دهید:
Blogger.PostController = Ember.ObjectController.extend({
    isEditing: false,
    actions: {
        edit: function () {
            this.set('isEditing', true);
        },
        save: function () {
            var post = this.get('model');
            post.save();
 
            this.set('isEditing', false);
        },
        delete: function () {
            if (confirm('Do you want to delete this post?')) {
                var thisController = this;
                var post = this.get('model');
                post.destroyRecord().then(function () {
                    thisController.transitionToRoute('posts');
                });
            }
        }
    }
});
به این ترتیب پس از پایان عملیات حذف سمت سرور، قسمت then اجرا خواهد شد. همچنین باید دقت داشت که this اشاره کننده به کنترلر جاری را باید پیش از فراخوانی then ذخیره و استفاده کرد.


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