نظرسنجی‌ها
در پروژه های خود از کلمه کلیدی var در #C چگونه استفاده می کنید؟
فقط از var استفاده میکنم، مگر جایی که امکان استفاده از var نباشه.
هر جایی که نوع عبارت واضح نباشه، از explicitly typed استفاده میکنم.
اکثرا از explicitly typed استفاده میکنم.
هیچکدام.(لطفا در صورت امکان در کامنت روش خود را توضیح دهید)
مطالب
ASP.NET MVC #23

اجرای برنامه‌های ASP.NET MVC توسط نگارش‌های متفاوت IIS

تا اینجا برای اجرای برنامه‌های ASP.NET MVC از وب سرور توکار VS.NET استفاده شد که صرفا جهت آزمایش برنامه‌ها طراحی شده است. تا این تاریخ سه رده از وب سرورهای مایکروسافت ارائه شده‌اند که برای نصب ASP.NET MVC می‌توانند مورد استفاده قرار گیرند و هر کدام هم نکته‌های خاص خودشان را دارند که در ادامه به بررسی آن‌ها خواهیم پرداخت.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP

پس از ایجاد یک دایرکتوری مجازی بر روی پوشه یک برنامه ASP.NET MVC و سعی در اجرای برنامه، بلافاصله پیغام خطای HTTP 403 forbidden مشاهده می‌شود.
اولین کاری که برای رفع این مساله باید صورت گیرد، کلیک راست بر روی نام دایرکتوری مجازی در کنسول IIS، انتخاب گزینه خواص و سپس مراجعه به برگه «ASP.NET» آن است. در اینجا شماره نگارش دات نت فریم ورک مورد استفاده را به 4 تغییر دهید (برای نمونه ASP.NET MVC 3.0 مبتنی بر دات نت فریم ورک 4 است).
بعد از این تغییر، بازهم موفق به اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x نخواهیم شد؛ چون در آن زمان مفاهیم مسیریابی و Routing که اصل و پایه ASP.NET MVC هستند وجود خارجی نداشتند. این نگارش از IIS به صورت پیش فرض تنها قادر به پردازش درخواست‌های رسیده‌ای که به یک فایل فیزیکی بر روی سرور اشاره می‌کند، می‌باشد (یعنی مشکلی با اجرای برنامه‌های ASP.NET Web forms ندارد).
برای رفع این مشکل، مجددا بر روی نام دایرکتوری مجازی برنامه در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه ظاهر شده، در برگه «Virtual directory» آن، بر روی دکمه «Configuration» کلیک نمائید. در صفحه باز شده مجددا بر روی دکمه «Add» کلیک کنید.
در صفحه باز شده، مسیر Executable را C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll وارد کرده و Extension را به .* (دات هرچی) تنظیم کنید. همین مقدار تنظیم، برای اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP کفایت می‌کند.

کاری که در اینجا انجام شده است، نگاشت تمام درخواست‌های رسیده صرفنظر از پسوند فایل‌ها، به موتور ASP.NET می‌باشد. به صورت پیش فرض در IIS 5.x درخواست‌ها تنها بر اساس پسوند فایل‌ها پردازش می‌شوند. مثلا اگر فایل درخواستی aspx است، درخواست رسیده به aspnet_isapi.dll یاد شده هدایت خواهد شد. اگر پسوند فایل php است به isapi مخصوص آن (در صورت نصب) هدایت می‌گردد و به همین ترتیب برای سایر سیستم‌های دیگر. زمانیکه Extension به «دات هرچی» و Executable به aspnet_isapi.dll دات نت 4 تنظیم می‌شود، دایرکتوری مجازی تنظیم شده تنها جهت سرویس دهی به یک برنامه ASP.NET عمل خواهد کرد و تمام درخواست‌های رسیده به آن، به موتور اجرایی ASP.NET هدایت می‌شوند.

بدیهی است تنظیمات فوق تنها به یک دایرکتوری مجازی اعمال شدند. اگر نیاز باشد تا بر روی تمام سایت‌ها تاثیر گذار شود، اینبار در کنسول IIS 5.x بر روی «Default web site» کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه باز شده به برگه «Home directory» مراجعه کرده و مراحل ذکر شده را تکرار کنید.

مشکل! این روش بهینه نیست.
روش فوق خوبه، کار می‌کنه، اما بهینه نیست؛ از این جهت که «نگاشت تمام درخواست‌ها به موتور ASP.NET» یعنی پروسه پردازش درخواست یک فایل تصویری، js یا css هم باید از فیلتر موتور ASP.NET عبور کند که ضروری نیست.
برای رفع این مشکل، توصیه شده است که سیستم مسیریابی ASP.NET MVC را در IIS 5.x «پسوند دار» کنید. به این نحو که با مراجعه به فایل Global.asax.cs، تعاریف مسیریابی را به نحو زیر ویرایش کنید:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
new Route("{controller}.aspx/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
})
});



اینبار برای مثال مسیر http://localhost/MyMvcApp/home.aspx/index به علت داشتن پسوند aspx وارد موتور پردازشی ASP.NET خواهد شد. البته در این حالت URL های تمیز ASP.NET MVC را از دست خواهیم داد و مدام باید دقت داشت که مسیرهای کنترلرها حتما باید به aspx ختم شوند. ضمنا با این تنظیم، دیگر نیازی به تغییر تعاریف نگاشت‌ها در کنسول مدیریتی IIS، نخواهد بود.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 6.x ویندوز سرور 2003

تمام نکات عنوان شده جهت IIS 5.x در IIS 6.x نیز صادق هستند. به علاوه برای اجرای برنامه‌های ASP.NET بر روی IIS 6.x باید به دو نکته مهم دیگر نیز دقت داشت:
الف) ASP.NET 4 به صورت پیش فرض در IIS 6.x غیرفعال است که باید با مراجعه به قسمت Web Services Extensions در کنسول مدیریتی IIS، آن‌را از حالت prohibited خارج کرد.
ب) در هر Application pool تنها از یک نگارش دات نت فریم ورک می‌توان استفاده کرد. برای مثال اگر هم اکنون AppPool1 مشغول سرویس دهی به یک سایت ASP.NET 3.5 است، از آن نمی‌توانید جهت اجرای برنامه‌های ASP.NET MVC 3 به بعد استفاده کنید. زیرا برای مثال ASP.NET MVC 3 مبتنی بر دات نت فریم ورک 4 است. به همین جهت حتما نیاز است تا یک Application pool مجزا را برای برنامه‌های دات نت 4 در IIS 6 اضافه نمائید و سپس در تنظیمات سایت، از این Application pool جدید استفاده نمائید.
البته روش صحیح و اصولی کار با IIS از نگارش 6 به بعد هم مطابق شرحی است که عنوان شد. برای دستیابی به بهترین کارآیی و امنیت بیشتر، بهتر است به ازای هر سایت، از یک Application pool مجزا استفاده نمائید.

اطلاعات تکمیلی:
نکات نصب برنامه‌های ASP.NET 4.0 بر روی IIS 6
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS



اجرای برنامه‌های ASP.NET MVC بر روی IIS 7.x ویندوز 7 و ویندوز سرور 2008

اگر برنامه ASP.NET MVC در IIS 7.x در حالت یکپارچه (integrated mode) اجرا شود، بدون نیاز به هیچگونه تغییری در تنظیمات سرور یا برنامه، بدون مشکل قابل اجرا خواهد بود. بدیهی است در اینجا نیز بهتر است به ازای هر برنامه، یک Application pool مجزا را ایجاد کرد.
اما در حالت classic (که برای برنامه‌های جدید توصیه نمی‌شود) نیاز است همان مراحل IIS 5,x تکرار شود. البته اینبار مسیر زیر را باید طی کرد تا به صفحه افزودن نگاشت‌ها رسید:
Right-click on a web site -> Properties -> Home Directory tab -> click on the Configuration button -> Mappings tab



نکته‌ای مهم در تمام نگارش‌های IIS

