مطالب
استفاده از Kendo UI templates
در مطلب «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid» در انتهای بحث، ستون IsAvailable به صورت زیر تعریف شد:
columns: [
               {
                   field: "IsAvailable", title: "موجود است",
                   template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>'
                }
]
Templates، جزو یکی از پایه‌های Kendo UI Framework هستند و توسط آن‌ها می‌توان قطعات با استفاده‌ی مجدد HTML ایی را طراحی کرد که قابلیت یکی شدن با اطلاعات جاوا اسکریپتی را دارند.
همانطور که در این مثال نیز مشاهده می‌کنید، قالب‌های Kendo UI از Hash (#) syntax استفاده می‌کنند. در اینجا قسمت‌هایی از قالب که با علامت # محصور می‌شوند، در حین اجرا، با اطلاعات فراهم شده جایگزین خواهند شد.
برای رندر مقادیر ساده می‌توان از # =# استفاده کرد. از # :# برای رندر اطلاعات HTML-encoded کمک گرفته می‌شود و #  # برای رندر کدهای جاوا اسکریپتی کاربرد دارد. از حالت HTML-encoded برای نمایش امن اطلاعات دریافتی از کاربران و جلوگیری از حملات XSS استفاده می‌شود.
اگر در این بین نیاز است # به صورت معمولی رندر شود، در حالت کدهای جاوا اسکریپتی به صورت #\\ و در HTML ساده به صورت #\ باید مشخص گردد.


مثالی از نحوه‌ی تعریف یک قالب Kendo UI

    <!--دریافت اطلاعات از منبع محلی-->
    <script id="javascriptTemplate" type="text/x-kendo-template">
        <ul>
            # for (var i = 0; i < data.length; i++) { #
            <li>#= data[i] #</li>
            # } #
        </ul>
    </script>

    <div id="container1"></div>
    <script type="text/javascript">
        $(function () {
            var data = ['User 1', 'User 2', 'User 3'];
            var template = kendo.template($("#javascriptTemplate").html());
            var result = template(data); //Execute the template
            $("#container1").html(result); //Append the result
        });
    </script>
این قالب ابتدا در تگ script محصور می‌شود و سپس نوع آن مساوی text/x-kendo-template قرار می‌گیرد. در ادامه توسط یک حلقه‌ی جاوا اسکریپتی، عناصر آرایه‌ی فرضی data خوانده شده و با کمک Hash syntax در محل‌های مشخص شده قرار می‌گیرند.
در ادامه باید این قالب را رندر کرد. برای این منظور یک div با id مساوی container1 را جهت تعیین محل رندر نهایی اطلاعات مشخص می‌کنیم. سپس متد kendo.template بر اساس id قالب اسکریپتی تعریف شده، یک شیء قالب را تهیه کرده و سپس با ارسال آرایه‌ای به آن، سبب اجرای آن می‌شود. خروجی نهایی، یک قطعه کد HTML است که در محل container1 درج خواهد شد.
همانطور که ملاحظه می‌کنید، متد kendo.template، نهایتا یک رشته را دریافت می‌کند. بنابراین همینجا و به صورت inline نیز می‌توان یک قالب را تعریف کرد.


کار با منابع داده راه دور

فرض کنید مدل برنامه به صورت ذیل تعریف شده‌است:
namespace KendoUI04.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}
و لیستی از آن توسط یک ASP.NET Web API کنترلر، به سمت کاربر ارسال می‌شود:
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using KendoUI04.Models;

namespace KendoUI04.Controllers
{
    public class ProductsController : ApiController
    {
        public IEnumerable<Product> Get()
        {
            return ProductDataSource.LatestProducts.Take(10);
        }
    }
}
در سمت کاربر و در View برنامه خواهیم داشت:
    <!--دریافت اطلاعات از سرور-->
    <div>
        <div id="container2"><ul></ul></div>
    </div>

    <script id="template1" type="text/x-kendo-template">
        <li> #=Id# - #:Name# - #=kendo.toString(Price, "c")#</li>
    </script>

    <script type="text/javascript">
        $(function () {
            var producatsTemplate1 = kendo.template($("#template1").html());

            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                change: function () {
                    $("#container2 > ul").html(kendo.render(producatsTemplate1, this.view()));
                }
            });
            productsDataSource.read();
        });
    </script>
ابتدا یک div با id مساوی container2 جهت تعیین محل نهایی رندر قالب template1 در صفحه تعریف می‌شود.
هرچند خروجی دریافتی از سرور نهایتا یک آرایه از اشیاء Product است، اما در template1 اثری از حلقه‌ی جاوا اسکریپتی مشاهده نمی‌شود. در اینجا چون از متد kendo.render استفاده می‌شود، نیازی به ذکر حلقه نیست و به صورت خودکار، به تعداد عناصر آرایه دریافتی از سرور، قطعه HTML قالب را تکرار می‌کند.
در ادامه برای کار با سرور از یک Kendo UI DataSource استفاده شده‌است. قسمت transport/read آن، کار تعریف محل دریافت اطلاعات را از سرور مشخص می‌کند. رویدادگران change آن اطلاعات نهایی دریافتی را توسط متد view در اختیار متد kendo.render قرار می‌دهد. در نهایت، قطعه‌ی HTML رندر شده‌ی نهایی حاصل از اجرای قالب، در بین تگ‌های ul مربوط به container2 درج خواهد شد.
رویدادگران change زمانیکه data source، از اطلاعات راه دور و یا یک آرایه‌ی جاوا اسکریپتی پر می‌شود، فراخوانی خواهد شد. همچنین مباحث مرتب سازی اطلاعات، صفحه بندی و تغییر صفحه، افزودن، ویرایش و یا حذف اطلاعات نیز سبب فراخوانی آن می‌گردند. متد view ایی که در این مثال فراخوانی شد، صرفا در روال رویدادگردان change دارای اعتبار است و آخرین تغییرات اطلاعات و آیتم‌های موجود در data source را باز می‌گرداند.


یک نکته‌ی تکمیلی: فعال سازی intellisense کدهای جاوا اسکریپتی Kendo UI

اگر به پوشه‌ی اصلی مجموعه‌ی Kendo UI مراجعه کنید، یکی از آن‌ها vsdoc نام دارد که داخل آن فایل‌های min.intellisense.js و vsdoc.js مشهود هستند.
اگر از ویژوال استودیوهای قبل از 2012 استفاده می‌کنید، نیاز است فایل‌های vsdoc.js متناظری را به پروژه اضافه نمائید؛ دقیقا در کنار فایل‌های اصلی js موجود. اگر از ویژوال استودیوی 2012 و یا بالاتر استفاده می‌کنید باید از فایل‌های intellisense.js متناظر استفاده کنید. برای مثال اگر از kendo.all.min.js کمک می‌گیرید، فایل متناظر با آن kendo.all.min.intellisense.js خواهد بود.
بعد از اینکار نیاز است فایلی به نام references.js_ را به پوشه‌ی اسکریپت‌های خود با این محتوا اضافه کنید (برای VS 2012 به بعد):
/// <reference path="jquery.min.js" />
/// <reference path="kendo.all.min.js" />
نکته‌ی مهم اینجا است که این فایل به صورت پیش فرض از مسیر Scripts/_references.js/~ خوانده می‌شود. برای اضافه کردن مسیر دیگری مانند js/_references.js/~ باید آن‌را به تنظیمات ذیل اضافه کنید:
 Tools menu –> Options -> Text Editor –> JavaScript –> Intellisense –> References
