استفاده از TypeScript Knockout در MVC

در این مقاله نگاهی به نحوه استفاده از تعاریف نوع‌های (Definitions Type) تایپ‌اسکریپت برای KnockoutJS جهت ساخت یک برنامه کوچک ASP.NET MVC می‌اندازیم. در طی این مقاله، زبان TypeScript را بررسی کرده و نحوه استفاده این زبان برای ساخت برنامه‌های ASP.NET MVC که جهت تعامل‌سازی بر پایه کتابخانه‌های JS سمت کلاینت می‌باشد را می‌سنجیم.

استفاده از TypeScript Knockout در MVC

در 1 اکتبر 2012، Ander Hejlsberg عضو تیم مایکروسافت، تایپ اسکریپت را معرفی کرد. او در سال 2013 آخرین به‌روزرسانی‌های تایپ اسکریپت (ورژن v 0.9.0) را ارائه داد. از اکتبر 2012 تاکنون، این زبان پیشرفت‌های مداومی داشته است و در حال حاضر شامل ساختاری پیشرفته مانند Genericها و lambda expressionها و غیره می‌باشد. در ابزارهای پشتیبانی آن نیز پیشرفت‌های خوبی صورت گرفته است. در حال حاضر یک پروژه Github به نام DefinitelyTyped وجود دارد که اکثر Type annotationها را برای تایپ‌اسکریپت نگه می‌دارد.

شروع به کار با تایپ‌اسکریپت در Visual Studio 2013 Preview

ابزارهای پشتیبانی تایپ‌اسکریپت به شکل افزونه‌هایی در ویژوال استودیو می‌باشند که می‌توانید آن‌ها را از http://www.microsoft.com/en-us/download/confirmation.aspx?id=34790 دانلود کنید. حجم فایل باینری آن در ورژن 0.9.1.1 تقریبا برابر MB12 بود.

بعد از دانلود، ویژوال استودیو را ببندید و فایل نصب را اجرا کنید. ما آن را روی Visual Studio 2013 Preview اجرا کردیم اما با VS 2012 هم به خوبی کار کرد.

نصب تقریبا یک دقیقه طول می‌کشد. پس از تکمیل نصب، می‌توانید با تایپ‌اسکریپت شروع به کار کنید.

برخلاف ابزارهای پیشین، هیچ قالب پروژه MVC ساخته شده‌ای برای تایپ‌اسکریپت وجود ندارد. با نصب تایپ‌اسکریپت فقط قالب پروژه "HTML Application with TypeScript" نصب می‌شود.

در حالی که می‌توانیم مورد بالا را برای نمایش قابلیت‌های تایپ‌اسکریپت استفاده کنیم، اما می‌خواهیم کمی کاربردی‌تر از آن استفاده کنیم، بنابراین ASP.NET MVC Application را انتخاب می‌کنیم.

TypeScript در ASP.NET MVC

برای کار با تایپ‌اسکریپت در Asp.Net MVC لازم نیست کار خاصی را انجام دهید. با پروژه‌ای از نوع ASP.NET Web Application  شروع به کار می‌کنیم.

سپس قالب MVC را انتخاب می‌کنیم تا استایل‌های بوت‌استرپ و غیره را وارد کند.

پس از تولید کامل کدها، فولدر Scripts را انتخاب می‌کنیم، روی آن راست کلیک کرده و New Item را می‌زنیم. در پنجره جستجو " TypeScript" را تایپ می‌کنیم تا آیتم TypeScript File را در لیست بیاورد،  فایل typescript-list.ts را به پروژه اضافه می‌کنیم.

پس از کلیک بر روی Add، ویژوال استودیو پیغام زیر را نمایش می‌دهد.

این پیغام می‌گوید که دستورات مورد نیاز برای ساخت فایل‌های ts. در js. در زمان کامپایل اضافه شده است و اگر Yes را کلیک کنید، Nuget Package Manager را باز می‌کند.

این صفحه با عبارت tag:typescript فیلتر شده است و لیستی از فایل‌های تعاریف نوع تایپ‌اسکریپت موجود را به ما نشان می‌دهد. از لیست ارائه شده،  jQuery، Knockout، BootStrap و Knockout.Mapping را اضافه می‌کنیم.

پس از کلیک بر روی Close، ساختار فولد Script به صورت زیر می‌شود.

فولدر typings دارای یک زیر پوشه برای هر کتابخانه است که حاوی فایل .ds برای کپسوله‌سازی انواع کتابخانه‌هاست.