ترتیب نصب دات نت فریم ورک 4 و IIS مهم است. اگر ابتدا IIS نصب شود و سپس دات نت فریم ورک 4، به صورت خودکار، کار نگاشت اطلاعات ASP.NET به IIS صورت خواهد گرفت.
اگر ابتدا دات نت فریم ورک 4 نصب شود و سپس IIS، برای مثال دیگر از برگه ASP.NET در IIS 6.x خبری نخواهد بود. برای رفع این مشکل دستور زیر را در خط فرمان اجرا کنید:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe /i

به این ترتیب، اطلاعات مرتبط با موتور ASP.NET مجددا به تنظیمات IIS اضافه خواهند شد.


مطالب دوره‌ها
بررسی کارآیی و ایندکس گذاری بر روی اسناد XML در SQL Server - قسمت دوم
تا اینجا ملاحظه کردید که XQuery ایندکس نشده چگونه بر روی Query Plan تاثیر دارد. در ادامه، مباحث ایندکس گذاری بر روی اسناد XML ایی را مرور خواهیم کرد.


ایندکس‌های XML ایی

ایندکس‌های XML ایی، ایندکس‌های خاصی هستند که بر روی ستون‌هایی از نوع XML تعریف می‌شوند. هدف از تعریف آن‌ها، بهینه سازی اعمال مبتنی بر XQuery، بر روی داده‌های این نوع ستون‌ها است. چهار نوع XML Index قابل تعریف هستند؛ اما primary xml index باید ابتدا ایجاد شود. در این حالت جدولی که دارای ستون XML ایی است نیز باید دارای یک clustered index باشد. هدف از primary XML indexها، ارائه‌ی تخمین‌های بهتری است به بهینه ساز کوئری‌ها در SQL Server.


جزئیات primary XML indexها

زمانیکه یک primary xml index را ایجاد می‌کنیم، node table یاد شده در قسمت قبل را، بر روی سخت دیسک ذخیره خواهیم کرد (بجای هربار محاسبه در زمان اجرا). متادیتای این اطلاعات ذخیره شده را در جداول سیستمی sys.indexes و sys.columns می‌توان مشاهده کرد. باید دقت داشت که تهیه‌ی این ایندکس‌ها، فضای قابل توجهی را از سخت دیسک به خود اختصاص خواهند داد؛ چیزی حدود 2 تا 5 برابر حجم اطلاعات اولیه. بدیهی است تهیه‌ی این ایندکس‌ها که نتیجه‌ی تجزیه‌ی اطلاعات XML ایی است، بر روی سرعت insert تاثیر خواهند گذاشت. Node table دارای ستون‌هایی مانند نام تگ، آدرس تگ، نوع داده آن، مسیر و امثال آن است.
زمانیکه یک Primary XML Index تعریف می‌شود، اگر به Query Plan حاصل دقت کنید، دیگر خبری از XML Readerها مانند قبل نخواهد بود. در اینجا Clustered index seek قابل مشاهده‌است.


ایجاد primary XML indexها

همان مثال قسمت قبل را که دو جدول از آن به نام‌های xmlInvoice و xmlInvoice2 ایجاد کردیم، درنظر بگیرید. اینبار یک xmlInvoice3 را با همان ساختار و همان 6 رکوردی که معرفی شدند، ایجاد می‌کنیم. بنابراین برای آزمایش جاری، در مثال قبل، هرجایی xmlInvoice مشاهده می‌کنید، آن‌را به xmlInvoice3 تغییر داده و مجددا جدول مربوطه و داده‌های آن‌را ایجاد کنید.
اکنون برای ایجاد primary XML index بر روی ستون invoice آن می‌توان نوشت:
 CREATE PRIMARY XML INDEX invoice_idx ON xmlInvoice3(invoice)
 SELECT * FROM sys.internal_tables
کوئری دومی که بر روی sys.internal_tables انجام شده، محل ذخیره سازی این ایندکس را نمایش می‌دهد که دارای نامی مانند xml_index_nodes_325576198_256000 خواهد بود. دو عدد پس از آن table object id و column object id هستند.
در ادامه علاقمند هستیم که بدانیم داخل آن چه چیزی ذخیره شده‌است:
 SELECT * FROM sys.xml_index_nodes_325576198_256000
اگر این کوئری را اجرا کنید احتمالا به خطای Invalid object name برخواهید خورد. علت اینجا است که برای مشاهده‌ی اطلاعات جداول داخلی مانند این، نیاز است حین اتصال به SQL Server، در قسمت server name نوشت admin:(local) و حالت authentication نیز باید بر روی Windows authentication باشد. به آن اصطلاحا Dedicated administrator connection نیز می‌گویند. برای این منظور حتما نیاز است از طریق منوی File -> New -> Database Engine Query شروع کنید در غیراینصورت پیام Dedicated administrator connections are not supported را دریافت خواهید کرد.
اگر به این جدول دقت کنید، 6 ردیف اطلاعات XML ایی، به حدود 100 ردیف اطلاعات ایندکس شده، تبدیل گردیده‌است. با استفاده از دستور ذیل می‌توان حجم ایندکس تهیه شده را نیز مشاهده کرد:
 sp_spaceused 'xmlInvoice3'
در صورت نیاز برای حذف ایندکس ایجاد شده می‌توان به نحو ذیل عمل کرد:
 --DROP INDEX invoice_idx ON xmlInvoice3


تاثیر primary XML indexها بر روی سرعت اجرای کوئری‌ها

همان 10 کوئری قسمت قبل را درنظر بگیرید. اینبار برای مقایسه می‌توان به نحو ذیل عمل کرد:
 SELECT * FROM xmlInvoice
WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1

SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1
دو کوئری یکی هستند اما اولی بر روی xmlInvoice اجرا می‌شود و دومی بر روی xmlInvoice3. هر دو کوئری را انتخاب کرده و با استفاده از منوی Query، گزینه‌ی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمه‌های Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده نمود.


چند نکته در این تصویر حائز اهمیت است:
- Query plan کوئری انجام شده بر روی جدول دارای primary XML index، مانند قسمت قبل، حاوی XML Readerها نیست.
- هزینه‌ی انجام کوئری بر روی جدول دارای XML ایندکس نسبت به حالت بدون ایندکس، تقریبا نزدیک به صفر است. (بهبود کارآیی فوق العاده)
اگر کوئری‌های دیگر را نیز با هم مقایسه کنید، تقریبا به نتیجه‌ی کمتر از یک سوم تا یک چهارم حالت بدون ایندکس خواهید رسید.
همچنین اگر برای حالت دارای Schema collection نیز ایندکس ایجاد کنید، اینبار کوئری پلن آن اندکی (چند درصد) بهبود خواهد یافت ولی نه آنچنان.


ایندکس‌های XML‌ایی ثانویه یا secondary XML indexes

سه نوع ایندکس XML ایی ثانویه نیز قابل تعریف هستند:
- VALUE : کار آن بهینه سازی کوئری‌های content و wildcard است.
- PATH : بهینه سازی انتخاب‌های مبتنی بر XPath را انجام می‌دهد.
- Property: برای بهینه سازی انتخاب خواص و ویژگی‌ها بکار می‌رود.

این ایندکس‌ها یک سری non-clustered indexes بر روی node tables هستند. برای ایجاد سه نوع ایندکس یاد شده به نحو ذیل می‌توان عمل کرد:
 CREATE XML INDEX invoice_path_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR PATH
در اینجا یک path index جدید ایجاد شده‌است. ایندکس‌های ثانویه نیاز به ذکر ایندکس اولیه نیز دارند.
پس از ایجاد ایندکس ثانویه بر روی مسیرها، اگر اینبار کوئری دوم را اجرا کنیم، به Query Plan ذیل خواهیم رسید:


همانطور که مشاهده می‌کنید، نسبت به حالت primary index، وضعیت clustered index seek به index seek تغییر کرده‌است و همچنین دقیقا مشخص است که از کدام ایندکس استفاده شده‌است.
در ادامه دو نوع ایندکس دیگر را نیز ایجاد می‌کنیم:
 CREATE XML INDEX invoice_value_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR VALUE
 
 CREATE XML INDEX invoice_prop_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR PROPERTY
 

سؤال: اکنون پس از تعریف 4 ایندکس یاد شده، کوئری دوم از کدام ایندکس استفاده خواهد کرد؟

در اینجا مجددا کوئری دوم را اجرا کرده و به قسمت Query Plan آن دقت خواهیم کرد:


برای مشاهده دقیق نام ایندکس مورد استفاده، کرسر ماوس را بر روی index seek قرار می‌دهیم. در اینجا اگر به قسمت object گزارش ارائه شده دقت کنیم، نام invoice_value_idx یا همان value index ایجاد شده، قابل مشاهده‌است؛ به این معنا که در کوئری دوم، اهمیت مقادیر بیشتر است از اهمیت مسیرها.

کوئری‌هایی مانند کوئری ذیل از property index استفاده می‌کنند:
 SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice//CustomerName[text() = "Vahid"]') = 1
در اینجا با بکارگیری // به دنبال CustomerName در تمام قسمت‌های سند Invoice خواهیم گشت. البته کوئری پلن آن نسبتا پیچیده‌است و شامل primary index اسکن و clusterd index اسکن نیز می‌شود. برای بهبود قابل ملاحظه‌ی آن می‌توان به نحو ذیل از عملگر self استفاده کرد:
 SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice//CustomerName[. = "Vahid"]') = 1


خلاصه نکات بهبود کارآیی برنامه‌های مبتنی بر فیلدهای XML

- در حین استفاده از XPath، ذکر  محور parent یا استفاده از .. (دو دات)، سبب ایجاد مراحل اضافه‌ای در Query Plan می‌شوند. تا حد امکان از آن اجتناب کنید و یا از روش‌هایی مانند cross apply و xml.nodes برای مدیریت اینگونه موارد تو در تو استفاده نمائید.
- ordinals را به انتهای Path منتقل کنید (مانند ذکر [1] جهت مشخص سازی نودی خاص).
- از ذکر predicates در وسط یک Path اجتناب کنید.
- اگر اسناد شما fragment با چند root elements نیستند، بهتر است document بودن آ‌ن‌ها را در حین ایجاد ستون XML مشخص کنید.
- xml.value را به xml.query ترجیح دهید.
- عملیات casting در XQuery سنگین بوده و استفاده از ایندکس‌ها را غیرممکن می‌کند. در اینجا استفاده از اسکیما می‌تواند مفید باشد.
- نوشتن sub queryها بهتر هستند از چندین XQuery در یک عبارت SQL.
- در ترکیب اطلاعات رابطه‌ای و XML، استفاده از متدهای xml.exist و sql:column نسبت به xml.value جهت استخراج و مقایسه اطلاعات، بهتر هستند.
- اگر قصد تهیه خروجی XML از جدولی رابطه‌ای را دارید، روش select for xml کارآیی بهتری را نسبت به روش FLOWR دارد. روش FLOWR برای کار با اسناد XML موجود طراحی و بهینه شده‌است؛ اما روش select for xml در اصل برای کار با اطلاعات رابطه‌ای بهینه سازی گردیده‌است.
مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این مطلب مثالی را در مورد نحوه‌ی تنظیمات یک پروژه‌ی خالی ASP.NET Core، جهت استفاده‌ی از یک پروژه‌ی Angular CLI قرار گرفته‌ی در پوشه‌ی آن‌را بررسی خواهیم کرد.

پیشنیازها

 - مطالعه‌ی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامه‌های آن، پیش از مطالعه‌ی این مطلب ضروری است.
 - همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کرده‌اید و با نحوه‌ی برپایی یک برنامه‌ی MVC آن و ارائه‌ی فایل‌های استاتیک توسط یک پروژه‌ی ASP.NET Core آشنایی دارید.


ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017

در ابتدا یک پروژه‌ی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
 - ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینه‌ی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
 - در صفحه‌ی بعدی آن هم گزینه‌ی «empty template» را انتخاب نمائید.


تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI

برای اجرای یک برنامه‌ی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامه‌ی ASP.NET Core کلیک راست کرده و گزینه‌ی Edit آن‌را انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:

الف) درخواست عدم کامپایل فایل‌های TypeScript
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  </PropertyGroup>
چون نحوه‌ی کامپایل پروژه‌های Angular CLI صرفا مبتنی بر کامپایل مستقیم فایل‌های TypeScript آن نیست و در اینجا از یک گردش کاری توکار مبتنی بر webpack، به صورت خودکار استفاده می‌کند، کامپایل فایل‌های TypeScript توسط ویژوال استودیو، مفید نبوده و صرفا سبب دریافت گزارش‌های خطای بیشماری به همراه کند کردن پروسه‌ی Build آن خواهد شد. بنابراین با افزودن تنظیم TypeScriptCompileBlocked به true، از VS 2017 خواهیم خواست تا در این زمینه دخالت نکند.

ب) مشخص کردن پوشه‌هایی که باید الحاق و یا حذف شوند
  <ItemGroup>
    <Folder Include="Controllers\" />
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="node_modules\**" />
    <Content Remove="node_modules\**" />
    <EmbeddedResource Remove="node_modules\**" />
    <None Remove="node_modules\**" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="src\**" />
    <Content Remove="src\**" />
    <EmbeddedResource Remove="src\**" />    
  </ItemGroup>
در اینجا پوشه‌های کنترلرها و wwwroot به پروژه الحاق شده‌اند. پوشه‌ی wwwroot جایی است که فایل‌هایی خروجی را Angular CLI ارائه خواهد کرد.
سپس دو پوشه‌ی node_modules و src واقع در ریشه‌ی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کرده‌ایم. پوشه‌ی node_modules وابستگی‌های Angular را به همراه دارد و src همان پوشه‌ی برنامه‌ی Angular ما خواهد بود.

ج) افزودن وابستگی‌های سمت سرور مورد نیاز
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
 
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
 
  <ItemGroup>
    <!-- extends watching group to include *.js files -->
    <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
  </ItemGroup>
برای اجرای یک برنامه‌ی تک صفحه‌ای وب Angular، صرفا به وابستگی MVC و StaticFiles آن نیاز است.
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شده‌اند که در ادامه‌ی بحث به آن اشاره خواهد شد.


افزودن یک کنترلر Web API جدید

با توجه به اینکه دیگر در اینجا قرار نیست با فایل‌های cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائه‌ی Web API و همچنین فایل‌های Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفته‌است.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
 
namespace ASPNETCoreIntegrationWithAngularCLI.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET: api/values
    [HttpGet]
    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
    public IEnumerable<string> Get()
    {
      return new string[] { "Hello", "DNT" };
    }
  }
}
در اینجا کدهای یک کنترلر نمونه را جهت بازگشت یک خروجی JSON ساده مشاهده می‌کنید که در ادامه، در برنامه‌ی Angular CLI تهیه شده از آن استفاده خواهیم کرد.


تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular

در ادامه به فایل Startup.cs برنامه‌ی خالی جاری، مراجعه کرده و آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace ASPNETCoreIntegrationWithAngularCLI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.Use(async (context, next) => {
                await next();
                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            });
 
            app.UseMvcWithDefaultRoute();
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }
    }
}
در اینجا برای ارائه‌ی کنترلر Web API، نیاز به ثبت سرویس‌های MVC است. همچنین ارائه‌ی فایل‌های پیش فرض و فایل‌های استاتیک (همان پوشه‌ی wwwroot برنامه) نیز فعال شده‌اند.
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامه‌های Angular را مشاهده می‌کنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال می‌شود و چون چنین صفحه‌ای در سمت سرور وجود ندارد، خطای 404 بازگشت داده می‌شود و کار به پردازش برنامه‌ی Angular نخواهد رسید. اینجا است که تنظیم میان‌افزار فوق، کار مدیریت خروجی‌های 404 را بر عهده گرفته و کاربر را به فایل index.html برنامه‌ی تک صفحه‌ای وب، هدایت می‌کند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کننده‌ی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحه‌ی index.html معاف خواهد شد.


ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری

اکنون از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core‌، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
 >ng new ClientApp --routing --skip-install --skip-git --skip-commit
به این ترتیب پوشه‌ی جدید ClientApp، در داخل پوشه‌ی برنامه اضافه خواهد شد که در آن تنظیمات اولیه‌ی مسیریابی Angular نیز انجام شده‌است؛ از دریافت وابستگی‌های npm آن صرفنظر شده و همچنین کار تنظیمات git آن نیز صورت نگرفته‌است (تا از تنظیمات git پروژه‌ی اصلی استفاده شود).
پس از تولید ساختار برنامه‌ی Angular CLI، به پوشه‌ی آن وارد شده و تمام فایل‌های آن را Cut کنید. سپس به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core جاری، وارد شده و این فایل‌ها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژه‌های Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایل‌های تنظیمات آن‌را می‌شناسد و قابلیت پردازش آن‌ها را دارد.
پس از این cut/paste، پوشه‌ی خالی ClientApp را نیز حذف کنید.


تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot

برای اینکه سیستم Build پروژه‌ی Angular CLI جاری، خروجی خود را در پوشه‌ی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آن‌را به wwwroot تنظیم کنیم:
"apps": [
    {
      "root": "src",
      "outDir": "wwwroot",
به این ترتیب پس از هر بار build آن، فایل‌های index.html و تمام فایل‌های js نهایی، در پوشه‌ی wwwroot که در فایل Startup.cs‌، کار عمومی کردن آن انجام شد، تولید می‌شوند.


فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI

در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private _httpService: Http) { }
 
  apiValues: string[] = [];
 
  ngOnInit() {
    this._httpService.get('/api/values').subscribe(values => {
      this.apiValues = values.json() as string[];
    });
  }
}
در اینجا خروجی JSON کنترلر Web API برنامه دریافت شده و به آرایه‌ی apiValues انتساب داده می‌شود.

سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1>
<ul *ngFor="let value of apiValues">
  <li>{{value}}</li>
</ul>
<router-outlet></router-outlet>
در اینجا یک حلقه ایجاد شده و عناصر آرایه‌ی apiValues به صورت یک لیست نمایش داده می‌شوند.


نصب وابستگی‌های برنامه‌ی Angular CLI

در ابتدای ایجاد پوشه‌ی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمام‌تر، ایجاد شود. اکنون برای نصب وابستگی‌های آن یا می‌توان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینه‌ی restore packages ظاهر می‌شود. و یا می‌توان به روش متداول این نوع پروژه‌ها، از طریق خط فرمان به پوشه‌ی ریشه‌ی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارش‌های این ابزار که بر روی سیستم نصب شده‌است، استفاده می‌کنید.


روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشده‌است.
بنابراین روش اول اجرای این نوع برنامه‌ها، شامل مراحل ذیل است:
الف) ساخت پروژه‌ی Angular CLI در حالت watch
 > ng build --watch
برای اجرای آن از طریق خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دستور فوق را وارد کنید. به این ترتیب کار build پروژه انجام شده و همچنین فایل‌های نهایی آن در پوشه‌ی wwwroot قرار می‌گیرند. به علاوه چون از پرچم watch استفاده شده‌است، با هر تغییری در پوشه‌ی src برنامه، این فایل‌ها به صورت خودکار به روز رسانی می‌شوند. بنابراین این پنجره‌ی خط فرمان را باید باز نگه داشت تا watcher آن بتواند کارش را به صورت مداوم انجام دهد.

ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشه‌ی wwwroot برنامه انجام شده‌است، می‌توان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:


یک نکته: می‌توان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build">
    <Exec Command="ng build" />
</Target>
به این ترتیب با هربار Build پروژه در VS 2017، کار تولید مجدد محتوای پوشه‌ی wwwroot نیز انجام خواهد شد.
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده می‌شود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.


روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامه‌های NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آن‌ها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگی‌های برنامه اضافه کردیم.

الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشه‌ی پروژه‌، صادر کنید:
> dotnet watch run
این دستور پروژه‌ی ASP.NET Core را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد:
>dotnet watch run
[90mwatch : [39mStarted
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
به علاوه پارامتر watch آن سبب خواهد شد تا هر تغییری که در کدهای پروژه‌ی ASP.NET Core صورت گیرند، بلافاصله کامپایل شده و قابل استفاده شوند.

ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شده‌است، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو ساده‌تری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامه‌ی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده می‌شود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامه‌ی Angular ما، یک چنین درخواست‌هایی را به سمت سرور، جهت دریافت اطلاعات ارسال می‌کند: localhost:4200/api
برای رفع این مشکل می‌توان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}
سپس دستور ng server صادر شده، اندکی متفاوت خواهد شد:
 >ng serve --proxy-config proxy.config.json -o
در اینجا به ng serve اعلام کرده‌ایم که تمامی درخواست‌های ارسالی به مسیر api/  (و یا همان localhost:4200/api جاری) را به سرور وب ASP.NET Core، بر روی پورت 5000 ارسال کن و نتیجه را در همینجا بازگشت بده. به این ترتیب مشکل عدم دسترسی به سرور وب، جهت تامین اطلاعات برطرف خواهد شد.
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.

نکته 1: بدیهی است می‌توان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژه‌ی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمی‌شود که آن‌را نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمه‌ی refresh پس از هر بار پایان کار ng build).
نکته 2: می‌توان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی می‌خواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه می‌خواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شده‌است. عدد این پورت را می‌توان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شده‌ی در این مطلب، می‌توان از آن‌ها استفاده کرد:
 - فایل restore.bat کار بازیابی و نصب وابستگی‌های پروژه‌ی دات نتی و همچنین Angular CLI را انجام می‌دهد.
 - دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائه‌ی نهایی.
 - دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کننده‌ی قسمت‌های «الف» و «ب» روش دوم هستند.
نظرات مطالب
استفاده از DataAnnotations در EF DataBase First
سلام.
به خاطر مطلب خوبتون تشکر میکنم.
من یه پروژه 2 لایه دارم که مدل در لایه DLL قرار گرفته.
و یک لایه که پروژه MVC  داخل اون قرار گافته.
میخواستم بدنم که  متادیتا  باید داخل کدوم لایه نوشته بشه؟
مطالب
مروری بر پلاگین‌های چندسکویی مرورگرها
NPAPI
این عبارت مخفف Netscape Plugin Application Programming Interface هست و یک پلاگین چندسکویی است که بسیاری از مرورگرها از آن استفاده می‌کنند. معماری این پلاگین ابتدا در سال 1995 برای مرورگر netscape نسخه 2 آن توسعه پیدا کرد. ولی رفته رفته توسط دیگر مرورگرها هم استفاده و پیاده سازی شد و البته تعدادی هم بعدها استفاده از آن را رها کردند و از فناوری‌های دیگری استفاده کردند.
کار این پلاگین تعریف یک سری ContentType مانند audio/mp3 است و موقعی‌که مرورگر در رندر کردن این صفحات به چنین محتوایی برخورد کند، پلاگین مربوط به این محتوا را در حافظه بار می‌کند تا پلاگین را در حافظه رندر کرده و جابجایی داده‌ها با پلاگین صورت بگیرد. سپس آن پلاگین محتوای مربوطه را رندر می‌کند. امروزه پلاگین‌ها در همان قسمت از حافظه صفحه مرورگر بار می‌شوند و مانند مرورگرهای قدیمی مجبور به اجرای برنامه خارجی برای محتواهای ناشناخته نیستند.
این API نیاز دارد هر پلاگین تقریبا 15 تابع را پیاده سازی کند. این توابع برای پیاده سازی، ایجاد، نابودسازی آن و موقعیت مکانی آن به کار می‌روند. NPAPI همچنین امکاناتی از قبیل اسکریت نویسی، چاپ کردن، تمام صفحه شدن، پلاگین بدون پنجره، استریم کردن محتوا را نیز دارد.
مبداء و منشا ابتدایی این فناوری از شرکت Adobe و محصول Adobe Reader آن و خواندن PDF‌های تحت وب آغاز گشت. در آن زمان در وب، تنها فرمت HTML به همراه تصاویر به عنوان محتوا حساب میشد و مابقی نوع‌ها به دانلود ختم می‌شدند و شرکت ادوب قصد داشت کاربران تجربه‌ی جدیدی از خواندن فایل‌های PDF را بر روی مرورگر وب داشته باشند و برای دیدن حتما نیازی به دانلود نداشته باشند.
شرکت ادوب برای جلب نظر توسعه دهنگان netscape از یک نسخه‌ی دمو استفاده کرد و موقعی که روی لینک یک PDF کلیک میشد به جای دانلود آن، محتوا داخل صفحه مرورگر رندر میشد که مخلوطی از کدهای html و PDF بود ولی آن‌ها خواستار یک سیستم یکپارچه بودند تا دیگر مرورگر netscape درگیر آن نباشد و بدون وابستگی به مرورگر و به طور مستقل این کار انجام شود که کم کم باعث ایجاد این API شد و از آن موقع در طول این سال‌ها مرورگرهای زیادی از این فناوری استفاده کردند تا استاندارد عمومی در توسعه مرورگرها باشد.