گزینه‌ی Reference Group را به (Implicit (Web تغییر داده و سپس مسیر جدیدی را اضافه نمائید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
KendoUI04.zip
مطالب دوره‌ها
مثال‌هایی بیشتر از عملکرد پشته‌ای MSIL
بررسی عملکرد و کدهای IL یک متد

        ldarg.0
        stloc_0
L_0000:
        ldloc_0
        ldc_i4 5
        add
        stloc_0
        ldloc_0
        ldc_i4 15
        blt L_0000
        ldloc_0
        ret
به کدهای IL فوق دقت کنید. در ادامه قصد داریم عملکرد این متد را بررسی کرده و سپس سعی کنیم تا معادل سی شارپ آن‌را حدس بزنیم. (البته سعی کنید طوری مطلب را مطالعه کنید که ادامه بحث را در ابتدا مشاهده نکنید!)
این متد، یک مقدار int را دریافت کرده و با انجام محاسباتی بر روی آن، مقدار int دیگری را بازگشت می‌دهد.
کار با ldarg.0 شروع می‌شود. به این ترتیب آرگومان موجود در ایندکس صفر، بر روی پشته بارگذاری خواهد شد. فرض کنید ورودی 5 را به این متد ارسال کرده‌ایم.
سپس stloc_0 این مقدار را از پشته pop کرده و در یک متغیر محلی ذخیره می‌کند.
در ادامه برچسب L_0000 تعریف شده است. از برچسب‌ها برای انتقال جریان اجرایی برنامه استفاده می‌کنیم.
ldloc_0 به معنای بارگذاری متغیر محلی از ایندکس صفر است. به این ترتیب عدد 5 بر روی پشته ارزیابی قرار می‌گیرد.
توسط ldc_i4 5، یک i4 یا int 32 بیتی یا int ایی با 4 بایت، به عنوان یک عدد ثابت بارگذاری می‌شود. این عدد نیز بر روی پشته ارزیابی قرار می‌گیرد.
در ادامه با فراخوانی Add، دو مقدار قرار گرفته بر روی پشته pop شده و نتیجه 10؛ مجددا بر روی پشته قرار می‌گیرد.
stloc_0 سبب می‌شود تا این عدد 10 در یک متغیر محلی در ایندکس صفر ذخیره شود.
با فراخوانی ldloc_0، این متغیر محلی به پشته ارزیابی منتقل می‌شود.
به کمک ldc_i4 15، یک عدد صحیح 4 بایتی با مقدار ثابت 15 بارگذاری می‌شود.
در ادامه blt بررسی می‌کند که اگر 10 کوچکتر است از 15 ایی که بر روی پشته قرار گرفته، آنگاه جریان عملیات را به برچسب L_0000 منتقل می‌کند (پرش به برچسب صورت خواهد گرفت).
اگر با سایر زبان‌های اسمبلی کار کرده باشید با lt، gt و امثال آن به طور قطع آشنایی دارید. در اینجا blt به معنای branch less than equal است.
بنابراین در ادامه مجددا همین اعمال فوق تکرار خواهند شد تا به ارزیابی blt جهت دو مقدار 15 با 15 برسیم. از آنجائیکه اینبار 15 کوچکتر از 15 نیست، سطر پس از آن یعنی ldloc_0 اجرا می‌شود که معادل است با بارگذاری 15 به پشته ارزیابی و سپس return فراخوانی می‌شود تا این مقدار را بازگشت دهد.



خوب؛ آیا می‌توانید کدهای معادل سی‌شارپ آن‌را حدس بزنید؟!

        public static int Calculate(int x)
        {
            for (; x < 15; x += 5)
            {
            }
            return x;
        }
بله. متد محاسباتی که در ابتدای بحث کدهای IL آن‌را ملاحظه نمودید، یک چنین معادل سی‌شارپی دارد.


فراخوانی متدها در MSIL

برای فراخوانی متدها در کدهای IL از OpCode ایی به نام call استفاده می‌شود:
 ldstr "hello world"
call void [mscorlib]System.Console::WriteLine(string)
در این مثال توسط ldstr، یک رشته بارگذاری شده و سپس توسط call اطلاعات متدی که باید فراخوانی شود، ذکر می‌گردد. همانطور که ملاحظه می‌کنید، امضای کامل متد نیاز است ذکر گردد؛ متدی از نوع void قرار گرفته در mscorlib با ذکر فضای نام و نام متد مورد نظر.


بررسی یک الگوریتم بازگشتی در MSIL

ابتدا متد بازگشتی ذیل را درنظر بگیرید:
        public static int Calculate(int n)
        {
            if (n <= 1) return n;
            return n * Calculate(n - 1);
        }
اگر کد دی‌کامپایل شده‌ی آن را در ILSpy بررسی کنیم، به دستورات ذیل خواهیم رسید:
.method public hidebysig static 
int32 Calculate (
int32 n
) cil managed 
{
// Method begins at RVA 0x3c0d
// Code size 17 (0x11)
.maxstack 8

IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: bgt.s IL_0006

IL_0004: ldarg.0
IL_0005: ret

IL_0006: ldarg.0
IL_0007: ldarg.0
IL_0008: ldc.i4.1
IL_0009: sub
IL_000a: call int32 FastReflectionTests.Test::Calculate(int32)
IL_000f: mul
IL_0010: ret
} // end of method Test::Calculate
در اینجا ابتدا مقدار آرگومان متد، توسط ldarg بارگذاری می‌شود. سپس مقدار ثابت یک بارگذاری می‌شود. توسط bgt بررسی خواهد شد اگر مقدار آرگومان (عدد اول) بزرگتر است از مقدار عدد ثابت یک (عدد دوم)، آنگاه به برچسب IL_0006 پرش صورت گیرد و قسمت ضرب بازگشتی انجام شود. در غیراینصورت آرگومان متد بارگذاری شده و در سطر IL_0005 کار خاتمه می‌یابد.
در سطرهای IL_0006 و IL_0007، دوبار کار بارگذاری آرگومان متد انجام شده است؛ یکبار برای عملیات ضرب و بار دیگر برای عملیات تفریق از یک.
سپس عدد ثابت یک بارگذاری شده و از مقدار آرگومان، توسط sub کسر خواهد شد. در ادامه متد Calculate به صورت بازگشتی فراخونی می‌گردد. در این حالت دوباره به سطر IL_0000 هدایت خواهیم شد و عملیات ادامه می‌یابد.
مطالب
کنترل نوع‌های داده با استفاده از EF در SQL Server
ورود سیستم‌های ORM مانند EF تحولی عظیم در در مباحث کار و تغییرات بر روی داده‌ها یا Data Manipulation بود. به طور خلاصه اصلی‌ترین هدف یک ORM، ایجاد فرامین شیء گرا به جای فرامین رابطه‌ای است؛ ولی در این بین نکات دیگری هم مد نظر گرفته شده‌است که یکی از آن‌ها پشتیبانی از چندین دیتابیس هست تا توسعه گران از یک سیستم واحد جهت اتصال به همه‌ی دیتابیس‌ها استفاده کنند و نیازی به دانش اضافه و سیستم جداگانه‌ای برای هر دیتابیس نباشد؛ مانند ADO که در دات نت به چندین دسته نقسیم شده بود و هم اینکه در صورتی که تمایلی به تغییر دیتابیس در آینده داشتید، کدها برای توسعه باز باشند و نیازی به تغییر سیستم نباشد.
ولی اگر کمی بیشتر به دنیای واقعی وارد شویم گاهی اوقات نیاز است که تنها بر روی یک دیتابیس فعالیت کنیم و یک دیتابیس نیاز است تا حد ممکن بهینه طراحی شود تا کارآیی بانک در حال حاضر و به خصوص در آینده تا حدی تضمین شود.
من همیشه در مورد EF یک نظری داشتم و آن اینست که با اینکه یک ORM، یک هدف مهم را در نظر دارد و آن اینست که تا حد ممکن استانداردهایی را که بین تمامی دیتابیس‌ها مشترک است، رعایت کند، ولی باز قابل قبول است اگر بگوییم که کاربران EF انتظار داشته باشند تا اطلاعات بیشتری در مورد sql server در آن نهفته باشد. از یک سو هر دو محصول مایکروسافت هستند و از سوی دیگر مطمئنا توسعه گران محصولات دات نت بیش از هر چیزی به sql server نگاه ویژه‌تری دارند. پس مایکروسافت در کنار حفظ آن ویژگی‌های مشترک، باید به حفظ استانداردهای جدایی برای sql server هم باشد.

تعدادی از برنامه نویسان در هنگام ایجاد Domain Model کم لطفی‌های زیادی را می‌کنند که یکی از آن‌ها عدم کنترل نوع داده‌های خود است. مثلا برای رشته‌ها هیچ محدودیتی را در نظر نمی‌گیرند. شاید در سمت کلاینت اینکار را انجام می‌دهند؛ ولی نکته‌ی مهم در طرف دیتابیس است که چگونه تعریف می‌شود. در این حالت (nvarchar(MAX در نظر گرفته میشود که به معنی اشاره به منطقه دوگیگابایتی از اطلاعات است. در نکات بعدی، قصد داریم این مرحله را یک گام به جلوتر پیش ببریم و آن هم ایجاد نوع داده‌های بهینه‌تر در Sql Server است.

نکته مهم: بدیهی است که تغییرات زیر، ORM شما را تنها به sql server مقید می‌کند که بعدها در صورت تغییر دیتابیس نیاز به حذف موارد زیر را خواهید داشت؛ در غیر اینصورت به مشکل عدم ایجاد دیتابیس برخواهید خورد.

اولین مورد مهم بحث تاریخ و زمان است؛ وقتی ما یک نوع داده را تنها DateTime در نظر بگیریم، در Sql Server هم همین نوع داده وجود دارد و انتخاب میشود. ولی اگر شما واقعا نیازی به این نوع داده نداشته باشید چطور؟ در حال حاضر من بر روی یک برنامه‌ی کارخانه کار میکنم که بخش کارمندان و گارگران آن سه داده زمانی زیر را شامل می‌شود:
        public DateTime BirthDate { get; set; }
        public DateTime HireDate { get; set; }
        public DateTime? LeaveDate { get; set; }

حال به جدول زیر نگاه کنید که هر نوع داده چه مقدار فضا را به خود اختصاص می‌دهد:
  SmallDateTime   4 بایت
  DateTime  8 بایت
  DateTime2  6 تا 8 بایت
  DateTimeOffset   8 تا 10 بایت
  Date  3 بایت
  Time  3 تا 5 بایت

از این جدول چه می‌فهمید؟ با یک نگاه می‌توان فهمید که ساختار بالای من باید 24 بایت گرفته باشد؛ برای ساختاری که هم تاریخ و هم زمان (ساعت) را پشتیبانی می‌کند. ولی با نگاه دقیق‌تر به نام پراپرتی‌ها این نکته روشن می‌شود که ما یک گپ Gap (فضای بیهوده) داریم چون زمان تولد، استخدام و ترک سازمان اصلا نیازی به ساعت ندارند و همان تاریخ کافی است. یعنی نوع Date با حجم کلی 9 بایت؛ که در نتیجه 15 بایت صرفه جویی در یک رکورد صورت خواهد گرفت.

پس کد بالا را به شکل زیر تغییر می‌دهم:

          [Column(TypeName = "date")]
        public DateTime BirthDate { get; set; }

        [Column(TypeName = "date")]
        public DateTime HireDate { get; set; }

        [Column(TypeName = "date")]
        public DateTime? LeaveDate { get; set; }
خصوصیت Column از نسخه 4.5دات نت فریم ورک اضافه شده و در فضای نام System.ComponentModel.DataAnnotations.Schema قرار گرفته است.
نوع‌هایی که در بالا با سایز متغیر هستند، به نسبت دقتی که برای آن تعیین می‌کنید، سایز می‌گیرند. مثل (time(0 که 3 بایت از حافظه را می‌گیرد. در صورتی که time معرفی کنید، به جای اینکه از شیء DateTime استفاده کنید، از شی Timespan استفاده کنید، تا در پشت صحنه از نوع داده time استفاده کند. در این حالت حداکثر حافظه یعنی 5 بایت را برخواهد داشت و بهترین حالت ممکن این هست که نیاز خود را بسنجید و خودتان دقت آن را مشخص کنید. دو شکل زیر نحوه‌ی تعریف نوع زمان را مشخص می‌کنند. یکی حالت پیش فرض و دیگری انتخاب دقت:
     public class Testtypes
    {
           public TimeSpan CloseTime { get; set; }
            public TimeSpan CloseTime2 { get; set; }
    }
   public class TestConfig : EntityTypeConfiguration<Testtypes>
    {
        public TestConfig()
        {
            this.Property(x => x.CloseTime2).HasPrecision(3);
        }
    }
در تکه کد بالا همه از نوع time تعریف می‌شوند ولی در خصوصیت شماره یک نهایت استفاده از نوع تایم یعنی (time(7 مشخص می‌شود. ولی در خصوصیت بعدی چون در کانفیگ دقت آن را مشخص کرده‌ایم از نوع (time(3 تعریف می‌شود.

مورد دوم در مورد داده‌های اعشاری است:
بسیاری از برنامه نویسان تا آنجا که دیده‌ام از نوع float و single و double برای اعداد اعشاری استفاده می‌کنند ولی باید دید که در آن سمت دیتابیس، برای این نوع داده‌ها چه اتفاقی می‌افتد. نوع float در دات نت، با نوع single برابری میکند؛ هر دو یک نام جدا دارند، ولی در واقع یکی هستند. عموما برنامه نویسان به طور کلی بیشتر از همان single استفاده می‌کنند و برای انتساب برای این دو نوع هم حتما باید حرف f را بعد از عدد نوشت:
float flExample=23.2f;
باید توجه کنید که اگر مثلا float انتخاب کردید، تصور نکنید که همان float در دیتابیس خواهد بود. این دو متفاوت هستند تبدیلات به شکل زیر رخ می‌دهد:
//real
public float FloatData { get; set; }
//real
public Single SingleData { get; set; }
//float
public double DoubleData { get; set; }
  همه نوع‌های بالا اعداد اعشاری هستند که به صورت تقریبی و به صورت نماد اعشاری ذخیره می‌گردند و برای به دست آوردن مقدار ذخیره شده، هیچ تضمینی نیست همان عددی که وارد شده است بازگردانده شود. اگر تا به حال در برنامه هایتان به چنین مشکلی برنخوردید دلیلش اعداد اعشاری کوچک بوده است. ولی با بزرگتر شدن عدد، این تفاوت به خوبی دیده می‌شود. 
حالا اگر بخواهیم اعداد اعشاری را به طور دقیق ذخیره کنیم، مجبور به استفاده از نوع decimal هستیم. در دات نت آنچنان محدودیتی بر سر استفاده‌ی از آن نداریم. ولی در سمت سرور داده‌ها بهتر هست برای آن تدابیری اندیشیده شود. هر عدد دسیمال از دقت و مقیاس تشکیل می‌شود. دقت آن تعداد ارقامی است که در عدد وجود دارد و مقیاس آن تعداد ارقام اعشاری است. به عنوان مثال عدد زیر دقتش 7 و مقیاسش 3 است:
4235.254
در صورتی که عدد اعشاری را به دسیمال نسبت دهیم باید حرف m را بعد از عدد وارد کنیم:
decimal d1=4545.112m;
برای اعداد صحیح نیازی نیست.
برای تعیین نوع دسیمال از  fluent api استفاده می‌کنیم:
modelBuilder.Entity<Class>().Property(object => object.property).HasPrecision(7, 3);
کد زیر برای خصوصیت شماره یک، دقت 18 و مقیاس 2 را در نظر می‌گیرد و دومین خصوصیت طبق آنچه که برایش تعریف کرده ایم دقت 7 و مقیاس 3 است:
     public class Testtypes
    {
        public Decimal Decimal1 { get; set; }
        public Decimal Decimal2 { get; set; }

     }

 public class TestConfig : EntityTypeConfiguration<Testtypes>
    {
        public TestConfig()
        {
            this.Property(x => x.Decimal2).HasPrecision(7, 3);
        }
    }

مورد سوم مبحث رشته هاست
:
کدهای زیر را مطالعه فرمایید:
[StringLength(25)]
public string FirstName { get; set; }

[StringLength(30)]
[Column(TypeName = "varchar")]
public string EnProductTitle { get; set; }


public string ArticleContent { get; set; }
[Column(TypeName = "varchar(max)")]
public string ArticleContentEn { get; set; }
اولین رشته بالا (نام) را به محدوده‌ای از کاراکترها محدود کرده‌ایم. به طور پیش فرض تمامی رشته‌ها به صورت nvarchar در نظر گرفته می‌شوند. بدین ترتیب در رشته نام کوچک (nvarchar(25 در نظر گرفته خواهد شد. حال اگر بخواهیم فقط حروف انگلیسی پشتیبانی شوند، مثلا نام فنی کالا را بخواهید وارد کنید، بهتر هست که نوع آن به طرز صحیحی تعریف شود که در کد بالا با استفاده از خصوصیت Column نوع varchar را معرفی می‌کنم. بدین ترتیب تعریف نهایی نوع به شکل (varchar(30 خواهد بود. استفاده از fluentApi‌ها هم در این رابطه به شکل زیر است:
this.Property(e => e.EnProductTitle).HasColumnType("VARCHAR").HasMaxLength(30);
برای مواردی که محدوده‌ای تعریف نشود (nvarchar(MAX در نظر گرفته میشود مانند پراپرتی ArticleContent بالا. ولی اگر قصد دارید فقط حروف اسکی پشتیبانی گردند، مثلا متن انگلیسی مقاله را نیز نگه می‌دارید بهتر هست که نوع آن بهیته‌ترین حالت در نظر گرفته شود که برای پراپرتی ArticleContentEn نوع (varchar(MAX تعریف کرده‌ایم. 
همانطور که گفتیم پیش فرض رشته‌ها nvarchar است، در صورتی که دوست دارید این پیش فرض را تغییر دهید روش زیر را دنبال کنید:
modelBuilder.Properties<string>().Configure(c => c.HasColumnType("varchar"));


//=========== یا

modelBuilder.Properties<string>().Configure(c => c.IsUnicode(false));


جهت تکمیل بحث نیز هر کدام از متغیرهای عددی در سی شارپ معادل نوع‌های زیر در Sql Server هستند:
//tinyInt
public byte Age { get; set; }

//smallInt
public Int16 OldInt { get; set; }

//int
public int Int32 { get; set; }

//Bigint
public Int64 HighNumbers { get; set; }

مطالب
سازگار کردن لینک‌های قدیمی یک سایت با ساختار جدید آن در ASP.NET MVC
اگر پیشتر سایتی را در آدرس مشخصی در اینترنت داشته‌اید و اکنون تنها نرم افزار آن تغییر کرده است، اما نحوه ارائه خدمات آن خیر، لازم است بتوانید شرایط ذیل را مدیریت کنید:
- موتورهای جستجو مدام اطلاعات قبلی خود را به روز می‌کنند. اگر آدرس قبلی مقاله‌ای در سایت شما http://site/year/month/day/title بوده، برای نمونه گوگل هر از چندگاهی مجددا به این آدرس مراجعه می‌کند تا حداقل مطمئن شود وجود خارجی دارد یا خیر (این نکته را از لاگ‌های خطای سایت استخراج کردم).
- سایت‌های زیادی هستند که پیشتر به سایت شما و مطالب آن لینک داده‌اند. نمی‌توانید از آن‌ها درخواست کنید لطفا بانک اطلاعاتی خود را به روز کنید.
- اگر فید قبلی سایت شما http://site/feeds/posts بوده و اکنون چیز دیگری است، باز هم نمی‌توانید از همه درخواست کنید اطلاعات خود را به روز کنند. عده‌ای اینکار را خواهند کرد و تعداد زیادی هم خیر.

برای مدیریت یک چنین مواردی می‌توان از امکانات مسیریابی موجود در ASP.NET MVC استفاده کرد؛ که نمونه‌ای عملی از آن‌را جهت سازگاری سایت جاری با هاست قبلی آن (بلاگر) در ادامه مطالعه خواهید نمود:

الف) سازگار سازی لینک‌های قدیمی برچسب‌های سایت با ساختار جدید آن
در بلاگر آدرس‌های برچسب‌ها، به صورت http://site/search/label/name تعریف شده است. در سایت جاری برچسب‌ها توسط کنترلر Tag مدیریت می‌شوند. برای هدایت آدرس‌های قدیمی (موجود در موتورهای جستجو یا ثبت شده در سایت‌هایی که به ما لینک داده‌اند) می‌توان از تعریف مسیریابی ذیل در فایل global.asax استفاده کرد:
routes.MapRoute(
                "old_bloger_tags_list", // Route name
                "search/label/{name}", // URL with parameters
                new { controller = "Tag", action = "Index", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );
به این ترتیب به صورت خودکار تمامی آدرس‌های شروع شده با http://site/search/label پالایش شده و سپس قسمت name آن‌ها جدا سازی می‌شود. این نام به متدی به نام Index در کنترلر Tag که دارای پارامتری به نام name است ارسال خواهد شد.

ب) از دست ندادن خوانندگان قدیمی فیدهای سایت
دو نوع فید کلی در بلاگر وجود دارد: http://site/feeds/posts/default و http://site/feeds/comments/default؛ اما در سایت جاری فیدها توسط کنترلری به نام Feed ارائه می‌شوند. برای سازگار سازی آدرس‌های قدیمی و هدایت آن‌ها به صورت خودکار به کنترلر فید می‌توان از دو تعریف مسیریابی ذیل استفاده کرد:
routes.MapRoute(
                "old_bloger_posts_feeds_list", // Route name
                "feeds/posts/default", // URL with parameters
                new { controller = "Feed", action = "Posts", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );

routes.MapRoute(
                "old_bloger_comments_feeds_list", // Route name
                "feeds/comments/default", // URL with parameters
                new { controller = "Feed", action = "Comments", name = UrlParameter.Optional, area = "" } // Parameter defaults
            );            
در اینجا دو آدرس ذکر شده به کنترلر Feed و متدهای Posts و Comments آن هدایت خواهند شد و به این نحو کاربران قدیمی سایت هیچگونه تغییری را احساس نکرده و باز هم فیدخوان‌های آن‌ها، بدون مشکل کار خواهند کرد.

ج) پردازش لینک‌های قدیمی مطالب سایت و هدایت آن‌ها به آدرس‌های جدید
این مورد اندکی مشکل‌تر از موارد قبلی است:
routes.MapRoute(
                "old_bloger_post_urls",
                "{yyyy}/{mm}/{title}",
                new { controller = "Post", action = "OldBloggerLinks" },
                new { yyyy = @"\d{4}", mm = @"\d{1,2}" }
            );
برای نمونه آدرس مقاله‌ای مانند http://site/2012/05/ef-code-first-15.html را درنظر بگیرید. سه قسمت سال، ماه و عنوان آن، حائز اهمیت هستند. این‌ها را در اینجا به کنترلر Post و متد OldBloggerLinks آن هدایت خواهیم کرد. همچنین برای سال و ماه آن نیز قید تعریف شده است. سال عددی 4 رقمی است و ماه عددی یک تا دو رقمی.
کدهای متد OldBloggerLinks را در اینجا مشاهده می‌کنید:
        public virtual ActionResult OldBloggerLinks(int yyyy, int mm, string title)
        {
            var oldUrl = string.Format(CultureInfo.InvariantCulture, "https://www.dntips.ir/{0}/{1}/{2}", yyyy, mm.ToString("00"), title);
            var blogPost = _blogPostsService.FindBlogPost(oldUrl);
            if (blogPost != null)
                return RedirectToActionPermanent(actionName: ActionNames.Index, controllerName: MVC.Post.Name,
                                                 routeValues: new { id = blogPost.Id, name = blogPost.Title.GetPostSlug() });
            return this.Redirect("/");
        }
در اینجا چون ساختار لینک‌ها کلا تغییر کرده است، ابتدا بر اساس پارامترهای دریافت شده، لینک قدیمی بازسازی می‌شود. سپس به بانک اطلاعاتی مراجعه شده و لینک قدیمی به همراه شماره مطلب مرتبط با آن یافت می‌شود (یک فیلد oldUrl برای مطالب قدیمی در بانک اطلاعاتی وجود دارد). در آخر هم به کمک متد  RedirectToActionPermanent آدرس رسیده به آدرس جدید مطلب در سایت ترجمه و هدایت خواهد شد. Permanent بودن آن برای به روز رسانی خودکار اطلاعات موتورهای جستجو مفید است.


نتیجه گیری
به کمک امکانات مسیریابی توکار ASP.NET MVC می‌توان ساختار قدیمی یک سایت را به ساختار جدید آن ترجمه کرد. به این ترتیب لینک‌های قدیمی ثبت شده در صدها سایت اینترنتی که به سایت ما اشاره می‌کنند، مجددا بدون مشکل قابل استفاده بوده و همچنین موتورهای جستجو نیز امکان به روز رسانی اطلاعات خود را خواهند یافت.

مطالب
استفاده از افزونه‌ی jQuery Autocomplete در ASP.NET

با استفاده از AutoComplete TextBoxes می‌توان گوشه‌ای از زندگی روزمره‌ی کاربران یک برنامه را ساده‌تر کرد. مشکل مهم dropDownList ها دریک برنامه‌ی وب، عدم امکان تایپ قسمتی از متن مورد نظر و سپس نمایان شدن آیتم‌های متناظر با آن در اسرع وقت می‌باشد. همچنین با تعداد بالای آیتم‌ها هم حجم صفحه و زمان بارگذاری را افزایش می‌دهند. راه حل‌های بسیار زیادی برای حل این مشکل وجود دارند و یکی از آن‌ها ایجاد AutoComplete TextBoxes است. پلاگین‌های متعددی هم جهت پیاده سازی این قابلیت نوشته‌ شده‌اند منجمله jQuery Autocomplete . این پلاگین دیگر توسط نویسنده‌ی اصلی آن نگهداری نمی‌شود اما توسط برنامه نویسی دیگر در github ادامه یافته است. در ادامه نحوه‌ی استفاده از این افزونه‌ را در ASP.NET Webforms بررسی خواهیم کرد.

الف) دریافت افزونه

لطفا به آدرس GitHub ذکر شده مراجعه نمائید.

سپس برای مثال پوشه‌ی js را به پروژه افزوده و فایل‌های jquery-1.5.min.js ، jquery.autocomplete.js ، jquery.autocomplete.css و indicator.gif را در آن کپی کنید. فایل indicator.gif به همراه مجموعه‌ی دریافتی ارائه نمی‌شود و یک آیکن loading معروف می‌تواند باشد.
علاوه بر آن یک فایل جدید custom.js را نیز جهت تعاریف سفارشی خودمان اضافه خواهیم کرد.


ب) افزودن تعاریف افزونه به صفحه

در ذیل نحوه‌ی افزودن فایل‌های فوق به یک master page نمایش داده شده است.
در اینجا از قابلیت‌های جدید ScriptManager (موجود در سرویس پک یک دات نت سه و نیم و یا دات نت چهار) جهت یکی کردن اسکریپت‌ها کمک گرفته شده است. به این صورت تعداد رفت و برگشت‌ها به سرور به‌جای سه مورد (تعداد فایل‌های اسکریپت مورد استفاده)، یک مورد (نهایی یکی شده) خواهد بود و همچنین حاصل نهایی به صورت خودکار به شکلی فشرده شده به مرورگر تحویل داده شده، سرآیندهای کش شدن اطلاعات به آن اضافه می‌گردد (که در سایر حالات متداول اینگونه نیست)؛ به علاوه Url نهایی آن هم بر اساس hash فایل‌ها تولید می‌شود. یعنی اگر محتوای یکی از این فایل‌ها تغییر کرد، چون Url نهایی تغییر می‌کند، دیگر لازم نیست نگران کش شدن و به روز نشدن اسکریپت‌ها در سمت کاربر باشیم.

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="AspNetjQueryAutocompleteTest.Site" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:PlaceHolder Runat="server">
<link href="<%= ResolveClientUrl("~/js/jquery.autocomplete.css")%>" rel="stylesheet" type="text/css" />
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="~/js/jquery-1.5.min.js" />
<asp:ScriptReference Path="~/js/jquery.autocomplete.js" />
<asp:ScriptReference Path="~/js/custom.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
علت استفاده از ResolveClientUrl در حین تعریف فایل css در اینجا به عدم مجاز بودن استفاده از ~ جهت مسیر دهی فایل‌های css در header صفحه بر می‌گردد.


ج) افزودن یک صفحه‌ی ساده به برنامه
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="default.aspx.cs" Inherits="AspNetjQueryAutocompleteTest._default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:TextBox ID="txtShenas" runat="server" />
</asp:Content>

فرض کنید می‌خواهیم افزونه‌ی ذکر شده را به TextBox استاندارد فوق اعمال کنیم. ID این TextBox در نهایت به شکل ContentPlaceHolder1_txtShenas رندر خواهد شد. البته در ASP.NET 4.0 با تنظیم ClientIDMode=Static می‌توان ID انتخابی خود را به جای این ID خودکار درنظر گرفت و اعمال کرد. اهمیت این مساله در قسمت (ه) مشخص می‌گردد.


د) فراهم آوردن اطلاعات مورد استفاده توسط افزونه‌ی AutoComplete به صورت پویا

مهم‌ترین قسمت استفاده از این افزونه، تهیه‌ی اطلاعاتی است که باید نمایش دهد. این اطلاعات باید به صورت فایلی که هر سطر آن حاوی یکی از آیتم‌های مورد نظر است، تهیه گردد. برای این منظور می‌توان از فایل‌های ASHX یا همان Generic handlers استفاده کرد:

using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;

namespace AspNetjQueryAutocompleteTest
{
public class AutoComplete : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string prefixText = context.Request.QueryString["q"];
var sb = new StringBuilder();

using (var conn = new SqlConnection())
{
//todo: این مورد باید از فایل کانفیگ خوانده شود
conn.ConnectionString = "Data Source=(local);Initial Catalog=MyDB;Integrated Security = true";
using (var cmd = new SqlCommand())
{
cmd.CommandText = @" select Field1 ,Field2 from tblData where Field1 like @SearchText + '%' ";
cmd.Parameters.AddWithValue("@SearchText", prefixText);
cmd.Connection = conn;
conn.Open();
using (var sdr = cmd.ExecuteReader())
{
if (sdr != null)
while (sdr.Read())
{
string field1 = sdr.GetValue(0) == DBNull.Value ? string.Empty : sdr.GetValue(0).ToString().Trim();
string field2 = sdr.GetValue(1) == DBNull.Value ? string.Empty : sdr.GetValue(1).ToString().Trim();
sb.AppendLine(field1 + "|" + field2);
}
}
}
}

context.Response.Write(sb.ToString());
}

public bool IsReusable
{
get
{
return false;
}
}
}
}

در این مثال از ADO.NET کلاسیک استفاده شده است تا به عمد نحوه‌ی تعریف پارامترها یکبار دیگر مرور گردند. اگر از LINQ to SQL یا Entity framework یا NHibernate و موارد مشابه استفاده می‌کنید، جای نگرانی نیست؛ زیرا کوئری‌های SQL تولیدی توسط این ORMs به صورت پیش فرض از نوع پارامتری هستند (+).
در این مثال اطلاعات دو فیلد یک و دوی فرضی از جدولی با توجه به استفاده از like تعریف شده دریافت می‌گردد. به عبارتی همان متد StartsWith معروف LINQ بکارگرفته شده است.
به صورت خلاصه افزونه، کوئری استرینگ q را به این فایل ashx ارسال می‌کند. سپس کلیه آیتم‌های شروع شده با مقدار دریافتی، از بانک اطلاعاتی دریافت شده و هر کدام قرارگرفته در یک سطر جدید بازگشت داده می‌شوند.
اگر دقت کرده باشید در قسمت sb.AppendLine ، با استفاده از "|" دو مقدار دریافتی از هم جدا شده‌اند. عموما یک مقدار کفایت می‌کند (در 98 درصد موارد) ولی اگر نیاز بود تا توضیحاتی نیز نمایش داده شود از این روش نیز می‌توان استفاده کرد. برای مثال یک مقدار خاص به همراه توضیحات آن به عنوان یک آیتم نمایش داده شده مد نظر است.


ه) اعمال نهایی افزونه به TextBox

در ادامه پیاده سازی فایل custom.js برای استفاده از امکانات فراهم شده در قسمت‌های قبل ارائه گردیده است:

function formatItem(row) {
return row[0] + "<br/><span style='text-align:justify;' dir='rtl'>" + row[1] + "</span>";
}

$(document).ready(function () {
$("#ContentPlaceHolder1_txtShenas").autocomplete('AutoComplete.ashx', {
//Minimum number of characters a user has to type before the autocompleter activates
minChars: 0,
delay: 5,
//Only suggested values are valid
mustMatch: true,
//The number of items in the select box
max: 20,
//Fill the input while still selecting a value
autoFill: false,
//The comparison doesn't looks inside
matchContains: false,
formatItem: formatItem
});
});

پس از این مقدمات، اعمال افزونه‌ی autocomplete به textBox ایی با id مساوی ContentPlaceHolder1_txtShenas ساده است. اطلاعات از فایل AutoComplete.ashx دریافت می‌گردد و تعدادی از خواص پیش فرض این افزونه در اینجا مقدار دهی شده‌اند. لیست کامل آن‌ها را در فایل jquery.autocomplete.js می‌توان مشاهده کرد.
تنها نکته‌ی مهم آن استفاده از پارامتر اختیاری formatItem است. اگر در حین تهیه‌ی AutoComplete.ashx خود تنها یک آیتم را در هر سطر نمایش می‌دهید و از "|" استفاده نکرده‌اید، نیازی به ذکر آن نیست. در این مثال ویژه، فیلد یک در یک سطر و فیلد دو در سطر دوم یک آیتم نمایش داده می‌شوند:



مطالب
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 داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت پنجم - ارسال اطلاعات به سرور
تا اینجا تنظیمات اصلی فرم ثبت اطلاعات کارمندان را انجام دادیم. اکنون نوبت به ارسال این اطلاعات به سمت سرور است. پیشنیاز آن نیز تدارک مواردی است که در مطلب «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» پیشتر بحث شدند. از این مطلب تنها تنظیمات موارد ذیل را نیاز خواهیم داشت و از تکرار آن‌ها در اینجا صرفنظر می‌شود تا هم مطلب کوتاه‌تر شود و هم بتوان بر روی اصل موضوع جاری، تمرکز کرد:
- ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017
- تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI
- تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular
- ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری: این مورد را تاکنون انجام داده‌ایم و تکمیل کرده‌ایم. بنابراین تنها کاری که نیاز است انجام شود، cut و paste محتوای پوشه‌ی angular-template-driven-forms-lab (پروژه‌ی این سری) به ریشه‌ی پروژه‌ی ASP.NET Core است.
- تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot
- روش اول و یا دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

البته سورس کامل تمام این تنظیمات را از انتهای بحث نیز می‌توانید دریافت کنید.
ضمن اینکه هیچ نیازی هم به استفاده از VS 2017 نیست و هر دوی برنامه‌ی Angular و ASP.NET Core را می‌توان توسط VSCode به خوبی مدیریت و اجرا کرد.


ایجاد ساختار مقدماتی سرویس ارسال اطلاعات به سرور

در برنامه‌های Angular مرسوم است جهت کاهش مسئولیت‌های یک کلاس و امکان استفاده‌ی مجدد از کدها، منطق ارسال اطلاعات به سرور، به درون کلاس یک سرویس منتقل شود و سپس این سرویس به کلاس‌های کامپوننت‌ها، برای مثال یک فرم ثبت اطلاعات، برای ارسال و یا دریافت اطلاعات، تزریق گردد. به همین جهت، ابتدا ساختار ابتدایی این سرویس و تنظیمات مرتبط با آن‌را انجام می‌دهیم.
ابتدا از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه وارد شده (جائیکه فایل Startup.cs قرار دارد) و سپس دستور ذیل را اجرا می‌کنیم:
 >ng g s employee/FormPoster -m employee.module
با این خروجی
 installing service
  create src\app\employee\form-poster.service.spec.ts
  create src\app\employee\form-poster.service.ts
  update src\app\employee\employee.module.ts
همانطور که در سطر آخر نیز ملاحظه می‌کنید، فایل employee.module.ts را جهت درج کلاس جدید FormPosterService در قسمت providers ماژول آن به روز رسانی می‌کند؛ تا بتوانیم این سرویس را در کامپوننت‌های این ماژول تزریق کرده و استفاده کنیم.
ساختار ابتدایی این سرویس را نیز به نحو ذیل تغییر می‌دهیم:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import { Employee } from './employee';

@Injectable()
export class FormPosterService {

    constructor(private http:Http) {
    }

    postEmployeeForm(employee: Employee) {
    }
}
در اینجا سرویس Http انگیولار به سازنده‌ی کلاس تزریق شده‌است و این نحوه‌ی تعریف سبب می‌شود تا بتوان به پارامتر http، به صورت یک فیلد خصوصی تعریف شده‌ی در سطح کلاس نیز دسترسی پیدا کنیم.
چون این کلاس از ماژول توکار Http استفاده می‌کند، نیاز است این ماژول را نیز به قسمت imports فایل src\app\app.module.ts اضافه کنیم:
import { HttpModule } from "@angular/http";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    EmployeeModule,
    AppRoutingModule
  ]
اکنون می‌توانیم این سرویس جدید FormPosterService را به سازنده‌ی کامپوننت EmployeeRegisterComponent در فایل src\app\employee\employee-register\employee-register.component.ts تزریق کنیم:
import { FormPosterService } from "../form-poster.service";

export class EmployeeRegisterComponent implements OnInit {

  constructor(private formPoster: FormPosterService) {}

}

در ادامه برای آزمایش برنامه، به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی، دستورات:
>npm install
>ng build --watch
و در دومی، دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
دستورات اول کار بازیابی وابستگی‌های سمت کلاینت و سپس ساخت تدریجی برنامه‌ی Angular را دنبال می‌کند. دستورات دوم، وابستگی‌های برنامه‌ی ASP.NET Core را دریافت و نصب کرده و سپس برنامه را در حالت watch ساخته و بر روی پورت 5000 ارائه می‌کند (بدون نیاز به اجرای VS 2017؛ این دستور عمومی است).
به همین جهت برای آزمایش ابتدایی آن، آدرس http://localhost:5000 را در مرورگر باز کنید. برگه‌ی developer tools مرورگر را نیز بررسی کنید تا خطایی در آن ظاهر نشده باشد. برای مثال اگر فراموش کرده باشید تا HttpModule را به app.module اضافه کنید، خطای no provider for HttpModule را مشاهده خواهید کرد.


مدیریت رخداد submit فرم در Angular

تا اینجا کار برپایی تنظیمات اولیه‌ی کار با سرویس Http را انجام دادیم. مرحله‌ی بعد مدیریت رخداد submit فرم است. به همین جهت فایل src\app\employee\employee-register\employee-register.component.html را گشوده و سپس رخدادگردان submit را به فرم آن اضافه کنید:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
در حین رخدادگردانی submit می‌توان به template reference variable تعریف شده‌ی form# برای دسترسی به وهله‌ای از ngForm نیز کمک گرفت.
export class EmployeeRegisterComponent implements OnInit {
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);
  }
}
امضای متد submitForm را در اینجا مشاهده می‌کنید. form دریافتی آن از نوع NgForm است که در ابتدای فایل import شده‌است.
در همین حال اگر بر روی دکمه‌ی ok کلیک کنیم، چنین خروجی را در کنسول developer مروگر می‌توان مشاهده کرد:


اولین مورد، محتوای this.model است و دومی محتوای form.value را گزارش کرده‌است. همانطور که مشاهده می‌کنید، مقدار form.value بسیار شبیه است به وهله‌ای از مدلی که در سطح کلاس تعریف کرده‌ایم و این مقدار همواره توسط Angular نگهداری و مدیریت می‌شود. بنابراین حتما الزامی نیست تا مدلی را جهت کار با فرم‌های مبتنی بر قالب‌ها به صورت جداگانه‌ای تهیه کرد. توسط شیء form نیز می‌توان به تمام اطلاعات فیلدها دسترسی یافت.


تکمیل سرویس ارسال اطلاعات به سرور

در ادامه می‌خواهیم اطلاعات مدل فرم را به سرور ارسال کنیم. برای این منظور سرویس FormPoster را به صورت ذیل تکمیل می‌کنیم:
import { Injectable } from "@angular/core";
import { Http, Response, Headers, RequestOptions } from "@angular/http";

import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/observable/of";

import { Employee } from "./employee";

@Injectable()
export class FormPosterService {
  private baseUrl = "api/employee";

  constructor(private http: Http) {}

  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }

  private handleError(error: Response): Observable<any> {
    console.error("observable error: ", error);
    return Observable.throw(error.statusText);
  }

  postEmployeeForm(employee: Employee): Observable<Employee> {
    const body = JSON.stringify(employee);
    const headers = new Headers({ "Content-Type": "application/json" });
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(this.baseUrl, body, options)
      .map(this.extractData)
      .catch(this.handleError);
  }
}
برای کار با Observables یا می‌توان نوشت 'import 'rxjs/Rx که تمام بسته‌ی RxJS را import می‌کند، یا همانند این مثال بهتر است تنها اپراتورهایی را که به آن‌ها نیاز پیدا می‌کنیم، import نمائیم. به این ترتیب حجم نهایی ارائه‌ی برنامه نیز کاهش خواهد یافت.
در متد postEmployeeForm، ابتدا توسط JSON.stringify محتوای شیء کارمند encode می‌شود. البته متد post اینکار را به صورت توکار نیز می‌تواند مدیریت کند. سپس ذکر هدر مناسب در اینجا الزامی است تا در سمت سرور بتوانیم اطلاعات دریافتی را به شیء متناظری نگاشت کنیم. در غیراینصورت model binder سمت سرور نمی‌داند که چه نوع فرمتی را دریافت کرده‌است و چه نوع decoding را باید انجام دهد.
در قسمت map، کار بررسی اطلاعات دریافتی از سرور را انجام خواهیم داد و اگر در این بین خطایی وجود داشت، توسط متد handleError در کنسول developer مرورگر نمایش داده می‌شود.
خروجی متد postEmployeeForm یک Observable است. بنابراین تا زمانیکه یک subscriber نداشته باشد، اجرا نخواهد شد. به همین جهت به کلاس EmployeeRegisterComponent مراجعه کرده و متد submitForm را به نحو ذیل تکمیل می‌کنیم:
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);

    // validate form
    this.validatePrimaryLanguage(this.model.primaryLanguage);
    if (this.hasPrimaryLanguageError) {
      return;
    }

    this.formPoster
      .postEmployeeForm(this.model)
      .subscribe(
        data => console.log("success: ", data),
        err => console.log("error: ", err)
      );
  }
در اینجا ابتدا اعتبارسنجی سفارشی drop down را که در قسمت قبل بررسی کردیم، قرار داده‌ایم. پس از آن متد postEmployeeForm سرویس formPoster فراخوانی شده‌است و در اینجا کار subscribe به نتیجه‌ی عملیات صورت گرفته‌است که می‌تواند حاوی اطلاعاتی از سمت سرور و یا خطایی در این بین باشد.

یک نکته: اگر علاقمند باشید تا ساختار واقعی شیء NgForm را مشاهده کنید، در ابتدای متد فوق، console.log(form.form) را فراخوانی کنید و سپس شیء حاصل را در کنسول developer مرورگر بررسی نمائید.


تکمیل Web API برنامه‌ی ASP.NET Core جهت دریافت اطلاعات از کلاینت‌ها

در ابتدای سرویس formPoster، یک چنین تعریفی را داریم:
export class FormPosterService {
  private baseUrl = "api/employee";
به همین جهت نیاز است سرویس Web API سمت سرور خود را بر این مبنا تکمیل کنیم.
ابتدا مدل زیر را به پروژه‌ی ASP.NET Core جاری، معادل نمونه‌ی تایپ‌اسکریپتی سمت کلاینت آن اضافه می‌کنیم. البته در اینجا یک Id نیز اضافه شده‌است:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Employee
    {
        public int Id { set; get; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsFullTime { get; set; }
        public string PaymentType { get; set; }
        public string PrimaryLanguage { get; set; }
    }
}

سپس کنترلر جدید EmployeeController را با محتوای ذیل اضافه خواهیم کرد:
using Microsoft.AspNetCore.Mvc;
using AngularTemplateDrivenFormsLab.Models;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        public IActionResult Post([FromBody] Employee model)
        {
            //todo: save model

            model.Id = 100;
            return Created("", new { fields = model });
        }
    }
}
این کنترلر با شیوه‌ی Web API تعریف شده‌است. مسیریابی آن با api شروع می‌شود تا با مسیر baseUrl سرویس formPoster تطابق پیدا کند.
در اینجا پس از ثبت فرضی مدل، Id آن به همراه اطلاعات مدل، به نحوی که ملاحظه می‌کنید، بازگشت داده شده‌است. این نوع خروجی، یک چنین JSON ایی را تولید می‌کند:
{"fields":{"id":100,"firstName":"Vahid","lastName":"N","isFullTime":true,"paymentType":"FullTime","primaryLanguage":"Persian"}}
به همین جهت است که در متد extractData، دسترسی به body.fields را مشاهده می‌کنید. این fields در اینجا دربرگیرنده‌ی اطلاعات بازگشتی از سرور است (نام آن دلخواه است و درصورت تغییر آن در سمت سرو، باید این نام را در متد extractData نیز اصلاح کنید).
  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }
اکنون اگر برنامه را با دستورات dotnet watch build و ng build --watch اجرا کنیم، بر روی پورت 5000 قابل دسترسی خواهد بود و پس از ارسال فرم به سرور، چنین خروجی را می‌توان در کنسول developer مرورگر مشاهده کرد:


نمایش success به همراه شیءایی که از سمت سرور دریافت شده‌است؛ که حاصل اجرای سطر ذیل در متد submitForm است:
 data => console.log("success: ", data)
همانطور که مشاهده می‌کنید، این شیء به همراه Id نیز هست. بنابراین درصورت نیاز به آن در سمت کلاینت، خاصیت معادل آن‌را به کلاس کارمند اضافه کرده و در همین سطر فوق می‌توان به آن دسترسی یافت.


بارگذاری اطلاعات drop down از سرور

تا اینجا اطلاعات drop down نمایش داده شده از یک آرایه‌ی مشخص سمت کلاینت تامین شدند. در ادامه قصد داریم تا آن‌ها را از سرور دریافت کنیم. به همین جهت اکشن متد ذیل را به کنترلر سمت سرور برنامه اضافه کنید:
[HttpGet("/api/[controller]/[action]")]
public IActionResult Languages()
{
    string[] languages = { "Persian", "English", "Spanish", "Other" };
    return Ok(languages);
}
که برای آزمایش آن می‌توانید مسیر http://localhost:5000/api/employee/languages را جداگانه در مرورگر درخواست کنید.
پس از آن در سمت کلاینت این تغییرات نیاز هستند:
ابتدا به سرویس FormPosterService دو متد ذیل را اضافه می‌کنیم که کار آن‌ها دریافت و پردازش اطلاعات از api/employee/languages سمت سرور هستند:
  private extractLanguages(res: Response) {
    const body = res.json();
    return body || {};
  }

  getLanguages(): Observable<any> {
    return this.http
      .get(`${this.baseUrl}/languages`)
      .map(this.extractLanguages)
      .catch(this.handleError);
  }
اینبار چون خروجی سمت سرور را مانند قبل (متد extractData) داخل فیلدی مانند fields محصور نکردیم، همان body دریافتی بازگشت داده شده‌است.
پس از آن دو تغییر ذیل را نیاز است به EmployeeRegisterComponent اعمال کنیم:
  languages = [];

  ngOnInit() {
    this.formPoster
      .getLanguages()
      .subscribe(
        data => this.languages = data,
        err => console.log("get error: ", err)
      );
  }
ابتدا آرایه‌ی زبان‌ها با یک آرایه‌ی خالی مقدار دهی شده‌است و سپس در متد ngOnInit، کار دریافت اطلاعات آن از سرور، صورت گرفته‌است.

مشکل! ممکن است مدت زمانی طول بکشد تا این اطلاعات از سمت سرور دریافت شوند. در این حالت می‌توان به شکل زیر در فایل employee-register.component.html فرم را تا زمان پر شدن دراپ داون آن مخفی کرد:
<h3 *ngIf="languages.length == 0">Loading...</h3>
<div class="container" *ngIf="languages.length > 0">
در این حالت هر زمانیکه آرایه‌ی زبان‌ها پر شد، loading حذف شده و div نمایان می‌گردد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-05.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات:
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون می‌توانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
مطالب
ارتقاء به Angular 6: بررسی تغییرات Angular CLI
اولین مرحله‌ی ارتقاء به Angular 6، به روز رسانی Angular CLI 1.x به نگارش 6 آن است. این شماره نگارش نیز با شماره نگارش Angular یکی شده‌است و دیگر 1x نیست. CLI 6.0 فقط پروژه‌های Angular 5.x و 6x را پشتیبانی می‌کند و برای نصب آن نیاز به حداقل NodeJS 8.9 و NPM 5.5 را خواهید داشت. بنابراین ابتدا دستورات زیر را صادر کرده و اگر هنوز از نگارش‌های قدیمی این ابزارها استفاده می‌کنید، قبل از هر کاری باید آن‌ها را به روز رسانی کنید:
node -v
npm -v
این نگارش از CLI در پشت صحنه از Webpack 4 استفاده می‌کند که نسبت به نگارش‌های پیشین آن بسیار سریعتر است، tree-shaking بهتری را انجام می‌دهد و در نهایت سبب تولید برنامه‌هایی با حجم کمتر و با سرعت build بیشتری خواهد شد.


فایل پیشین angular-cli.json حذف و فایل جدید angular.json بجای آن معرفی شده‌است

یکی از مهم‌ترین تغییرات CLI 6.0 نسبت به نگارش‌های قبلی آن، پشتیبانی از چندین پروژه است و به همین منظور ساختار فایل تنظیمات آن‌را به طور کامل تغییر داده‌اند و اگر دستور ng new project1 را صادر کنید، دیگر از فایل پیشین angular-cli.json خبری نبوده و بجای آن فایل جدید angular.json قابل مشاهده‌است:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "project1": {
در اینجا ساختار ابتدای فایل angular.json را مشاهده می‌کنید که در آن مفهوم projects (و یا workspace در اینجا) به همراه project1 جدیدی که ایجاد کردیم، به عنوان زیر مجموعه‌ی آن قابل مشاهده‌است.
مزیت مهم این قابلیت، امکان ایجاد libraries است که به صورت توکار از این نگارش پشتیبانی می‌شود و می‌توان اجزایی مانند components ،directives ،pipes و services اشتراکی را در یک یا چندین کتابخانه قرار داد و سپس از آن‌ها در پروژه‌ی اصلی و یا چندین پروژه‌ی متصل استفاده کرد.
نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagr را به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌های npm حاصل را نیز به همراه دارد؛ مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار، با بسته‌های فعلی npm مخصوص Angular وجود دارند. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات 13 صفحه‌ای ساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0، به صورت خودکار ایجاد و مدیریت می‌شود.

بنابراین اکنون سؤال مهمی که مطرح می‌شود این است: آیا باید فایل angular-cli.json پیشین را به صورت دستی به این فایل جدید به روز رسانی کرد و چگونه؟


به روز رسانی تمام بسته‌های سراسری سیستم

در ادامه پیش از هر کاری نیاز است تمام بسته‌های سراسری npm ایی را که هر از چندگاهی به سیستم خود اضافه کرده‌اید، به روز رسانی کنید. برای مشاهده‌ی لیست موارد تاریخ مصرف گذشته‌ی آن‌ها، دستور زیر را صادر کنید:
 npm outdated -g --depth=0
و برای به روز رسانی یکجای آن‌ها نیاز است دستور زیر اجرا شود:
 npm update -g


به روز رسانی خودکار ساختار فایل angular-cli.json

این به روز رسانی توسط CLI 6.0 به صورت خودکار پشتیبانی می‌شود و شامل این مراحل است:
ابتدا نیاز است بسته‌ی سراسری آن‌را به روز رسانی کرد:
 npm i -g @angular/cli
سپس وارد پوشه‌ی اصلی پروژه‌ی خود شده و این دستور را به صورت محلی نیز وارد کنید:
 npm install --save-dev @angular/cli@latest
با اینکار بسته‌ی CLI محلی پروژه به روز شده و اکنون می‌توانیم از قابلیت جدید آن که ng update نام دارد، استفاده کنیم. برای این منظور دستورات ذیل را به ترتیب اجرا کنید:
ng update @angular/cli
ng update @angular/core
ng update rxjs
دستور اول کار تبدیل خودکار فایل angular-cli.json قدیمی را به ساختار جدید آن انجام میدهد؛ با این لاگ:
DELETE .angular-cli.json
CREATE angular.json (4273 bytes)
UPDATE karma.conf.js (1008 bytes)
UPDATE src/tsconfig.spec.json (322 bytes)
UPDATE package.json (2076 bytes)
UPDATE tslint.json (3217 bytes)
دستور دوم بسته‌های هسته‌ی angular را به روز رسانی می‌کند و دستور سوم کار به روز رسانی کتابخانه‌ی rxjs را انجام می‌دهد.
لیست سایر بسته‌هایی را که می‌توان توسط این دستور به روز رسانی کرد، با اجرا دستور ng update می‌توانید مشاهده کنید. برای مثال اگر از Angular Material نیز استفاده می‌کنید، دستور به روز رسانی آن به صورت زیر است:
 ng update @angular/material


مشکل! دستور ng update کار نمی‌کند!

اگر پروژه‌ی شما صرفا مبتنی بر بسته‌های اصلی Angular باشد، مراحل یاد شده‌ی فوق را با موفقیت به پایان خواهید رساند. اما اگر از کتابخانه‌های ثالثی استفاده کرده باشید، منهای دستور «ng update @angular/cli» که کار تولید فایل جدید angular.json را انجام می‌دهد، مابقی با خطاهایی مانند «incompatible peer dependency» و یا «Invalid range:>=2.3.1 <3.0.0||>=4.0.0» متوقف می‌شوند.
در یک چنین حالتی نیاز است ابتدا وابستگی‌های محلی پروژه را به روز کرد و سپس دستورات ng update را تکرار نمود. برای این منظور ابتدا بسته‌ی npm-check-updates را نصب کنید:
 npm install npm-check-updates -g
کار آن به روز رسانی خودکار بسته‌های npm یک پروژه است. پس از آن دستورات زیر را صادر نمائید:
ncu -u
npm install
دستور اول تمام شماره نگارش‌های بسته‌های موجود در فایل package.json را به صورت خودکار به آخرین نگارش آن‌ها روز رسانی می‌کند و دستور دوم این بسته‌های جدید را دریافت و نصب خواهد کرد.
اکنون تمام وابستگی‌های محلی پروژه‌ی شما به صورت خودکار به آخرین نگارش آن‌ها به روز رسانی شده‌اند و اینبار اگر دستورات ذیل را اجرا کنید، با خطاهای یاد شده مواجه نخواهید شد:
ng update @angular/core
ng update rxjs

البته اگر در این حالت برنامه را کامپایل کنید، کار نخواهد کرد. علت اصلی آن به به‌روز رسانی rxjs به نگارش 6 آن مرتبط می‌شود که در مطلب بعدی پیگیری خواهد شد و این نگارش شامل حذفیات بسیاری است در جهت کاهش حجم آن، یکپارچکی و یک دست شدن syntax آن و همچنین بهبود قابل ملاحظه‌ی کارآیی آن. البته پیشنیاز الزامی آن آشنایی با pipe-able operators است. علت دیگر کامپایل نشدن برنامه هم می‌تواند عدم استفاده از HttpClient نگارش 4.3 به بعد باشد.


خاموش کردن اخطار «TypeScript version mismatch»

اگر نگارش TypeScript نصب شده‌ی در سیستم به صورت سراسری، با نگارش محلی پروژه‌ی شما یکی نباشد، اخطار «TypeScript version mismatch» را دریافت می‌کنید. روش خاموش کردن آن در CLI جدید با اجرای دستور زیر است:
 ng config cli.warnings.typescriptMismatch false
البته نگارش 6 نیاز به TypeScript 2.7.2 ذکر شده‌ی در package.json پروژه‌ی محلی را دارد؛ وگرنه اصلا شروع به کامپایل برنامه نمی‌کند:
{
  "devDependencies": {
    "typescript": "~2.7.2"
  }
}
بهترین راه برای یافتن این شماره، اجرای دستور ng new projet1 در یک پوشه‌ی خالی است و سپس مقایسه‌ی محتوای فایل package.json آن با فایل پروژه‌ی موجود.


 تغییرات ng build در نگارش 6

در نگارش 6، مفهوم پیشین environments به configurations تغییر یافته‌است و اینبار در فایل جدید angular.json تنظیم می‌شوند:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "angular-template-driven-forms-lab": {
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },
و پیشتر اگر برای تنظیم محیط build از سوئیچ env استفاده می‌شد:
 ng build --env staging
اکنون این سوئیچ به configuration تغییر یافته‌است و نام آن از مداخل configurations فایل angular.json، مانند مثال فوق که «production» نام دارد، باید دریافت و تنظیم شود:
 ng build --configuration staging
و یا برای مثال دستور «ng build --env=prod» دیگر اجرا نمی‌شود و env=prod حذف شده‌است و اکنون اجرای  ng build --prod مانند اجرای دستور ng build --configuration=prod است.


تغییرات نام‌های نهایی تولیدی

در CLI 6.0، نام‌های نهایی تولیدی دیگر به همراه bundle  و یا chunk نیستند. برای مثال دستور ng build یک چنین خروجی را تولید می‌کند:
 >ng build --watch

Date: 2018-05-05T09:10:50.158Z
Hash: a43eab94ff01539b8592
Time: 31733ms
chunk {main} main.js, main.js.map (main) 9.38 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 226 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 5.4 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 15.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.05 MB [initial] [rendered]

و هش‌های حالت prod به صورت زیر تولید و به نام فایل اضافه می‌شوند:
>ng build --prod --watch

Date: 2018-05-05T09:17:01.803Z
Hash: f25fd6788a4969c52b70
Time: 73279ms
chunk {0} runtime.6afe30102d8fe7337431.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.6c08419970f9e4781b69.js (polyfills) 59.4 kB [initial] [rendered]


ساده شدن افزودن وابستگی‌های ثالث به پروژه‌های CLI

برای نصب یک کتابخانه‌ی ثالث، پیشتر می‌بایستی ابتدا بسته‌ی npm آن جداگانه نصب و سپس فایل config برنامه، جهت معرفی مداخل آن، ویرایش می‌شد. اکنون دستور جدید ng add تمام این مراحل را به صورت خودکار انجام می‌دهد:
 ng add @angular/material
برای نمونه دستور فوق نه تنها فایل‌های Angular Material را دریافت می‌کند، بلکه فایل‌های CSS و ماژول‌های مرتبط با آن‌را نیز import خواهد کرد. البته این مورد از کلید ng-add فایل package.json بسته‌ی در حال نصب دریافت و تنظیم می‌شود و کتابخانه‌های جدید باید خود را بر این اساس وفق دهند.
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت چهارم - تکمیل مستندات نوع‌های خروجی API
Swashbuckle.AspNetCore چگونه اطلاعات خود را فراهم می‌کند؟

در برنامه‌های ASP.NET Core، اطلاعات OpenAPI بر اساس سرویس توکاری به نام ApiExplorer تولید می‌شود که کار آن فراهم آوردن متادیتای مرتبط با یک برنامه‌ی وب است. برای مثال توسط این سرویس می‌توان به لیست کنترلرها، متدها و پارامترهای آن‌ها دسترسی یافت. Swashbuckle.AspNetCore به کمک ApiExplorer کار تولید OpenAPI Specification را انجام می‌دهد. برای فعالسازی این سرویس نیازی نیست کار خاصی انجام شود و زمانیکه ()services.AddMvc را فراخوانی می‌کنیم، ثبت و معرفی این سرویس نیز جزئی از آن است.


اهمیت تولید Response Types صحیح

در قسمت‌های قبل مشاهده کردیم که اگر متدی برای مثال در قسمتی از آن return NotFound یا 404 را داشته باشد، این نوع از خروجی، در OpenAPI Specification تولیدی لحاظ نمی‌شود و ناقص است و یا حتی ممکن است Response Type پیش‌فرض تولیدی که 200 است، ارتباطی به هیچکدام از نوع‌های خروجی یک اکشن متد نداشته باشد و نیاز به اصلاح آن است. این مورد برای تکمیل مستندات یک API ضروری است و استفاده کنندگان از یک API باید بدانند چون نوع خروجی‌هایی را ممکن است در شرایط مختلف، دریافت کنند.


روش تغییر و اصلاح Response Type پیش‌فرض OpenAPI Specification

اکشن متد GetBook کنترلر کتاب‌ها، دارای دو نوع return Ok و return NotFound است؛ اما OpenAPI Specification تولیدی پیش‌فرض، تنها حالت return Ok یا 200 را مستند می‌کند. برای تکمیل مستندات این اکشن متد، می‌توان به صورت زیر عمل کرد:
/// <summary>
/// Get a book by id for a specific author
/// </summary>
/// <param name="authorId">The id of the book author</param>
/// <param name="bookId">The id of the book</param>
/// <returns>An ActionResult of type Book</returns>
/// <response code="200">Returns the requested book</response>
[HttpGet("{bookId}")]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<Book>> GetBook(Guid authorId, Guid bookId)
در اینجا با استفاده از ویژگی ProducesResponseType، می‌توان StatusCodes بازگشتی از متد را به صورت صریحی مشخص کرد. وجود این متادیتاها سبب خواهد شد تاOpenAPI Specification تولیدی، به نحو صحیحی حالت 404 را نیز لحاظ کند.
در اینجا StatusCodes.Status400BadRequest را نیز مشاهده می‌کنید. هرچند حالت return BadRequest در کدهای این اکشن متد وجود خارجی ندارد، اما در صورت بروز مشکلی در فراخوانی و یا پردازش آن، به صورت خودکار توسط فریم‌ورک بازگشت داده می‌شود. بنابراین مستندسازی آن نیز ضروری است.

برای آزمایش آن، برنامه را اجرا کنید. در قسمت مستندات متد فوق، اکنون سه حالت 404، 400 و 200 قابل مشاهده هستند. برای نمونه بر روی دکمه‌ی try it out آن کلیک کرده و زمانیکه authorId و bookId را درخواست می‌کند، دو Guid اتفاقی و کاملا بی‌ربط را وارد کنید. همچنین Controls Accept header را نیز بر روی application/json قرار دهید. سپس بر روی دکمه‌ی execute در ذیل آن کلیک نمائید. یک چنین خروجی 404 کاملی را مشاهده خواهید کرد:


در این تصویر، response body بر اساس rfc 7807 تولید می‌شود و استاندارد گزارش مشکلات یک API است. این مورد اکنون به صورت یک اسکیمای جدید در انتهای مستندات تولیدی نیز قابل مشاهده‌است:



بهبود مستندات تشخیص نوع‌های مدل‌های خروجی اکشن متدها

مورد دیگری که در اینجا جالب توجه است، تشخیص نوع خروجی، در حالت return Ok است:


در اینجا اگر بر روی لینک Schema، بجای Example value پیش‌فرض کلیک کنیم، تصویر فوق حاصل می‌شود. تشخیص این نوع، به علت استفاده‌ی از ActionResult از نوع Book، به صورت زیر است که در ASP.NET Core 2.1 برای همین منظور (تکمیل مستندات Swagger) معرفی شده‌است:
public async Task<ActionResult<Book>> GetBook(Guid authorId, Guid bookId)
بنابراین از ASP.NET Core 2.1 به بعد، بهتر است در APIها خود از IActionResult استفاده نکنید و شروع به کار با <ActionResult<T نمائید تا بتوان مستندات بهتری را تولید کرد. اگر از IActionResult استفاده کنید، دیگر خبری از Example value و Schema تصویر فوق نخواهد بود و از روی متادیتای این اکشن متد نمی‌توان نوع خروجی آن‌را تشخیص داد. البته در این حالت می‌توان این مشکل را به صورت زیر نیز حل کرد؛ اما باز هم بهتر است از <ActionResult<T استفاده کنید:
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Book))]