در نهایت KnockoutJS  را با استفاده از کنسول Nuget Package Management نصب می‌کنیم.

PM> install-package knockoutjs

با این کار، ما تمام وابستگی‌های ضروری را تنظیم کرده‌ایم. حالا بیایید برنامه را شروع کنیم.

نکته: نصب Type Definitions یا همان فایل‌های تعاریف نوع، نصب کتابخانه اصلی نیست. مثلا نصب Type Definitions برای KnockoutJS واقعا KO را نصب نکرده است. ما مجبوریم که جداگانه این کارها را انجام دهیم.

برنامه لیست کردن وظایف (Task) در TypeScript

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

Task Entity

در فولدر Models کلاس Task را مانند تعریف زیر اضافه می‌کنیم.

public class TaskDetails
{
public int Id { get; set; }
public string Title { get; set; }
public string Details { get; set; }
public DateTime Starts { get; set; }
public DateTime Ends { get; set; }
}

حالا برنامه را می‌سازیم.

View مربوط به لیست Task

جهت اضافه کردن یک ویو برای Task، روی فولدر Views\Home کلیک راست کرده و Scaffold را انتخاب کنید. همانند تصویر زیر "MVC 5 View – List" را انتخاب کنید. این گزینه UI جدولی را ایجاد می‌کند.

در صفحه Add View، Model Class را انتخاب می‌کنیم و تیک partial را می‌زنیم تا یک partial view داشته باشیم.

بعد از کلیک بر روی Add، یک ویوی strongly typed برای ما تولید می‌شود.

به روز رسانی کنترلر با بازگرداندن داده‌های ساخته‌شده برای Task

در این مرحله یک متد Action اضافه می‌کنیم که داده‌هایی که ساخته‌ایم را بازمی‌گرداند:

public ActionResult Tasks()
{
List<TaskDetails> tasks = new List<TaskDetails>();
for (int i = 0; i < 10; i++)
{
  TaskDetails newTask = new TaskDetails
  {
   Id = i,
   Title = "Task " + (i + 1),
   Details = "Task Details " + (i + 1),
   Starts = DateTime.Now,
   Ends = DateTime.Now.AddDays(i + 1)
  };
  tasks.Add(newTask);
}
return View(tasks);
}

همانطور که در بالا می‌بینید، کدها 10 Taskای که با داده‌های ساده تولید شده‌اند را بازمی‌گرداند.

خروجی این برنامه را می‌توانید در تصویر زیر ببینید.

این نمایش از Task در سمت سرور ارائه شده است. حالا بیایید ببینیم که این Taskها چگونه با ساختن ViewModel در تایپ‌اسکریپت در سمت کلاینت ارائه می‌شوند.

اضافه کردن رفرنس به کد TypeScript

در فایل Tasks.cshtml، کد زیر را در پایین صفحه اضافه کنید.

@section Scripts{
<script src="~/Scripts/knockout-2.3.0.js"></script>
<script src="~/Scripts/typescript-list.js"></script>
}

اولین رفرنس اسکریپتی، KnockoutJS را به پروژه‌یمان اضافه می‌کند. اما وابستگی دوم به یک فایل JS اشاره می‌کند که واقعا وجود ندارد. در واقع ما فایل typescript-list.ts را نصب کرده بودیم. خوب، task  ساخته شده که‌ به پروژه ما اضافه شده است، وقتی اولین فایل TypeScrip را اضافه کردیم،‌ به ما اطمینان می‌دهد که یک فایل JSای بعد از یک ساخت موفقیت‌آمیز وجود خواهد داشت.

اگر برنامه را مجددا اجرا کنید و به صفحه /Home/Tasks بروید، در سولوشن ویژوال استودیو ساختار فولدری شبیه به تصویر زیر را خواهید دید. (توجه کنید که فایل ts مرتبط با فایل js تولید شده است).

تنظیمات Knockout ViewModel مورد استفاده در TypeScript

با وابستگی‌هایی که ایجاد کرده‌ایم، اجازه داریم تا اولین بیت تایپ‌اسکریپت را بنویسیم.

رفرنس‌های دیگر TypeScript Definitions

اولین کاری که باید انجام شود رفرنس دادن definitionهای Knockout و jQuery است، که سینتکس آن مانند زیر است:

///<reference path="typings/jquery/jquery.d.ts" />
///<reference path="typings/knockout/knockout.d.ts" />

سپس کلاسی با نام Task Details را ایجاد می‌کنیم و ویژگی‌های لازم را اعلان می‌کنیم.