عدم پشتیبانی گوگل از این پلاگین API
گوگل در سال 2013 عدم پشتیبانی از این API را در مرورگرش اعلام کرد که در این حالت پشتیبانی از تمامی فناوری‌های مرتبط آن نیز خاتمه میابد که شامل جاوا و سیلورلایت می‌شود و دلیل آن مسائل پایداری و امنیت آن در کنار سایر موارد است.

پشتیبانی از اسکریپت نویسی
این ویژگی اجازه میدهد که شما با استفاده از کدهای جاوااسکریپت، در صفحات وب با پلاگین تعامل کنید. ابتدا netscape این امکان را اضافه کرد و بعدها موزیلا این ویژگی را با استفاده از فناوری هایی چون Live Connect ،  XP Connect و npRuntime ارتقاء بخشید.

Live Connect
از netscape 4 به بعد NPAPI برای اسکریپت نویسی توسعه یافت. این ویژگی به مرورگرها اجازه میدهد که برنامه‌های نوشته شده با جاوا و جاوااسکریپت، با صفحات وب به تعامل بپردازند. به عنوان مثال اپلت نوشته شده با جاوا می‌تواند با کدهای داخل صفحه ارتباط برقرار کند یا بیشتر از خود اسکریت‌های صفحه با محیط جاوااسکریت داخلی ارتباط برقرار کند.  با این ویژگی یک پلاگین میتواند یک نمونه کلاس جاوا را پیاده سازی و بازگرداند. این کلاس میتواند از طریق جاوااسکریت یا اپلت‌های جاوا صدا زده شود.

XP Connect
ابتدا قبل از هر چیز بگذارید با XPCOM اشنا شویم. XPCOM یا Cross Platform Component Object Model از طرف شرکت موزیلا ارائه شده است و اصلی‌ترین ویژگی آن اتصال چندین زبان به یکدیگر از طریق IDL یا یک زبان واسط است. بدین ترتیب برنامه نویس‌ها میتوانستند کدهای اختصاصی خود را با دیگر کامپوننت‌ها مرتبط کنند.
حال کار xpconnect برقراری ارتباط بین جاوااسکریپت با XPCOM یا کدهای نوشته شده توسعه گران است. XP Connect به اشیاء جاوااسکریپتی اجازه می‌دهد به طور مستقیم به اشیاء XPCom دسترسی داشته و آن‌ها را دستکاری کنند.

NPRuntime
در پایان سال 2004 بسیاری از شرکت‌های اصلی ارائه کننده مرورگرهای وب که از NPAPI استفاده می‌کردند، به این توافق رسیدند که از فناوری NPRuntime به عنوان یک اکستنشن (افزونه) برای تامین اسکریپت نویسی NPAPI استفاده کنند. اسکریپت نویسی این فناوری مستقل از دیگر فناوری‌هایی چون جاوا و XPCOM است و توسط گوگل (در آن زمان)، فایرفاکس، سافاری و اپرا (در آن زمان)  پشتیبانی می‌شود.

PPAPI
این فناوری در 12 آگوست 2009 معرفی شد. Pepper Plugin API مجموعه ای از تغییرات و اصلاحات روی NPAPI است که باعث انعطاف پذیری و امنیت بیشتر می‌شود. این افزونه جهت پیاده سازی آسان اجرای پلاگین در یک پروسه‌ی جداگانه است. هدف این پروژه تهیه یک فریمورک برای ایجاد پلاگین‌های مستقل یا Cross-Platform میباشد.
تعدادی از مواردی که در طراحی این افزونه در نظر گرفته شده است:
  • اجرای جداگانه از پروسه‌ی مرورگر
  • تعریف رویدادهای استاندارد و رسم تصاویر دو بعدی
  • تهیه گرافیک سه بعدی
  • ریجستری پلاگین
  • استاندارد سازی برای استفاده از سیستم رندر مرورگر Compositing Process 
  • تعریف مفهوم یا معنای واحد از NPAPI بین تمامی مروگرها
مرورگرهایی که در حال حاضر به این افزونه روی آورده اند طبعا مرورگر کروم و اپرا از نسخه 24 به بعد است.
شرکت موزیلا در سال 2011 اعلام کرد که فعلا علاقه ای به شرکت در این پروژه ندارد.
شرکت ادوب برای افزونه flash player اعلام کرد که دیگر تنها بر روی PPAPI فعالیت می‌کنند و از آخرین نسخه فلش پلیر 11.2 که برای NPAPI ایجاد شده است تنها به مدت پنج سال بسته‌های آپدیت آن را در دسترس قرار میدهد.

ActiveX
یکی دیگر از این پلاگین‌ها ActiveX است که توسط IE مورد استفاده قرار می‌گیرد ولی در مرورگر Edge  حذف شده است. به این دلیل، پشتیبانی از جاوا در این مرورگر قطع شده است و فلش پلیر را به صورت ذاخلی Built-in پشتیبانی می‌کند. امروزه بسیاری از تولیدکنندگان اعتقاد دارند که با Html5 بسیاری از مسائل حل می‌شود.
مطالب
ASP.NET MVC #8

معرفی HTML Helpers

یک HTML Helper تنها یک متد است که رشته‌ای را بر می‌گرداند و این رشته می‌تواند حاوی هر نوع محتوای دلخواهی باشد. برای مثال می‌توان از HTML Helpers برای رندر تگ‌های HTML، مانند img و input استفاده کرد. یا به کمک HTML Helpers می‌توان ساختارهای پیچیده‌تری مانند نمایش لیستی از اطلاعات دریافت شده از بانک اطلاعاتی را پیاده سازی کرد. به این ترتیب حجم کدهای تکراری تولید رابط کاربری در Viewهای برنامه‌های ASP.NET MVC به شدت کاهش خواهد یافت، به همراه قابلیت استفاده مجدد از متدهای الحاقی HTML Helpers در برنامه‌های دیگر.
HTML Helpers در ASP.NET MVC معادل کنترل‌های ASP.NET Web forms هستند اما نسبت به آن‌ها بسیار سبک‌تر می‌باشند؛ برای مثال به همراه ViewState و همچنین Event model نیستند.
ASP.NET MVC به همراه تعدادی متد HTML Helper توکار است و برای دسترسی به آن‌ها شیء Html که وهله‌ای از کلاس توکار HtmlHelper می‌باشد، در تمام Viewها قابل استفاده است.


نحوه ایجاد یک HTML Helper سفارشی