یک نکته: در این تصویر، در قسمت توضیحات حالت 200، عبارت "Returns the requested book" مشاهده می‌شود. اما در حالت‌های دیگر response types تعریف شده، عبارات پیش‌فرض bad request و یا not found نمایش داده شده‌اند. نحوه‌ی بازنویسی این پیش‌فرض‌ها، با تکمیل مستندات XMLای اکشن متد و ذکر response code دلخواه، به صورت زیر است:
/// <response code="200">Returns the requested book</response>


استفاده از API Analyzers برای بهبود OpenAPI Specification تولیدی

اکنون این سؤال مطرح می‌شود که پس از این تغییرات، هنوز چه مواردی در OpenAPI Specification تولیدی ما وجود خارجی ندارند و بهتر است اضافه شوند. برای پاسخ به این سؤال، از زمان ارائه‌ی ASP.NET Core 2.2، بسته‌ی جدید Microsoft.AspNetCore.Mvc.Api.Analyzers نیز ارائه شده‌است که کار آن دقیقا بررسی همین نقایص و کمبودها است. بنابراین ابتدا آن‌را به فایل OpenAPISwaggerDoc.Web.csproj اضافه کرده و سپس دستور dotnet restore را صادر می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Api.Analyzers" Version="2.2.0" />
  </ItemGroup>
