مطالب
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 داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.
نظرات مطالب
هدایت درخواست فایل‌های استاتیک در ASP.NET MVC به یک کنترلر
فرقی نمی‌کند: Mvc5RouteExistingFiles.zip
فقط اگر قرار است تمام فایل‌های استاتیک پوشه‌ی export، منهای تصاویر jpg آن route شوند، باید یک سطر زیر را هم اضافه کنید:
routes.IgnoreRoute("export/{resource}.jpg");
نظرات مطالب
الگوی مشاهده‌گر Observer Pattern
یکی از مثال هایی که خودم چند روز پیش از این الگو در جاوا برای اندروید استفاده کردم این است که در بخشی از برنامه انتقال اطلاعاتی صورت میگرفت که شاید نیاز باشد هزاران آیتم در برنامه انتقال یابند که در این صورت بهتر بود که فعالیت توسط یک نوار پیشرفت نمایش داده شود و از آنجا که این انتقال برای آیتم‌ها در کلاسی جداگانه قرار داشت و در این حالت ممکن بود تکه کد نامربوط بین بخش رابط کاربری و منطق اضافه کند و وابستگی ایجاد کند از این الگو استفاده کردم. در این حالت هر وقت متد مورد نظر انتقالی انجام میداد کلاس پیشرفت را برای محاسبه درصد آگاه می‌ساخت.
مطالب
هدایت درخواست فایل‌های استاتیک در ASP.NET MVC به یک کنترلر
فرض کنید یک پوشه Export در ریشه سایت دارید که حاوی تعدادی فایل PDF عمومی است.
سؤال: آیا می‌شود دسترسی به فایل‌های قرار گرفته در این پوشه عمومی را کنترل کرد؟ به نحوی که فقط کاربران عضو سایت پس از اعتبارسنجی بتوانند آن‌ها را دریافت کنند؟
پاسخ: شاید عنوان کنید که می‌توان از تگ location در فایل web.config برای اینکار استفاده کرد:
<location path="Export">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>
این تنظیمات هیچ اثری بر روی فایل‌های استاتیک PDF ندارند؛ چون در IIS 6 از موتور ASP.NET رد نخواهند شد. مگر اینکه این نوع پسوند‌ها به aspnet_isapi.dll انتساب داده شوند. در IIS 7 به بعد این وضع بهبود یافته است. اگر تنظیم runAllManagedModulesForAllRequests در وب کانفیگ برنامه به true تنظیم شده باشد و برنامه در حالت Integrated pipeline بجای Classic mode اجرا شود، امکان مدیریت فایل‌های استاتیک نیز در برنامه‌های ASP.NET وجود دارد .

سؤال: آیا راه حلی وجود دارد که بتوان فایل‌های استاتیک را صرفنظر از نوع، نگارش و حالت اجرای IIS توسط موتور ASP.NET مدیریت کرد؟
پاسخ: بلی. در ASP.NET MVC با تنظیم یک سطر ذیل، اینکار انجام می‌شود:
public static void RegisterRoutes(RouteCollection routes)
{
   // ...
   routes.RouteExistingFiles = true;
   // ...
}
توضیحات:
RouteCollection در ASP.NET MVC به کمک امکانات MapPathBasedVirtualPathProvider خود، ابتدا درخواست رسیده را بررسی می‌کند. اگر این درخواست به یک فایل عمومی اشاره کند، کل سیستم مسیریابی را غیرفعال می‌کند. اما با تنظیم RouteExistingFiles دیگر این بررسی صورت نخواهد گرفت (به عبارتی در بالا بردن سرعت نمایشی سایت نیز تاثیر گذار خواهد بود؛ چون یکی از بررسی‌ها را حذف می‌کند).


ایجاد کنترلری به نام پوشه‌ای که قرار است محافظت شود

نام پوشه قرار گرفته در ریشه سایت، Export است. بنابراین برای هدایت درخواست‌های رسیده به آن (پس از تنظیم فوق)، نیاز است یک کنترلر جدید را به نام Export نیز ایجاد کنیم:
using System.IO;
using System.Web.Mvc;

namespace Mvc4RouteExistingFiles.Controllers
{
    public class ExportController : Controller
    {
        public ActionResult Index(string id)
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                return Redirect("/");
            }

            var fileName= Path.GetFileName(id);
            var path = Server.MapPath("~/export/"+ fileName);
            return File(path, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
        }
    }
}
البته بدیهی است در اینجا می‌توان فیلتر Authorize را به کل کنترلر اعمال کرد، یا هر تنظیم دیگری که نیاز است.
برای اینکه بررسی کنیم، آیا واقعا فایل‌های استاتیک قرار گرفته در پوشه Export به این کنترلر هدایت می‌شود یا خیر، یک breakpoint را بر روی سطر اول اکشن متد Index قرار می‌دهیم. برنامه را اجرا کنید ... کار نخواهد کرد، زیرا مسیر یک فایل فرضی به صورت ذیل:
 http://localhost/export/test.pdf