class TaskDetails {
    id: KnockoutObservable<number>;
    title: KnockoutObservable<string>;
    details: KnockoutObservable<string>;
    starts: KnockoutObservable<string>;
    ends: KnockoutObservable<string>;
// … more to come
}

همان‌طور که می‌بینید، هر یک از ویژگی‌ها را به عنوان KnockoutObservable همراه با نوع مربوطه اعلان کرده‌ایم. تعریف نوع جنریک KnockoutObservable<T> از طریق فایل توصیفی نوع knockout.d.ts ارائه می‌شود. نکته کلیدی که در اینجا وجود دارد این است که ما فقط متغیرهایی که از آن‌ها نمونه‌ای نداریم را تعریف کردیم.

برای نمونه‌سازی آن‌ها، ما از سازنده تابع استفاده می‌کنیم، مانند زیر:

class TaskDetails
{
// … variable declarations

constructor(id: number, title: string, details: string,
  starts: string, ends: string) {
  this.id = ko.observable(id);
  this.title = ko.observable(title);
  this.details = ko.observable(details);
  this.starts = ko.observable(starts);
  this.ends = ko.observable(ends);
}
}

توجه داشته باشید، در حالی که ویژگی‌ها را نمونه‌سازی می‌کنیم، هر جا که مقادیر را به سازنده پاس می‌دهیم از KO استفاده می‌کنیم. در حال حاضر View Model ما شامل آرایه‌ای از TaskDetails است، پس بیایید کلاس ViewModel را درست کنیم.

class TaskViewModel {
public tasks: KnockoutObservableArray<TaskDetails>;
  constructor() {
   this.tasks = ko.observableArray([]);
}
}

کلاس TaskViewModel  را با یک ویژگی عمومی task ایجاد کرده‌ایم. نوع این ویژگی KnockoutObservableArray<TaskDetails> می‌باشد که در سازنده می‌سازیم.

ذخیره‌سازی داده‌های Strongly Typed به عنوان JSON و بازیابی آن سمت کلاینت

به طور رایج ما از یک ActionResult در MVC استفاده می‌کنیم تا یک ویوی خالی را return کنیم سپس وقتی سند لود می شود یک AJAX GET را انجام می‌دهیم. این کار گزینه‌هایی برای بارگذاری بنرها، میله‌های پیشرفت و غیره را در صورت لزوم به ما می‌دهد. می‌خواهیم مدل داده‌های خود را به صورت مرتب با JSON در یک فیلد ورودی مخفی پر کنیم. سپس وقتی سند لود شد آن را بازیابی کرده و یک view Model خارج از آن بسازیم.

برای ذخیره داده در فیلد ورودی مخفی باید دستورات زیر را در Tasks.cshtml اضافه کنیم.

<input type="hidden" id="serverJSON"  
value="@Newtonsoft.Json.JsonConvert.SerializeObject(Model)" />

دلیل انجام این کار این است که، همانطور که می‌دانیم،‌ تمام سینتکس‌های Razor روی سرور ارزیابی می شوند. بنابراین سرور از Newtonsoft Json برای مرتب کردن تمام مدل درون JSON استفاده می‌کند و آن را در فیلد ورودی مخفی قرار می‌دهد. این روش ظاهرا یک رفت و برگشت را ذخیره می‌کند، اما اگر ViewModel شما بزرگ باشد، می‌تواند یک صفحه نسبتا بزرگ برای اولین بارگذاری ایجاد کند.

حالا ببینیم چگونه از داده‌ها استفاده کنیم.

ابتدا یک شیء serverData را از نوع any[] ایجاد می‌کنیم. اعلان آن به عنوان آرایه حداقل Intellisense را به ما می‌دهد.

سپس از JSON.parse بومی استفاده می‌کنیم تا رشته در فیلد serverJSON را به شیء آرایه‌ای JS تبدیل کنیم. این تنها شیء weakly typedای است که در تایپ اسکریپت داریم. اگر از Knockout Mapping استفاده می‌کردیم می‌توانستیم از این مرحله بگذریم، اما بعدا به KO Mapping می‌رسیدیم.

سپس یک متغیر لوکال vm از نوع TaskViewModel را اعلان کرده و از آن نمونه می سازیم.

برای loop اساسا از طریق داده‌هایی که از سرور دریافت می‌کنیم حلقه‌ها ایجاد شده و شی‌ء‌های strongly typed TaskDetails ساخته می‌شوند و به آرایه vm.tasks اضافه می‌شوند.