پس از نصب این ابزار جدید که به صورت افزونه‌ای برای کامپایلر #‍C کار می‌کند، بلافاصله اخطارهایی توسط کامپایلر ظاهر خواهند شد؛ مانند:
Controllers\BooksController.cs(40,17): warning API1000: Action method returns undeclared status code '404'.
Controllers\BooksController.cs(89,13): warning API1000: Action method returns undeclared status code '201'.
همانطور که ملاحظه می‌کنید، هنوز هم نیاز به تعریف تعدادی ProducesResponseType فراموش شده وجود دارد که آن‌ها را به صورت زیر اضافه خواهیم کرد:
/// <summary>
/// Get the books for a specific author
/// </summary>
/// <param name="authorId">The id of the book author</param>
/// <returns>An ActionResult of type IEnumerable of Book</returns>
[HttpGet()]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesDefaultResponseType]
public async Task<ActionResult<IEnumerable<Book>>> GetBooks(Guid authorId)
در ابتدا نوع‌های خروجی اکشن متد GetBooks را تکمیل می‌کنیم که آن نیز دارای return NotFound و همچنین return Ok است. به علاوه در اینجا ویژگی جدید ProducesDefaultResponseType را نیز ملاحظه می‌کنید که یک چنین خروجی را تولید می‌کند:


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