از دات نت سه و نیم به بعد امکان توسعه اشیاء توکار فریم ورک، به کمک متدهای الحاقی (extension methods) میسر شده است. برای نوشتن یک HTML Helper نیز باید همین شیوه عمل کرد و کلاس HtmlHelper را توسعه داد. در ادامه قصد داریم یک HTML Helper را جهت رندر تگ label در صفحه ایجاد کنیم. برای این منظور پوشه‌ی جدیدی به نام Helper را به پروژه اضافه نمائید (جهت نظم بیشتر). سپس کلاس زیر را به آن اضافه کنید:

using System;
using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static string MyLabel(this HtmlHelper helper, string target, string text)
{
return string.Format("<label for='{0}'>{1}</label>", target, text);
}
}
}

همانطور که ملاحظه می‌کنید متد Label به شکل یک متد الحاقی توسعه دهنده کلاس HtmlHelper که تنها یک رشته را بر می‌گرداند، تعریف شده است. اکنون برای استفاده از این متد در View دلخواهی خواهیم داشت:

@using MvcApplication4.Helpers
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@Html.MyLabel("firstName", "First Name:")

ابتدا فضای نام مرتبط با متد الحاقی باید پیوست شود و سپس از طریق شیء Html می‌توان به این متد الحاقی دسترسی پیدا کرد. اگر برنامه را اجرا کنید، این خروجی را مشاهده خواهیم کرد. چرا؟

Index
<label for='firstName'>First Name:</label>

علت این است که Razor، اطلاعات را Html encoded به مرورگر تحویل می‌دهد. برای تغییر این رویه باید اندکی متد الحاقی تعریف شده را تغییر داد:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyLabel(this HtmlHelper helper, string target, string text)
{
return MvcHtmlString.Create(string.Format("<label for='{0}'>{1}</label>", target, text));
}
}
}

تنها تغییر صورت گرفته، استفاده از MvcHtmlString بجای string معمولی است تا Razor آن‌را encode نکند.

تعریف HTML Helpers سفارشی به صورت عمومی:

می‌توان فضای نام MvcApplication4.Helpers این مثال را عمومی کرد. یعنی بجای اینکه بخواهیم در هر View  آن‌را ابتدا تعریف کنیم، یکبار آن‌را همانند تعاریف اصلی یک برنامه ASP.NET MVC، عمومی معرفی می‌کنیم. برای این منظور فایل web.config موجود در پوشه Views را باز کنید (و نه فایل web.config قرار گرفته در ریشه اصلی برنامه). سپس فضای نام مورد نظر را در قسمت namespaces صفحات اضافه نمائید:

<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="MvcApplication4.Helpers"/>
</namespaces>

به این ترتیب متدهای الحاقی تعریف شده در فضای نام MvcApplication4.Helpers،‌ در تمام Viewهای برنامه در دسترس خواهند بود.

استفاده از کلاس TagBuilder برای تولید HTML Helpers سفارشی:

using System.Web.Mvc;

namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyNewLabel(this HtmlHelper helper, string target, string text)
{
var labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", target);
labelTag.InnerHtml = text;
return MvcHtmlString.Create(labelTag.ToString());
}
}
}

در فضای نام System.Web.Mvc، کلاسی وجود دارد به نام TagBuilder که کار تولید تگ‌های HTML، مقدار دهی ویژگی‌ها و خواص آن‌ها را بسیار ساده می‌کند و روش توصیه شده‌ای است برای تولید متدهای HTML Helper. یک نمونه از کاربرد آن‌را برای بازنویسی متد MyLabel ذکر شده در اینجا ملاحظه می‌کنید.
شبیه به همین کلاس، کلاس دیگری به نام HtmlTextWriter در فضای نام System.Web.UI برای انجام اینگونه کارها وجود دارد.


نوشتن HTML Helpers ویژه، به کمک امکانات Razor

نوع دیگری از این متدهای کمکی، Declarative HTML Helpers نام دارند. از این جهت هم Declarative نامیده شده‌اند که مستقیما درون فایل‌های cshtml یا vbhtml به کمک امکانات Razor قابل تعریف هستند. تولید این نوع متدهای کمکی به این شکل نسبت به مثلا روش TagBuilder ساده‌تر است،‌ چون توسط Razor به سادگی و به نحو طبیعی‌تری می‌توان تگ‌های HTML و کدهای مورد نظر را با هم ترکیب کرد (این رفتار طبیعی و روان، یکی از اهداف Razor است).
به عنوان مثال، تعاریف همان کلاس‌های Product و Products قسمت قبل (قسمت هفتم) را در نظر بگیرید. با همان کنترلر و View ایی که ذکر شد.
سپس برای تعریف این نوع خاص از HTML Helpers/Razor Helpers باید به این نحو عمل کرد:
الف) در ریشه پروژه یا سایت، پوشه‌ی جدیدی به نام App_Code ایجاد کنید (دقیقا به همین نام. این پوشه، جزو پوشه‌های ویژه ASP.NET است).
ب) بر روی این پوشه کلیک راست کرده و گزینه Add|New Item را انتخاب کنید.
ج) در صفحه باز شده، MVC 3 Partial Page/Razor را یافته و مثلا نام ProductsList.cshtml را وارد کرده و این فایل را اضافه کنید.
د) محتوای این فایل جدید را به نحو زیر تغییر دهید:

@using MvcApplication4.Models
@helper GetProductsList(List<Product> products)
{
<ul>
@foreach (var item in products)
{
<li>@item.Name ($@item.Price)</li>
}
</ul>
}

در اینجا نحوه تعریف یک helper method مخصوص Razor را مشاهده می‌کنید که با کلمه @helper شروع شده است. مابقی آن هم ترکیب آشنای code و markup هستند که به کمک امکانات Razor به این شکل روان میسر شده است.
اکنون اگر Viewایی بخواهد از این اطلاعات استفاده کند تنها کافی است به نحو زیر عمل نماید:

@model List<MvcApplication4.Models.Product>
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@ProductsList.GetProductsList(@Model)

ابتدا نام فایل ذکر شده بعد نام متد کمکی تعریف شده در آن. Model هم در اینجا به لیستی از محصولات اشاره می‌کند.
همچنین چون در پوشه app_code قرار گرفته، تمام Viewها به اطلاعات آن دسترسی خواهند داشت. علت هم این است که ASP.NET به صورت خودکار محتوای این پوشه ویژه را همواره کامپایل می‌کند و در اختیار برنامه قرار می‌دهد.
به علاوه در این فایل ProductsList.cshtml، باز هم می‌توان متدهای helper دیگری را اضافه کرد و از این بابت محدودیتی ندارد. همچنین می‌توان این متد helper را مستقیما داخل یک View هم تعریف کرد. بدیهی است در این حالت قابلیت استفاده مجدد از آن‌را به همراه داشتن Viewهایی تمیز و کم حجم، از دست خواهیم داد.

جهت تکمیل بحث
Turn your Razor helpers into reusable libraries


مطالب
آزمون‌های یکپارچگی در برنامه‌های ASP.NET Core
تا اینجا دو روش را برای آزمایش کلی یک سیستم Web API به همراه تمام زیر ساخت‌های آن، بررسی کردیم:
- استفاده از Postman برای آزمایش یک برنامه‌ی Web API
- استفاده از strest برای آزمایش یک برنامه‌ی Web API

روش سومی هم برای انجام اینکار وجود دارد که به صورت توکار از زمان ارائه‌ی ASP.NET Core 2.1 به همراه TestServer آزمایشی آن میسر شد. این روش در نگارش 3.1، با تغییر روش تعریف فایل program.cs، جهت سازگاری آن با آزمون‌های یکپارچگی/آزمایش کل سیستم، بهبود یافته‌است که خلاصه‌ای از آن را در این مطلب بررسی می‌کنیم.


آزمون‌های یکپارچگی در ASP.NET Core

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