وقتی View Model خوانده می‌شود، از ko.applyBindings برای تخصیص ViewModel به View استفاده می‌کنیم.

سورس کامل TypeScript برای این کار مانند زیر است:

$(document).ready(function () {
var serverData: any[];
serverData = JSON.parse($("#serverJSON").val());
var vm: TaskViewModel;
vm = new TaskViewModel();
var i: number;
 
for (i = 0; i < serverData.length; i++) {
  var serverTask: any;
  serverTask = serverData[i];
  vm.tasks.push(new TaskDetails(serverTask.Id, serverTask.Title, 
   serverTask.Details, serverTask.Starts, serverTask.Ends));
}
ko.applyBindings(vm);
});

لازم به ذکر است که اگر سعی کنیم هر شیء را درون آرایه vm.tasks، push کنیم، خطایی به صورت زیر دریافت می‌کنیم:

آپدیت View با استفاده از اتصالات سمت کلاینت

ما تمام نشانه‌گذاری‌های سمت سرور را حذف می‌کنیم و آن را با موارد زیر جایگزین می‌کنیم:

<table class="table">
<tr>
  <th>Title</th>
  <th>Details</th>
  <th>Starts</th>
  <th>Ends</th>
  <th></th>
</tr>
<tbody data-bind="foreach: tasks">
  <tr>
   <td data-bind="text: title"></td>
   <td data-bind="text: details"></td>
   <td data-bind="text: starts"></td>
   <td data-bind="text: ends"></td>
   <td></td>
  </tr>
</tbody>
</table>

این استاندارد KO binding است و چیز جدیدی نیست. اگر دوباره برنامه را اجرا کنیم، باید تصویر زیر را ببینیم.

تاریخ‌ها کمی زشت به نظر می‌رسند چون تاریخ پیش‌فرض به صورت رشته بوده است. می‌توانیم با کمک moment.js آن را تمیز کنیم.بنابراین Moment JS را با استفاده از Nuget اضافه می‌کنیم. توجه داشته باشید که اگر قصد انجام عملیات پیچیده‌تر را داشتیم، می‌توانستیم فایل‌های تعاریف نوع تایپ اسکریپت را اضافه کنیم. از آنجا که ما قصد داریم فقط آن را برای قالب‌بندی استفاده کنیم، مستقیما از Moment استفاده می‌کنیم.

به کلاس TaskDetails برمی‌گردیم. سازنده را با استفاده از Moment آپدیت می‌کنیم،‌ مانند زیر:

constructor(id: number, title: string, details: string,
starts: string, ends: string) {
this.id = ko.observable(id);
this.title = ko.observable(title);
this.details = ko.observable(details);
this.starts = ko.observable(moment(starts).format("MMM DD, YYYY  h:mm:ss a"));
this.ends = ko.observable(moment(ends).format("MMM DD, YYYY  h:mm:ss a"));
}

حالا اگر برنامه را اجرا کنیم،‌ می‌بینیم که ظاهر زیباتری دارد.

خلاصه‌سازی کدهای TypeScript

قبل از اینکه بخواهیم این مقاله را به پایان برسانیم، بیایید ببینیم کد جاوااسکریپتی که کد تایپ‌اسکریپت ما را کامپایل کرد چیست:

در زمان اجرا، ما فایل typescript-list.js را باز کردیم و اسکریپت بالا را دریافت کردیم. همانطور که می‌بینید،‌ این بسیار نزدیک به چیزی است که ما نوشتیم. با این حال باید اعتراف کنیم که این خیلی ساده‌تر از یک کلاس entity مربوط به "TaskDetails" و " TaskViewModel" است به خصوص اگر از یک زبان statically typed مثل سی‌شارپ یا جاوا برای زبان پویاتری مثل جاوااسکریپت استفاده کنید. تایپ‌اسکریپت یک پل زیبا و آسان را فراهم می‌کند.

نتیجه‌گیری

ما ویژگی‌هایی مثل کلاس‌های Statically typed، انواع جنریک‌ها و Knockout را برای تایپ اسکریپت در عمل دیدیم.

دلیل مهم استفاده از تایپ‌اسکریپت این است که ساختار بهتری از کدنویسی را برای ساخت برنامه‌های جاوااسکریپت بزرگ در اختیار ما قرار می‌دهد. تایپ‌اسکریپت با عملکردهای ساده پیاده‌سازی‌های خوبی را انجام می‌دهد.

آموزش asp.net mvc