ساده سازی کدهای تکراری تعریف ProducesResponseTypeها

مواردی مانند StatusCodes.Status400BadRequest و یا 406 را در حالت عدم قبول درخواست (مثلا با انتخاب یک accept header اشتباه) و یا 500 را در صورت وجود استثنائی در سمت سرور، باید به تمام اکشن متدها نیز اضافه کرد؛ چون می‌توانند تحت شرایطی، نوع‌های خروجی معتبری باشند. برای خلاصه سازی این عملیات، یا می‌توان این ویژگی‌ها را بجای قراردادن آن‌ها در بالای تعریف امضای اکشن متدها، به بالای تعریف کلاس کنترلر انتقال داد. با اینکار ویژگی که به یک کنترلر اعمال شده باشد به تمام اکشن متدهای آن نیز اعمال خواهد شد و یا حتی برای عدم تعریف این ویژگی‌های تکراری به ازای هر کنترلر موجود، می‌توان آن‌ها را به صورت سراسری تعریف کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));
                setupAction.Filters.Add(
                    new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));
                setupAction.Filters.Add(
                    new ProducesDefaultResponseTypeAttribute());

                setupAction.ReturnHttpNotAcceptable = true;
// ...
به این ترتیب یکسری از نوع‌های خروجی که ممکن است توسط خود فریم‌ورک به صورت خودکار بازگشت داده شوند، به صورت سراسری به تمام اکشن متدهای برنامه اعمال می‌شوند و دیگر نیازی به تعریف دستی آن‌ها نخواهد بود.



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

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