برای ایجاد آزمون‌های یکپارچگی در برنامه‌های ASP.NET Core، حداقل سه مرحله باید طی شوند:
الف) ایجاد یک class library که ارجاعی را به پروژه‌ی اصلی دارد. این پروژه حاوی آزمایش‌های ما خواهد بود.
ب) راه اندازی یک هاست وب آزمایشی برای ارسال درخواست‌ها به آن و دریافت پاسخ‌های نهایی.
ج) استفاده از یک test runner (انواع و اقسام فریم ورک‌های unit testing) برای اجرای آزمایش‌ها


ایجاد یک پروژه‌ی کتابخانه برای هاست و اجرای آزمایش‌های یکپارچگی

فرض کنید می‌خواهیم برای همان پروژه‌ی ایجاد JWTها، آزمایش یکپارچگی بنویسیم. پس از ایجاد یک پروژه‌ی کتابخانه‌ی جدید که قرار است هاست آزمایش‌های ما شود، نیاز است محتوای فایل csproj آن‌را به صورت زیر تغییر داد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <NoWarn>RCS1090</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\ASPNETCore2JwtAuthentication.WebApp\ASPNETCore2JwtAuthentication.WebApp.csproj" />
  </ItemGroup>
  <ItemGroup>
    <None Include="..\ASPNETCore2JwtAuthentication.WebApp\appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
  <ItemGroup>
    <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c329}" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="fluentassertions" Version="5.10.3" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.8" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
    <PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
  </ItemGroup>
</Project>
در اینجا، این نکات قابل مشاهده هستند:
1) TargetFramework آن باید به netcoreapp تنظیم شود.
2) باید ارجاع مستقیمی به کل پروژه‌ی نهایی WebApp در آن وجود داشته باشد. چون در ادامه می‌خواهیم فایل Program.cs آن‌را برای راه اندازی یک هاست وب آزمایشی، فراخوانی کنیم.
3) بسته‌ی نیوگتی که کار راه اندازی هاست وب آزمایشی را انجام می‌دهد، Microsoft.AspNetCore.Mvc.Testing نام دارد. این بسته، کار کپی فایل‌های پروژه‌ی اصلی و همچنین تنظیم مسیر پروژه را به این مسیر جدید نیز انجام می‌دهد.
4) روش افزودن بسته‌های MSTest را مشاهده می‌کنید.
5) همچنین جهت ساده‌تر شدن بررسی نتایج آزمون‌های انجام شده می‌توان از fluentassertions نیز استفاده کرد.


راه اندازی هاست وب آزمایشی جهت انجام آزمون‌های واحد

پس از انجام تنظیمات ابتدایی پروژه‌ی آزمون یکپارچگی، نیاز است یک WebApplicationFactory سفارشی را ایجاد کرد:
using ASPNETCore2JwtAuthentication.WebApp;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;

namespace ASPNETCore2JwtAuthentication.IntegrationTests
{
    public class CustomWebApplicationFactory : WebApplicationFactory<Program>
    {
        protected override IWebHostBuilder CreateWebHostBuilder()
        {
            var builder = base.CreateWebHostBuilder();
            builder.ConfigureLogging(logging =>
            {
                //TODO: ...
            });
            return builder;
        }

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureTestServices(services =>
            {
                // Don't run `IHostedService`s when running as a test
                services.RemoveAll(typeof(IHostedService));
            });
        }
    }
}
در این تعریف، Program در <WebApplicationFactory<Program، دقیقا به همان کلاس Program برنامه‌ی اصلی وب اشاره می‌کند. به همین جهت امضای این کلاس در نگارش 3.1 تغییر کرده‌است تا با WebApplicationFactory بسته‌ی Microsoft.AspNetCore.Mvc.Testing هماهنگ شود.
در ادامه روش سفارشی سازی WebApplicationFactory  را مشاهده می‌کنید. برای مثال اگر خواستید سرویس‌ها و تنظیمات پیش‌فرض برنامه‌ی اصلی را تغییر دهید می‌توانید متد CreateWebHostBuilder را بازنویسی کنید و یا اگر خواستید سرویس جدیدی را اضافه و یا حذف کنید، می‌توان متد ConfigureWebHost را بازنویسی کرد.


استفاده از WebApplicationFactory سفارشی، جهت ایجاد یک HttpClient

هدف اصلی از ایجاد CustomWebApplicationFactory نه فقط راه اندازی یک هاست وب سفارشی است، بلکه توسط متد CreateClient آن می‌توان به یک HttpClient دسترسی یافت که قابلیت ارسال اطلاعات را به برنامه‌ی وبی که در پشت صحنه راه اندازی می‌شود، دارا است. کار CustomWebApplicationFactory شبیه به راه اندازی dotnet run در پشت صحنه‌است. در اینجا دیگر نیازی نیست تا اینکار را به صورت دستی انجام داد. به همین جهت چون برنامه‌ی وب اصلی به نحو متداولی در پشت صحنه اجرا می‌شود، عموما راه اندازی آن که شامل تنظیمات اولیه و یا حتی ایجاد بانک اطلاعاتی است، کمی کند است و اگر قرار باشد هربار اینکار صورت گیرد، به آزمون‌های بسیار کندی خواهیم رسید. به همین جهت می‌توان یک کلاس singleton را برای مدیریت تک وهله‌ی نهایی HttpClient آن به صورت زیر ایجاد کرد:
using System;
using System.Threading;
using System.Net.Http;

namespace ASPNETCore2JwtAuthentication.IntegrationTests
{
    public static class TestsHttpClient
    {
        private static readonly Lazy<HttpClient> _serviceProviderBuilder =
                new Lazy<HttpClient>(getHttpClient, LazyThreadSafetyMode.ExecutionAndPublication);

        /// <summary>
        /// A lazy loaded thread-safe singleton
        /// </summary>
        public static HttpClient Instance { get; } = _serviceProviderBuilder.Value;

        private static HttpClient getHttpClient()
        {
            var services = new CustomWebApplicationFactory();
            return services.CreateClient(); //NOTE: This action is very time consuming, so it should be defined as a singleton.
        }
    }
}
مزیت کار با این کلاس، عدم راه اندازی مجدد برنامه به ازای هر آزمون انجام شده‌است. چون به ازای هر آزمونی که خواهیم نوشت، نیاز است به HttpClient دسترسی داشته باشیم.


نوشتن اولین آزمون یکپارچگی