به اکشن متد Index کنترلر Export، نگاشت نخواهد شد (index در این مسیر ذکر نشده است).
برای حل این مشکل فقط کافی است مسیر یابی متناظری را تعریف کنیم:
using System.Web.Mvc;
using System.Web.Routing;

namespace Mvc4RouteExistingFiles
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.RouteExistingFiles = true;

            routes.MapRoute(
                name: "ExportRoute",
                url: "Export/{id}",
                defaults: new { controller = "Export", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}
در اینجا ExportRoute را مشاهده می‌کنید که به آدرس‌هایی به فرم Export/id پاسخ می‌دهد. در این حالت به صورت خودکار با توجه به تنظیمات انجام شده، اکشن متدی که انتخاب می‌شود همان Index خواهد بود و نیازی به ذکر صریح آن نخواهد بود.
اینبار اگر برنامه را اجرا کنیم، breakpoint ما کار خواهد کرد:



تنظیمات ثانویه پس از فعال سازی RouteExistingFiles

در این حالت با فعال سازی مسیریابی فایل‌های موجود، دیگر هیچ فایل استاتیکی به صورت معمول در اختیار کاربران قرار نخواهد گرفت و اگر همانند توضیحات قبل برای آن‌ها کنترلر جداگانه‌ای را تهیه نکنیم، عملا سایت از کار خواهد افتاد.
برای رفع این مشکل، در ابتدای متد RegisterRoutes فوق، تنظیمات ذیل را اضافه کنید تا پوشه‌های content، scripts و همچنین یک سری فایل با پسوند مشخص، همانند سابق و مستقیما توسط سرور ارائه شوند؛ در غیراینصورت کاربر پیغام 404 را پس از درخواست آن‌ها، دریافت خواهد کرد:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("scripts/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.IgnoreRoute("{resource}.ico");
routes.IgnoreRoute("{resource}.png");
routes.IgnoreRoute("{resource}.jpg");
routes.IgnoreRoute("{resource}.gif");
routes.IgnoreRoute("{resource}.txt");
مطالب
ایجاد صفحات راهنما برای ASP.NET Web API
وقتی یک Web API می‌سازید بهتر است صفحات راهنمایی هم برای آن در نظر بگیرید، تا توسعه دهندگان بدانند چگونه باید سرویس شما را فراخوانی و استفاده کنند. گرچه می‌توانید مستندات را بصورت دستی ایجاد کنید، اما بهتر است تا جایی که ممکن است آنها را بصورت خودکار تولید نمایید.

بدین منظور فریم ورک ASP.NET Web API کتابخانه ای برای تولید خودکار صفحات راهنما در زمان اجرا (run-time) فراهم کرده است.


ایجاد صفحات راهنمای API

برای شروع ابتدا ابزار ASP.NET and Web Tools 2012.2 Update را نصب کنید. اگر از ویژوال استودیو 2013 استفاده می‌کنید این ابزار بصورت خودکار نصب شده است. این ابزار صفحات راهنما را به قالب پروژه‌های ASP.NET Web API اضافه می‌کند.

یک پروژه جدید از نوع ASP.NET MVC Application بسازید و قالب Web API را برای آن انتخاب کنید. این قالب پروژه کنترلری بنام ValuesController را بصورت خودکار برای شما ایجاد می‌کند. همچنین صفحات راهنمای API هم برای شما ساخته می‌شوند. تمام کد مربوط به صفحات راهنما در قسمت Areas قرار دارند.

اگر اپلیکیشن را اجرا کنید خواهید دید که صفحه اصلی لینکی به صفحه راهنمای API دارد. از صفحه اصلی، مسیر تقریبی Help/ خواهد بود.

این لینک شما را به یک صفحه خلاصه (summary) هدایت می‌کند.

نمای این صفحه در مسیر Areas/HelpPage/Views/Help/Index.cshtml قرار دارد. می‌توانید این نما را ویرایش کنید و مثلا قالب، عنوان، استایل‌ها و دیگر موارد را تغییر دهید.

بخش اصلی این صفحه متشکل از جدولی است که API‌‌ها را بر اساس کنترلر طبقه بندی می‌کند. مقادیر این جدول بصورت خودکار و توسط اینترفیس IApiExplorer تولید می‌شوند. در ادامه مقاله بیشتر درباره این اینترفیس صحبت خواهیم کرد. اگر کنترلر جدیدی به API خود اضافه کنید، این جدول بصورت خودکار در زمان اجرا بروز رسانی خواهد شد.

ستون "API" متد HTTP و آدرس نسبی را لیست می‌کند. ستون "Documentation" مستندات هر API را نمایش می‌دهد. مقادیر این ستون در ابتدا تنها placeholder-text است. در ادامه مقاله خواهید دید چگونه می‌توان از توضیحات XML برای تولید مستندات استفاده کرد.

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


افزودن صفحات راهنما به پروژه ای قدیمی

می توانید با استفاده از NuGet Package Manager صفحات راهنمای خود را به پروژه‌های قدیمی هم اضافه کنید. این گزینه مخصوصا هنگامی مفید است که با پروژه ای کار می‌کنید که قالب آن Web API نیست.

از منوی Tools گزینه‌های Library Package Manager, Package Manager Console را انتخاب کنید. در پنجره Package Manager Console فرمان زیر را وارد کنید.

Install-Package Microsoft.AspNet.WebApi.HelpPage
این پکیج اسمبلی‌های لازم برای صفحات راهنما را به پروژه اضافه می‌کند و نماهای MVC را در مسیر Areas/HelpPage می‌سازد. اضافه کردن لینکی به صفحات راهنما باید بصورت دستی انجام شود. برای اضافه کردن این لینک به یک نمای Razor از کدی مانند لیست زیر استفاده کنید.

@Html.ActionLink("API", "Index", "Help", new { area = "" }, null)

همانطور که مشاهده می‌کنید مسیر نسبی صفحات راهنما "Help/" می‌باشد. همچنین اطمینان حاصل کنید که ناحیه‌ها (Areas) بدرستی رجیستر می‌شوند. فایل Global.asax را باز کنید و کد زیر را در صورتی که وجود ندارد اضافه کنید.
protected void Application_Start()
{
    // Add this code, if not present.
    AreaRegistration.RegisterAllAreas();

    // ...
}

افزودن مستندات API

بصورت پیش فرض صفحات راهنما از placeholder-text برای مستندات استفاده می‌کنند. می‌توانید برای ساختن مستندات از توضیحات XML استفاده کنید. برای فعال سازی این قابلیت فایل Areas/HelpPage/App_Start/HelpPageConfig.cs را باز کنید و خط زیر را از حالت کامنت درآورید:

config.SetDocumentationProvider(new XmlDocumentationProvider(
    HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
حال روی نام پروژه کلیک راست کنید و Properties را انتخاب کنید. در پنجره باز شده قسمت Build را کلیک کنید.

زیر قسمت Output گزینه XML documentation file را تیک بزنید و در فیلد روبروی آن مقدار "App_Data/XmlDocument.xml" را وارد کنید.

حال کنترلر ValuesController را از مسیر Controllers/ValuesController.cs/ باز کنید و یک سری توضیحات XML به متدهای آن اضافه کنید. بعنوان مثال:

/// <summary>
/// Gets some very important data from the server.
/// </summary>
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

/// <summary>
/// Looks up some data by ID.
/// </summary>
/// <param name="id">The ID of the data.</param>
public string Get(int id)
{
    return "value";
}

اپلیکیشن را مجددا اجرا کنید و به صفحات راهنما بروید. حالا مستندات API شما باید تولید شده و نمایش داده شوند.

صفحات راهنما مستندات شما را در زمان اجرا از توضیحات XML استخراج می‌کنند. دقت کنید که هنگام توزیع اپلیکیشن، فایل XML را هم منتشر کنید.


توضیحات تکمیلی

صفحات راهنما توسط کلاس ApiExplorer تولید می‌شوند، که جزئی از فریم ورک ASP.NET Web API است. به ازای هر API این کلاس یک ApiDescription دارد که توضیحات لازم را در بر می‌گیرد. در اینجا منظور از "API" ترکیبی از متدهای HTTP و مسیرهای نسبی است. بعنوان مثال لیست زیر تعدادی API را نمایش می‌دهد:

  • GET /api/products
  • {GET /api/products/{id
  • POST /api/products

اگر اکشن‌های کنترلر از متدهای متعددی پشتیبانی کنند، ApiExplorer هر متد را بعنوان یک API مجزا در نظر خواهد گرفت. برای مخفی کردن یک API از ApiExplorer کافی است خاصیت ApiExplorerSettings را به اکشن مورد نظر اضافه کنید و مقدار خاصیت IgnoreApi آن را به true تنظیم نمایید.

[ApiExplorerSettings(IgnoreApi=true)]
public HttpResponseMessage Get(int id) {  }

همچنین می‌توانید این خاصیت را به کنترلر‌ها اضافه کنید تا تمام کنترلر از ApiExplorer مخفی شود.

کلاس ApiExplorer متن مستندات را توسط اینترفیس IDocumentationProvider دریافت می‌کند. کد مربوطه در مسیر Areas/HelpPage/XmlDocumentation.cs/ قرار دارد. همانطور که گفته شد مقادیر مورد نظر از توضیحات XML استخراج می‌شوند. نکته جالب آنکه می‌توانید با پیاده سازی این اینترفیس مستندات خود را از منبع دیگری استخراج کنید. برای اینکار باید متد الحاقی SetDocumentationProvider را هم فراخوانی کنید، که در HelpPageConfigurationExtensions تعریف شده است.

کلاس ApiExplorer بصورت خودکار اینترفیس IDocumentationProvider را فراخوانی می‌کند تا مستندات API‌ها را دریافت کند. سپس مقادیر دریافت شده را در خاصیت Documentation ذخیره می‌کند. این خاصیت روی آبجکت‌های ApiDescription و ApiParameterDescription تعریف شده است.


مطالعه بیشتر

مطالب
آموزش 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 بسازید که در پست بعدی به صورت کامل شرح داده خواهد شد.
مطالب
Blazor 5x - قسمت دوازدهم - مبانی Blazor - بخش 9 - یک تمرین
تا اینجا با مبانی Blazor آشنا شدیم. در این قسمت می‌خواهیم مثالی را بررسی کنیم که بسیاری از این مفاهیم ابتدایی را پوشش می‌دهد. برای نمونه می‌خواهیم یک کامپوننت modal بوت استرپی را جهت دریافت تائیدیه‌ی حذف اتاق‌های تعریف شده‌ی در مثال این سری نمایش دهیم که به همراه مفاهیمی است مانند فرگمنت‌ها جهت تعیین محتوای نمایشی مودال به صورت پویا، ارسال نتیجه‌ی انتخاب بله یا خیر از کامپوننت دریافت تائید، به کامپوننت والد، ارسال پارامترها به کامپوننت فرزند جهت نمایش عنوان و فراخوانی متدهای نمایش و مخفی کردن وهله‌ای از کامپوننت مودال، در کامپوننت والد؛ بدون یک سطر کدنویسی جاوا اسکریپتی!


مرور مثال این قسمت

تا اینجا در مثالی که بررسی کردیم، لیست اتاق‌ها توسط کامپوننت IndividualRoom.razor و لیست خدمات رفاهی یک هتل توسط کامپوننت IndividualAmenity.razor در کامپوننت والد DemoHotel.razor، نمایش داده شده‌اند:


دکمه‌های حذف و ویرایش هر اتاق نیز در کامپوننت EditDeleteButton.razor قرار دارند که توسط کامپوننت IndividualRoom.razor مورد استفاده قرار می‌گیرند.
اکنون می‌خواهیم با کلیک بر روی دکمه‌ی حذف کامپوننت EditDeleteButton، یک modal بوت استرپی جهت دریافت تائیدیه‌ی عملیات، نمایش داده شود و در صورت تائید آن، اتاق انتخابی از لیست اتاق‌های کامپوننت DemoHotel حذف گردد.


بنابراین در ابتدا کامپوننت EditDeleteButton، به کامپوننت IndividualRoom خبر درخواست حذف یک اتاق را می‌دهد. سپس کامپوننت IndividualRoom، یک مودال دریافت تائیدیه‌ی حذف را نمایش می‌دهد. پس از تائید حذف توسط کاربر، این رویداد به کامپوننت DemoHotel، جهت حذف اتاق انتخابی از لیست اتاق‌ها، اطلاع رسانی خواهد شد.


ایجاد کامپوننت مودال دریافت تائید

در ابتدا، فایل جدید Pages\LearnBlazor\LearnBlazor‍Components\Confirmation.razor را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
@if (ShowModal)
{
    <div class="modal-backdrop show"></div>

    <div class="modal fade show" id="exampleModal" tabindex="-1"
        role="dialog" aria-labelledby="exampleModalLabel"
        aria-hidden="true" style="display: block;">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">
                        @Title
                    </h5>
                    <button @onclick="OnCancelClicked" type="button" class="close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    @ChildContent
                </div>
                <div class="modal-footer">
                    <button @onclick="OnCancelClicked" type="button" class="btn btn-secondary">@CancelButtonLabel</button>
                    <button @onclick="OnConfirmClicked" type="button" class="btn btn-primary">@OkButtonLabel</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private bool ShowModal;

    [Parameter] public string Title { get; set; } = "Confirm";

    [Parameter] public string CancelButtonLabel { get; set; } = "Cancel";

    [Parameter] public string OkButtonLabel { get; set; } = "Ok";

    [Parameter] public RenderFragment ChildContent { get; set; }

    [Parameter] public EventCallback OnConfirm { get; set; }

    [Parameter] public EventCallback OnCancel { get; set; }

    public void Show() => ShowModal = true;

    public void Hide() => ShowModal = false;

    private async Task OnConfirmClicked()
    {
        ShowModal = false;
        await OnConfirm.InvokeAsync();
    }

    private async Task OnCancelClicked()
    {
        ShowModal = false;
        await OnCancel.InvokeAsync();
    }
}
توضیحات:
- در اینجا در ابتدا تگ‌ها و کلاس‌های مرتبط با نمایش یک modal استاندارد بوت استرپی را مشاهده می‌کنید.
- اگر فیلد خصوصی ShowModal به false تنظیم شود، چون کل محتوای این کامپوننت از DOM حذف خواهد شد (اثر if@ تعریف شده)، سبب مخفی شدن و عدم نمایش آن می‌گردد.
- این کامپوننت عنوان و برچسب‌های دکمه‌های خودش را به صورت پارامتر دریافت می‌کند.
- برای اینکه بتوان محتوای نمایشی این کامپوننت را پویا کرد، از یک RenderFragment استفاده کرده‌ایم:
[Parameter] public RenderFragment ChildContent { get; set; }
- خروجی این کامپوننت به والد یا فراخوان آن، دو رویداد OnConfirm و OnCancel هستند. همچنین چون نمی‌خواهیم کدهای مخفی کردن modal را به ازای هربار کلیک بر روی این دکمه‌ها فراخوانی کنیم، این رویدادها، ابتدا به دو متد خصوصی OnConfirmClicked و OnCancelClicked متصل شده‌اند، تا کار مخفی سازی و سپس هدایت این رویدادها را به کامپوننت والد انجام دهند.
- همچنین می‌خواهیم به کامپوننت فراخوان این امکان را بدهیم تا بتواند به صورت مستقل، سبب نمایش یا مخفی شدن وهله‌ای از این کامپوننت شود. به همین جهت دو متد عمومی Show و Hide نیز تعریف شده‌اند.


هدایت درخواست Delete به کامپوننت نمایش مشخصات اتاق

با توجه به اینکه دکمه‌های حذف و ویرایش هر اتاق، در کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\EditDeleteButton.razor قرار دارند، به آن مراجعه کرده و امکان انتشار این رخ‌داد را به فراخوان آن، با تعریف رویداد OnDelete می‌دهیم:
@if (IsAdmin)
{
    <input type="button" class="btn btn-danger" value="Delete" @onclick="OnDelete" />
    <input type="button" class="btn btn-success" value="Edit" />
}

@code
{
    [Parameter]  public bool IsAdmin { get; set; }

    [Parameter] public EventCallback OnDelete { get; set; }
}


واکنش نشان دادن کامپوننت IndividualRoom.razor به درخواست حذف آن اتاق

کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\IndividualRoom.razor که نمایش دهنده‌ی جزئیات هر اتاق است، با مدیریت رویداد OnDelete کامپوننت EditDeleteButton، از درخواست حذف اتاق جاری مطلع می‌شود:
<EditDeleteButton IsAdmin="true" OnDelete="OnDeleteClicked"></EditDeleteButton>

<Confirmation @ref="Confirmation1"
    OnCancel="OnCancelClicked"
    OnConfirm="@(() => OnDeleteSelectedRoom.InvokeAsync(Room))">
    <div>
        Do you want to delete `@Room.Name`?
    </div>
</Confirmation>
- در اینجا در ابتدا کامپوننت جدید Confirmation را مورد استفاده قرار داده و برای مثال محتوای «آیا می‌خواهید این اتاق را حذف کنید؟»، به صورت پویا به آن ارسال می‌کنیم که در این کامپوننت، توسط فرگمنت مرتبطی نمایش داده می‌شود.
- سپس نیاز است زمانیکه OnDelete کامپوننت EditDeleteButton رخ‌داد، این modal دریافت تائید را نمایش دهیم. به همین جهت باید بتوانیم متد عمومی Show آن‌را فراخوانی کنیم. بنابراین از ref@ برای دسترسی به وهله‌ای از این کامپوننت تعریف شده استفاده کرده‌ایم تا توسط شیء Confirmation1، بتوانیم متد عمومی Show را در رویدادگردان منتسب به OnDelete فراخوانی کنیم.
- همچنین دو رویداد OnCancel و OnConfirm کامپوننت دریافت تائید را به متد خصوصی OnCancelClicked و رویداد جدید OnDeleteSelectedRoom متصل کرده‌ایم. یعنی زمانیکه کاربر بر روی دکمه‌ی OK مودال ظاهر شده کلیک می‌کند، Room جاری، از طریق رویداد OnDeleteSelectedRoom به فراخوان کامپوننت IndividualRoom ارسال می‌شود تا دقیقا بداند که چه اتاقی را بایدحذف کند:
@code
{
    Confirmation Confirmation1;

    [Parameter]
    public BlazorRoom Room { get; set; }

    [Parameter]
    public EventCallback<BlazorRoom> OnDeleteSelectedRoom { get; set; }

    void OnDeleteClicked()
    {
        Confirmation1.Show();
    }

    void OnCancelClicked()
    {
        // Confirmation1.Hide();
    }

   // ...
}
بنابراین کامپوننت IndividualRoom، یک شیء Room را از والد خود دریافت کرده و مشخصات آن‌را نمایش می‌دهد. همچنین پس از تائید حذف این اتاق، آن‌را از طریق رویداد جدید OnDeleteSelectedRoom به والد خود اطلاع رسانی می‌کند.


حذف اتاق انتخابی در کامپوننت نمایش لیست اتاق‌ها

مرحله‌ی آخر این مثال، بسیار ساده‌است. در حلقه‌ای که هر اتاق را توسط کامپوننت IndividualRoom نمایش می‌دهد، به رویداد OnDeleteSelectedRoom گوش فرا داده و selectedRoom یا همان BlazorRoom ارسالی را، دریافت و از لیست Rooms کامپوننت جاری حذف می‌کنیم. این حذف شدن، بلافاصله سبب رندر مجدد UI و حذف آن از رابط کاربری نیز خواهد شد:
@foreach (var room in Rooms)
        {
            <IndividualRoom
                OnRoomCheckBoxSelection="RoomSelectionCounterChanged"
                Room="room"
                OnDeleteSelectedRoom="@(selectedRoom => Rooms.Remove(selectedRoom))">
            </IndividualRoom>
        }


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-12.zip
مطالب
آشنایی با CLR: قسمت بیست و سوم
در قسمت قبل یاد گرفتیم که اگر یک ناشر، باگی را در یک اسمبلی برطرف کند، باید مدیر سیستم با تغییر فایل پیکربندی، نسخه‌ی جدید اسمبلی را به CLR معرفی کند. ولی اگر ناشر بخواهد این اسمبلی را در اختیار همه‌ی کاربرانش قرار دهد و به همه‌ی کاربران بگوید که باید فایل را تغییر دهید، این روش آن چنان راحت و مناسب نیست. به همین دلیل ناشر باید یک فایل تعیین سیاست داشته باشد، تا این کار راحت صورت بگیرد.
بیایید فرض کنیم که شما، ناشر این اسمبلی جدید با رفع باگ‌های اسمبلی قبلی هستید. موقعیکه شما یک اسمبلی جدید را توزیع می‌کنید، به همراه آن فایل پیکربندی جدیدی را تشکیل داده و ارسال می‌کنید. این فایل پیکربندی جدید دقیقا شبیه به همان فایل پیکربندی است که با آن آشنا شدیم، منتها همنام اسمبلی مورد نظر می‌باشد؛ مثل dotnettips.config برای dotnettips.dll.
<configuration>
     <runtime>
        <assemblyBinding xmlns="urn:schemas­microsoft­com:asm.v1">
           <dependentAssembly>
                <assemblyIdentity name="SomeClassLibrary"  publicKeyToken="32ab4ba45e0a69a1" culture="neutral"/>
                <bindingRedirect   oldVersion="1.0.0.0" newVersion="2.0.0.0" />
                <codeBase version="2.0.0.0"  href="http://www.Wintellect.com/SomeClassLibrary.dll"/>
             </dependentAssembly>
        </assemblyBinding>
     </runtime>
  </configuration>
در واقع این فایل، تنها یک تعیین سیاست برای این اسمبلی است و تنها از همین المان‌هایی که در بالا می‌بینید، می‌شود استفاده کرد و المان‌هایی غیر از آن‌ها مثل Probing یا publisher policy در اینجا قابل استفاده نیست. خطوط بالا به CLR میگویند، هر موقع به نسخه‌ی یک این اسمبلی ارجاعی را داشتی، از نسخه‌ی 2 آن استفاده کن. برای ساخت فایل بالا می‌توانید از طریق دستور زیر نیز اقدام کنید:
AL.exe /out:Policy.1.0.SomeClassLibrary.dll
          /version:1.0.0.0          
/keyfile:MyCompany.snk         
/linkresource:SomeClassLibrary.config
تشریح دستور بالا به شکل زیر است:

سوئییچ out باعث ساخت فایل PE با نام مشخص شده می‌باشد که تنها شامل یک manifest می‌شود و شامل 5 بخش می‌شود:
  بخش اول شامل عبارت policy است تا بگویید که این اسمبلی شامل یک فایل اطلاعاتی درباره سیاست‌های تعیین شده است.  بخش دوم و سوم مربوط به ورژن اسمبلی است که به ترتیب major و minor آن مشخص می‌شوند و این سیاست‌ها فقط روی این نسخه‌ها اتفاق می‌افتد. ادامه شماره نسخه‌ها چون revision number و ... اهمیتی ندارد و فقط همان دو مقدار اول شرط اصلی هستند. بخش چهارم هم شامل نام اسمبلی مربوطه می‌شود و بخش پنجم هم پسوند اسمبلی است.
سوئیچ version تنها مشخص کننده ورژن فایل پیکربندی است و هیچ ارتباطی با ورژن خود اسمبلی ندارد. این ورژن به این دلیل است که مثلا امروز ناشر دوست دارد که CLR به جای اسمبلی 1.0.0، نسخه‌ی 2.0.0 آن مورد استفاده قرار بگیرد. ولی در آینده تصمیم می‌گیرد از نسخه‌ی 1.0.0 به نسخه‌ی 2.5.0 هدایت شود و در اینجاست که بالاترین نسخه‌ی فایل پیکربندی مورد استفاده قرار می‌گیرد.

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

سوئیچ linkresource هم میگوید که فایل پیکربندی قرار است جدای از اسمبلی باشد و ناشر باید این فایل را به همراه نسخه‌ی جدید اسمبلی ارسال و توزیع کند. شما نمی‌توانید با استفاده از سوئیچ EmbedResource فایل پیکربندی را داخل اسمبلی قرار دهید؛ چون CLR انتظار دارد این فایل جدا باشد. (در مورد این سوئیچ‌ها در قسمت شانزدهم توضیح داده‌ایم)

در صورتی که اسمبلی جدید ناشر، باگ‌های بیشتری به همراه داشته باشد، به طوریکه ادمین میخواهد فعلا این فایل سیاست‌های تعیین شده جدید را موقتا لغو کند، می‌تواند تگ زیر را در فایل پیکربندی اصلی به کار ببرد:
<publisherPolicy apply="no"/>
نحوه‌ی استفاده از این تگ را در قسمت قبلی دیده بودیم.

پایان فصل سوم و بخش اول
مطالب
آشنایی با Virtual Directory
نمیدانم آیا تا به حال برای شما پیش آمده است که بخواهید اطلاعاتتان را در جایی غیر از زیرشاخه‌های wwwroot ذخیره نمایید یا خیر؟
یادم هست برای یکی از مشتریانم یک سرور خریده بودیم که دو پارتیشن داشت و آن موقع به ذهنم خطور کرد که اگه بخواهم مثلا فایل‌های سیستم مدیریتی را داخل یک پارتیشن دیگر قرار بدهم چگونه انجام می‌شود؛ چطوری میتوانم به مکانی غیر از شاخه‌ی wwwroot، عمل mappath را انجام بدم؟ چگونه میتوانم یک لینک مستقیم، به مکانی دیگر داشته باشم؟
جواب تمام این سوالات در امکانی از IIS به اسم virtual Directory بود. در این روش ما یک مکان فیزیکی را به iis معرفی می‌کنیم و به آن یک نام مجازی میدهیم که از آن در برنامه استفاده خواهیم کرد.
در iis، در بخش sites، همه سایت‌های ایجاد شده بر روی IIS لیست می‌شوند. یک context menu بر روی سایت مورد نظر خود باز کنید و گزینه‌ی add virtual directory انتخاب کنید. یک نام مجازی به آن بدهید و مکان مورد نظر را انتخاب کنید و کادر را تایید کنید.

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

Right Click On virtual Directory>Manage Virtual Directory >Advanced Settings

از این پس در IIS دسترسی به پوشه، از طریق ~/media میسر هست؛ ولی بسیاری از ما برای تست، برنامه را از طریق IIS Express اجرا و تست می‌کنیم. پس بهتر هست این گزینه در آنجا هم مورد بررسی قرار بگیرد.

برای دسترسی به کانفیگ iis express عموما مسیرهای زیر معتبر هستند:

%userprofile%\documents\iisexpress\config\applicationhost.config
%userprofile%\my documents\iisexpress\config\applicationhost.config

فایل applicationhost.config  فایلی است که قرار است تغییر بدهیم.

ولی اگر مسیرهای بالا راهگشا نبود، برای پیدا کردن فایل‌های کانفیگ می‌توانید از طریق آیکن IIS Express که حین اجرای پروژه در notification area باز می‌شود نیز اقدام کرد.

یک context menu از طریق این آیکن باز کرده و گزینه‌ی show all applications را انتخاب کنید. لیست تمامی سایت‌های در حال اجرا نمایش داده می‌شود. یکی را انتخاب کنید تا در زیر اطلاعات نمایش یابد. در قسمت کانفیگ، آدرس فایل کانفیگ داده شده است و مسیر نیز مشخص است. با کلیک بر روی آن فایل applicationhost.config باز می‌شود.

فایل مورد نظر را باز کنید. این فایل را می‌توان با نوت پد یا یک xml editor گشود. در آن یک تگ sites وجود دارد که تمامی پروژه‌های تحت وبی را که تا الان دارید، درون خودش ذخیره کرده‌است. به ازای هر سایت یک تگ site هست و خصوصیات هر کدام، داخل این تگ قرار گرفته‌است. اگر دقت کنید هر پروژه شما یا همان تگ site، شامل تگ زیر می‌باشد:

<application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>

در اینجا خود IIS Express اقدام به ساخت یک دایرکتوری مجازی که همان مسیر ذخیره پروژه هست کرده. برای معرفی دایرکتوری مجازی جدید، یک کپی از تگ application را ایجاد کنید.

برای مثال من قصد دارم یک دایرکتوری مجازی به اسم media بسازم تا تصاویر و دیگر فایل‌های چندرسانه ای را در آن ذخیره کنم و محل فیزیکی آن نیز D:\testvd می‌باشد. پس تگ کپی شده را به نحو زیر تغییر می‌دهم:

 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>

بنابراین در کل، تگ site من به شکل زیر تغییر پیدا می‌کند:

<site name="Oscar_Manager" id="23">
                <application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>
 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>
                <bindings>
                    <binding protocol="http" bindingInformation="*:1844:localhost" />
                </bindings>
            </site>

الان مسیر مجازی ما ساخته شده است. برای تست صحت این کار، یک فایل تصویری را در آن قرار می‌دهم و در کنترلر مورد نظر، این کد را می‌نویسم تا یک تصویر به سمت کلاینت در یک virtual directory ارسال شود.

var dir = Server.MapPath("~/media");
            var path = System.IO.Path.Combine(dir, "1.jpg");
            return base.File(path, "image/jpeg");

کنترلر را صدا زده تا نتیجه کار را ببنید.

همانطور که حتما متوجه شدید IIS Express محیط GUI ندارد. البته مدتی است افزونه‌ای برای این کار محیا شده ولی بیشتر کاربرد آن ایجاد یک سایت جدید و اجرا و توقف IIS می‌باشد که می‌توانید آن را از اینجا  دریافت نمایید.

نکته:البته بنده این نکته را تایید نمی‌کنم ولی شنیده‌ام که در نسخه‌های بالاتر ویژوال استادیو با راست کلیک بر روی نام پروژه، گزینه Use IIS Express وجود دارد که به یک محیط گرافیکی ختم می‌شود و از آنجا که بنده نسخه 2012 را دارم این مورد را تست نکردم.


ایجاد virtual Directory از طریق Appcmd

دسترسی به appcmd از طریق مسیر زیر امکان پذیر است:

%systemroot%\system32\inetsrv\AppCmd.exe

این دستور به تمامی اشیاء سرور، از قبیل سایت‌ها و اپلیکیشن‌ها دسترسی دارد و میتواند هر نوع متدی را بر روی اشیاء سرور، از قبیل ثبت، ویرایش و حذف را انجام دهد.

یکی از این دستورات، برای ساخت Virtual Directory استفاده می‌شود:

APPCMD add vdir /app.name:"default we site/" /path:/vdir1 /physicalPath:d:\testvd

سوییچ /app برای نام وب سایت یا پروژه است و حتما در انتهای نام، علامت / قرار گیرد. سوییچ بعدی هم که /path هست برای دادن مسیر مجازی و سوییچ آخری هم آدرس محل فزیکی است. بعد از اجرای دستور، پیام زیر نمایش داده می‌شود:

VDIR object "Default Web Site/vdir1" added

پنجره commandprompt باید به صورت Run a Administrator باز شده باشد.

برای تغییر محل فیزیکی یک virtual directory میتوان از دستور زیر استفاده کرد:

appcmd.exe set vdir "Default Web Site/vdir1/" -physicalPath:"D:\Files"

از این پس مسیر فیزیکی این آدرس مجازی مسیر D:\Files خواهد بود.

البته مباحث پیشرفته‌تری چون application pool‌ها نیز وجود دارد که از حوصله این مقاله خارج هست و در وقت و مقاله دیگر بررسی خواهیم کرد.

برای ارسال دستور به IIS Express هم از طریق مسیر زیر appcmd را باز کنید:

%ProgramFiles%\IIS Express\appcmd.exe

امکان ایجاد virtual directory عموما در سرورهای مجازی و اختصاصی در پنل مربوطه نیز وجود دارد.

ساخت virtual Directory در وب سایت پنل 

ساحت virtual Directory در پلسک 

بازخوردهای دوره
ارتباطات بلادرنگ و SignalR
در اینجا
- در سمت کلاینت فایروال مانعی نخواهد بود چون ارتباطات از طریق مرورگر (هم می‌تواند) انجام می‌شود.
- باز هم نهایتا از سوکت‌ها استفاده خواهد شد اما در سطحی بالاتر و بدون درگیری با جزئیات آن‌ها. اینبار یک فریم ورک آماده، تست شده و تهیه شده برفراز سوکت‌های دات نت و ویندوز در اختیار شما است. به علاوه این فریم ورک فراتر است از صرفا برقراری ارتباط و ارسال داده، بلکه حالت امکان اجرای متدهای خاصی در سمت کلاینت یا سرور را هم دارا است (بحث قسمت بعد).
- تنوع کلاینت‌ها. محدود به یک برنامه ویندوزی نخواهید بود. مثلا امکان استفاده از یک کلاینت jQuery، که برای اجرا، نیازی به سطح دسترسی خاصی ندارد، یا حتی یک کلاینت سیلورلایت یا اندروید و غیره هم برای آن تهیه کرده‌اند.
- امکان استفاده از IIS به عنوان سرور. همین مساله یعنی درگیر نشدن با مسایلی مانند مقیاس پذیری، مدیریت تعداد کانکشن‌های بالا و امثال آن.
- امکان یکپارچه کردن یک برنامه سرویس دهنده هاب با یک برنامه ASP.NET در کنار هم در یک پروژه.
و ...