Duck typing چیست؟

duck typing به این معنا است که اگر پرنده‌ای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده می‌شود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته می‌شود. برای نمونه به مثال ذیل دقت کنید:
interface Duck {
    walk: () => void;
    swim: () => void;
    quack: () => void;
}

let probablyADuck = {
    walk: () => console.log('walking like a duck'),
    swim: () => console.log('swimming like a duck'),
    quack: () => console.log('quacking like a duck')
}

function FlyOverWater(bird: Duck) { }

FlyOverWater(probablyADuck); // works!
در این مثال اینترفیس Duck، متدهایی را تعریف کرده‌است که یک Duck می‌تواند انجام دهد.
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شده‌است که همان متدهای اینترفیس Duck را پیاده سازی می‌کند و امضای آن‌ها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شده‌است.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شده‌است و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کرده‌است، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing می‌گویند.


نحوه‌ی تعریف یک اینترفیس در TypeScript

تعریف یک اینترفیس با واژه‌ی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آن‌ها تعریف خواهند شد:
interface Book {
    id: number;
    title: string;
    author: string;
    pages?: number;
    markDamaged: (reason: string) => void;
}
در این مثال خواص id، title و author اجباری هستند و پیاده سازی کننده موظف است آن‌ها را به همراه داشته باشد.
در اینترفیس‌های TypeScript می‌توان خواص اختیاری و optional را نیز تعریف کرد. نمونه‌ی آن خاصیت pages در این مثال است که با ? مشخص شده‌است و نمونه‌ی آن‌را در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی  انجام می‌شود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.