پس از تنظیم هاست وب آزمایشی و ایجاد یک HttpClient از پیش تنظیم شده که به آن اشاره می‌کند، اکنون می‌توان اولین آزمون یکپارچگی را به صورت زیر نوشت:
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ASPNETCore2JwtAuthentication.IntegrationTests
{
    [TestClass]
    public class JwtTests
    {
        [TestMethod]
        public async Task TestLoginWorks()
        {
            // Arrange
            var client = TestsHttpClient.Instance;

            // Act
            var token = await doLoginAsync(client);

            // Assert
            token.Should().NotBeNull();
            token.AccessToken.Should().NotBeNullOrEmpty();
            token.RefreshToken.Should().NotBeNullOrEmpty();
        }

        [TestMethod]
        public async Task TestCallProtectedApiWorks()
        {
            // Arrange
            var client = TestsHttpClient.Instance;

            // Act
            var token = await doLoginAsync(client);

            // Assert
            token.Should().NotBeNull();
            token.AccessToken.Should().NotBeNullOrEmpty();
            token.RefreshToken.Should().NotBeNullOrEmpty();

            // Act
            const string protectedApiUrl = "/api/MyProtectedApi";
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
            var response = await client.GetAsync(protectedApiUrl);
            response.EnsureSuccessStatusCode();

            // Assert
            var responseString = await response.Content.ReadAsStringAsync();
            responseString.Should().NotBeNullOrEmpty();
            var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
            var apiResponse = JsonSerializer.Deserialize<MyProtectedApiResponse>(responseString, options);
            apiResponse.Title.Should().NotBeNullOrEmpty();
            apiResponse.Title.Should().Be("Hello from My Protected Controller! [Authorize]");
        }

        private static async Task<Token> doLoginAsync(HttpClient client)
        {
            const string loginUrl = "/api/account/login";
            var user = new { Username = "Vahid", Password = "1234" };
            var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, loginUrl)
            {
                Content = new StringContent(JsonSerializer.Serialize(user), Encoding.UTF8, "application/json")
            });
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            responseString.Should().NotBeNullOrEmpty();
            return JsonSerializer.Deserialize<Token>(responseString);
        }
    }
}
توضیحات:
- در هر آزمونی نیاز است در ابتدا به TestsHttpClient.Instance، که همان HttpClient ساخته شده‌ی توسط CustomWebApplicationFactory است، دسترسی یافت و همانطور که عنوان شد، دسترسی به وهله‌ای از HttpClient که به هاست وب آزمایشی برنامه‌ی اصلی اشاره می‌کند، عموما بسیار زمانبراست و برای مثال در دو آزمایش نوشته شده‌ی در اینجا اگر قرا باشد هربار اینکار از صفر انجام شود، زمان به اتمام رسیدن این آزمایش‌ها بسیار طولانی خواهد شد. به همین جهت طول عمر TestsHttpClient را singleton تعریف کردیم تا فقط یکبار کار برپایی وب سرور آزمایشی در پشت صحنه انجام شود.
- سپس مابقی کار، همان روش استاندارد کار با HttpClient است. در ابتدا درخواستی را به سمت سرور آزمایشی که در پشت صحنه در حال اجرا است، ارسال می‌کنیم. چون HttpClient دریافتی توسط  CustomWebApplicationFactory تنظیم شده‌است، دیگر نیازی به ذکر آدرس پایه‌ی وب سایت مانند https://localhost:5001 نیست و آدرس‌های ذکر شده‌ی در اینجا، نسبی هستند. سپس محتوای Response دریافتی از سرور را جهت تکمیل آزمایشات، بررسی خواهیم کرد.


یک نکته: اگر OpenAPI را در برنامه‌های Web API فعال کنید، می‌توان با استفاده از ابزارهای تولید کد، کدهای مرتبط با HttpClient را نیز به صورت خودکار تولید و سپس از آن‌ها در اینجا استفاده کرد.


اجرای آزمون‌های یکپارچگی نوشته شده

چون ظاهر این آزمون‌ها با آزمون‌های واحد MSTest یا هر فریم ورک مشابه دیگری یکسان است، می‌توان از امکانات IDEها برای اجرای آن‌ها استفاده کرد و یا حتی می‌توان دستور dotnet test را نیز در ریشه‌ی این پروژه‌ی جدید برای اجرای تمام آزمون‌های نوشته شده، اجرا کرد:



کدهای کامل این مطلب را در اینجا می‌توانید مشاهده کنید.
مطالب
MVC Scaffolding #1
پیشنیازها
کل سری ASP.NET MVC
به همراه کل سری EF Code First


MVC Scaffolding چیست؟

MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامه‌های ASP.NET MVC مبتنی بر EF Code First.


بررسی مقدماتی MVC Scaffolding

امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژه‌های ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندی‌ها و قابلیت‌های سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [DisplayName("Is Complete")]
        public bool IsComplete { set; get; }

        [StringLength(450)]
        public string Description { set; get; }
    }
}
سپس بر روی پوشه Controllers کلیک راست کرده و گزینه Add controller را انتخاب کنید. تنظیمات صفحه ظاهر شده را مطابق شکل زیر تغییر دهید:


همانطور که ملاحظه می‌کنید در قسمت قالب‌ها، تولید کنترلرهایی با اکشن متد‌های ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آن‌را پذیرفته‌ایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:


الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدل‌های برنامه ایجاد می‌گردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه می‌کند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلی‌های دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)

معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شده‌ای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی می‌تواند شامل فیلدهای بیشتری باشد و نباید آن‌را مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).

مزیت‌ها:
قسمت عمده‌ای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شده‌اند. کارهای اولیه‌ای که با هر روش و الگوی شناخته شده‌ای قصد پیاده سازی آن‌ها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمده‌ای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرم‌های آن‌را بخواهید با jQuery Ajax پیاده سازی کنید یا کنترل‌های پیش فرض را با افزونه‌های jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.


نصب بسته اصلی MVC Scaffolding توسط NuGet

بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console می‌توان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
اگر به مراحل نصب آن دقت کنید یک سری وابستگی را نیز به صورت خودکار دریافت کرده و نصب می‌کند:
Attempting to resolve dependency 'T4Scaffolding'.
Attempting to resolve dependency 'T4Scaffolding.Core'.
Attempting to resolve dependency 'EntityFramework'.
Successfully installed 'T4Scaffolding.Core 1.0.0'.
Successfully installed 'T4Scaffolding 1.0.8'.
Successfully installed 'MvcScaffolding 1.0.9'.
Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1.
Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1.
Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
از مواردی که با T4 آغاز شده‌اند در قسمت‌های بعدی برای سفارشی سازی کدهای تولیدی استفاده خواهیم کرد.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز می‌توان پیاده سازی کرد:
Scaffold Controller Task
نوشتن این دستور نیز ساده است. حروف sca را تایپ کرده و دکمه tab را فشار دهید. منویی ظاهر خواهد شد که امکان انتخاب دستور Scaffold را می‌دهد. یا برای نوشتن Controller نیز به همین نحو می‌توان عمل کرد.
نکته و مزیت مهم دیگری که در اینجا در دسترس می‌باشد، سوئیچ‌های خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچ‌های قابل استفاده است.


برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
اگر این دستور را اجرا کنیم به همان نتیجه حاصل از مراحل توضیح داده شده قبل خواهیم رسید؛ البته یا یک تفاوت: یک Partial View اضافه‌تر نیز به نام CreateOrEdit در پوشه Views\Tasks ایجاد شده است. این Partial View بر اساس بازخورد برنامه نویس‌ها مبنی بر اینکه Viewهای Edit و Create بسیار شبیه به هم هستند، ایجاد شده است.


بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding

در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
البته اگر دستور فوق را به همین نحو اجرا کنید با یک سری خطای Skipping مواجه خواهید شد مبنی بر اینکه فایل‌های قبلی موجود هستند و این دستور قصد بازنویسی آن‌ها را ندارد. برای اجبار به تولید مجدد کدهای موجود می‌توان از سوئیچ Force استفاده کرد:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
اتفاقی که در اینجا رخ خواهد داد، بازنویسی کد بی‌کیفت ابتدایی همراه با وهله سازی مستقیم DbContext در کنترلر، به نمونه بهتری که از الگوی مخزن استفاده می‌کند می‌باشد:
    public class TasksController : Controller
    {
        private readonly ITaskRepository taskRepository;

        // If you are using Dependency Injection, you can delete the following constructor
        public TasksController()
            : this(new TaskRepository())
        {
        }

        public TasksController(ITaskRepository taskRepository)
        {
            this.taskRepository = taskRepository;
        }
کیفیت کد تولیدی جدید مبتنی بر الگوی مخزن بد نیست؛ دقیقا همانی است که در هزاران سایت اینترنتی تبلیغ می‌شود؛ اما ... آنچنان مناسب هم نیست و اشکالات زیر را به همراه دارد:
public interface ITaskRepository : IDisposable
{
  IQueryable<Task> All { get; }
  IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties);
  Task Find(int id);
  void InsertOrUpdate(Task task);
  void Delete(int id);
  void Save();
}
اگر به ITaskRepository تولیدی دقت کنیم دارای خروجی IQueryable است؛ به این حالت leaky abstraction گفته می‌شود. زیرا امکان تغییر کلی یک خروجی IQueryable در لایه‌های دیگر برنامه وجود دارد و حد و مرز سیستم توسط آن مشخص نخواهد شد. بهتر است خروجی‌های لایه سرویس یا لایه مخزن در اینجا از نوع‌های IList یا IEnumerable باشند که درون آن‌ها از IQueryable‌ها برای پیاده سازی منطق مورد نظر کمک گرفته شده است.
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.