استفاده از اینترفیس‌ها برای تعریف نوع خروجی توابع

در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوع‌های رشته‌ای و عددی است و خروجی آن نیز از نوع رشته‌ای تعریف شده‌است:
function CreateCustomerID(name: string, id: number): string {
    return name + id;
}
در ادامه تعریف متغیری را مشاهده می‌کنید که نوع آن، متدی است که با امضای متد CreateCustomerID یکسان است:
 let IdGenerator: (chars: string, nums: number) => string;
به این ترتیب امکان انتساب متد CreateCustomerID به متغیر IdGenerator وجود خواهد داشت:
IdGenerator = CreateCustomerID;
جهت مدیریت بهتر یک چنین تعریف‌هایی و همچنین امکان استفاده‌ی مجدد از آن‌ها، می‌توان از اینترفیس‌ها کمک گرفت:
interface StringGenerator {
     (chars: string, nums: number): string;
}
اینترفیس StringGenerator نام بهتر و با قابلیت استفاده‌ی مجددی را به نوع متدی که قابل انتساب است به متغیر IdGenerator، تعریف می‌کند. در اینجا syntax تعریف نوع متد، در اینترفیس StringGenerator اندکی با حالت‌های قبلی متفاوت است. در اینجا بجای استفاده از <= جهت مشخص کردن نوع خروجی متد، از کولن استفاده شده‌است.
اکنون می‌توان نحوه‌ی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
 let IdGenerator: StringGenerator;
به عنوان نمونه می‌توان یک چنین تغییری را در نحوه‌ی تعریف اینترفیس Book ابتدای بحث و تغییر متد markDamaged آن نیز اعمال کرد.


بسط و توسعه‌ی اینترفیس‌ها

بسط و توسعه‌ی اینترفیس‌ها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، می‌توان به نوعی مرکب رسید:
interface LibraryResource {
   catalogNumber: number;
}

interface LibraryBook {
   title: string;
}

interface Encyclopedia extends LibraryResource, LibraryBook {
   volume: number;
}
در این مثال، ابتدا دو اینترفیس منابع و کتاب‌های یک کتابخانه تعریف شده‌اند. سپس اینترفیس جدیدی به نام Encyclopedia با بسط این دو اینترفیس توسط واژه‌ی کلیدی extends ایجاد شده‌است.
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیس‌های ذکر شده‌ی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
let refBook: Encyclopedia = {
   catalogNumber: 1234,
   title: 'The Book of Everything',
   volume: 1
}


نوع کلاس‌ها

مبحث کلاس‌ها به صورت جداگانه‌ای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشاره‌ی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیس‌ها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف می‌کنند؛ بدون ارائه‌ی پیاده سازی خاصی از آن‌‌ها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاس‌ها روش بهتری برای انجام این‌کار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه می‌کنید:
interface Librarian {
   doWork: () => void;
}
متد doWork دارای پارامتری نیست و خروجی نیز ندارد. سپس با استفاده از واژه‌ی کلیدی class، یک کلاس جدید را ایجاد کرده‌ایم که با استفاده‌ی واژه‌ی کلیدی implements، یک پیاده سازی مشخص از اینترفیس Librarian را ارائه می‌دهد:
class ElementarySchoolLibrarian implements Librarian {
   doWork() {
     console.log('Reading to and teaching children...');
   }
}
اکنون داخل این کلاس، پیاده سازی خاصی از متد doWork مشخص شده‌ی در قرارداد و اینترفیس Librarian را مشاهده می‌کنید.
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل می‌کنیم:
 let kidsLibrarian: Librarian = new ElementarySchoolLibrarian();
kidsLibrarian.doWork();
در اینجا متغیر kidsLibrarian از نوع اینترفیس کتابدار تعریف شده‌است. به این معنا که شیءایی که به آن انتساب داده می‌شود باید این اینترفیس را پیاده سازی کند. این شیء نیز توسط واژه‌ی کلیدی new، نمونه سازی/وهله سازی می‌شود. در ادامه می‌توان به متدها و خواص شیء kidsLibrarian دسترسی یافت و آن‌ها را فراخوانی کرد.