مطالب
نحوه‌ی صحیح فراخوانی SQL Aggregate Functions حین استفاده از LINQ - قسمت دوم

در قسمت قبل در مورد حالتی که کوئری انجام شده نتیجه‌ای را بر نگردانده است، بحث شد. در این قسمت یکی از شایع‌ترین مشکلات حین کار با تابع Sum بررسی خواهد شد.

ابتدا جدول ساده Transactions را با دو فیلد Id و Amount مطابق تصویر زیر در نظر بگیرید:


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


همانطور که ملاحظه می‌نمائید این عملیات میسر نیست، زیرا حاصل نهایی فراتر از بازه‌ی تعریف شده‌ی Int32 است.
برای رفع این مشکل باید Amount را تبدیل به BigInt (برای مثال مرتبط با نگارش‌های مختلف SQL Server) کرد. مطابق توضیحات قسمت قبل، این عملیات casting باید به lambda expression تعریف شده اعمال گردد، زیرا خروجی Sum بر مبنای آن تعیین می‌گردد.



در این حالت خروجی SQL آن نیز به صورت زیر در خواهد آمد:



هر چند این مباحث ساده به نظر می‌رسند ولی در صورت عدم رعایت سبب سرخ و سفید شدن در هنگام مقتضی خواهند گردید.

مطالب
فایرفاکس 3.5.6 و غیرفعال شدن افزونه‌ها

امروز فایرفاکس 3.5.6 به صورت خودکار نصب شد؛ پس از نصب هم هیچکدام از افزونه‌های نصب شده ظاهر نشدند. به عبارتی به نظر همه‌ی آن‌ها غیرفعال شده بودند. اگر هم قرار باشد از فایرفاکس بدون افزونه استفاده کرد، استفاده از IE8، هم از نظر میزان مصرف حافظه و هم از نظر تعداد باگ‌های امنیتی کمتر گزارش شده (مطابق آمار) ارجحیت بالاتری دارد.
پس از اندکی جستجو مشخص شد که کاربران دیگری هم به این مشکل دچار شده‌اند.

راه حل ساده‌ای هم دارد:
فایرفاکس را بسته و پوشه‌ی Profiles مربوط به فایرفاکس را پیدا کنید. برای مثال:
C:\Users\Vahid\AppData\Roaming\Mozilla\Firefox\Profiles
در این پوشه و زیر پوشه‌های آن چهار فایل زیر را پیدا کرده و به یک پوشه دیگر منتقل کرده و یا حذف کنید:
extensions.cache
extensions.ini
extensions.rdf
compatibility.ini

اکنون با اجرای فایرفاکس این فایل‌ها مجددا تولید شده و مشکلات مربوطه هم برطرف می‌شود.
پس از تولید خودکار مجدد این فایل‌ها، مجددا افزونه‌ها ظاهر شدند و همه چیز مثل قبل شد!

نظرات نظرسنجی‌ها
شما برای کار با دیتا در اندروید، کدامیک از روش های زیر را استفاده میکنید یا ترجیح می دهید؟
خطاهایی نظیر Specified key was too long; max key length is 767 bytes تنها یکی از موارد است (این مشکلی است که من با آن مواجه بوده ام و لینک دادن صرفا برای توضیحات بیشتر است )و منظور از ساختار کدنویسی پیچیده موردی که خود من با آن روبه رو شدم در توسعه سیستمی افزونه پذیر و بکارگیری روش IUnitOfWork  بود و تغییر بدون درد سر نوع بانک اطلاعاتی است نه عدم امکان آن. و برنامه نویس زمانی می‌تواند درک مناسبی از این مشکلات داشته باشد که با ORM ای مانند  Gorm کار کرده باشد .(نکته همینجاست که با وجود کوچک بودن رضایت برنامه نویسان مرتبط خودش را فراهم کرده است .  )
کسی منکر کامل بودن EF نیست و در سیستمهای دات نتی همواره از آن استفاده می‌کنم و معتقدم هر ابزار و روشی در برنامه نویسی مناسب موقعیت خاصی است و پیچیدن نسخه ای تمام عیار برای تمامی سناریوها اشتباه است . و به نظر من کامل بودن و پرداختن به جزئیات می‌تواند تغییر موقعیت در سناریو را با چالش مواجه کند .  توضیحاتی که در بالا عنوان کردم صرفا دلیل شخصی من برای رای دادن است .  
نظرات نظرسنجی‌ها
آیا به یادگیری یا ادامه‌ی استفاده از AngularJS خواهید پرداخت؟
AngularJS فریم ورک خوبیه، ولی مشکلات زیادی داره. اول از همه اینکه در فاز تحقیقاتی هست و بر خلاف ادعا هایی که وجود داره Google در محصولات یا پروژه‌های داخلیش ازش استفاده نمی‌کنه. ثانیا تا اوایل سال 2016 انتشار مهمی (major release) نخواهیم دید و نسخه 2.0 فریم ورک کاملا جدیدی خواهد بود. بنابراین نه تنها backward compatibility نخواهیم داشت بلکه تمام تجربه و دانش فعلی عملا بلا استفاده خواهند بود و باید همه چیز رو از ابتدا یاد گرفت. باید منتظر بود تا نسخه بعدی منشر بشه و اگر اون موقع تیم Angular تونست نسبت به رقبای دیگش حرف اول رو بزنه، می‌تونیم بریم سراغش و فریم ورک‌های دیگه رو کنار بگذاریم.

در نگاه اول شاید برای توسعه دهندگان مبتدی یک سری مباحث گیج کننده باشن و خیلی از قابلیت‌ها هم جادویی و جذاب. اما حقیقت امر این است که code base این فریم ورک مشکلات شگفت آوری داره. چند ساعت وقت گذاشتن روی اینترنت کافی هست تا از تمام جنبه‌ها فریم ورک‌های مطرح رو بررسی کرد و متوجه شد که Angular چقدر مشکلات اساسی داره. بصورت تیتر وار چند مورد رو لیست می‌کنم:
  • Dynamic Scoping, and scope inheritance
  • Two-way data binding with $watchers
  • The $digest cycle
  • No DOM manipulation capabilities
  • Finite Routing, unless you use a 3rd party like ui-router
  • app logic and structure expressed in HTML
  • No server-side rendering (mostly for speed boost and SEO)
  • string-based Dependency Injection
  • Ill-Conceived architecture (obsolete constructor functions etc)
  • Debugging issues
  • Re-defining well established terminology
  • Syntactic Sugar
  • Execution Contexts
  • Unnecessary Complications
  • Incompatible with 3rd party libraries, like jQuery etc.
  • Sparse documentation with literally no real-world examples

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

در حال حاظر بین فریم ورک‌های دیگه بهترین انتخاب Ember هست که بسیاری از مشکلات ذکر شده رو نداره و ساختار و معماری قوی و خوبی هم داره. Backbone و Durandal هم فریم ورک‌های قوی ای هستند ولی تفاوت‌های نسبتا زیادی با Ember دارن.

حائز اهمیت این که، اپلیکیشن‌های SPA جوان هستند و فعلا همه جای دنیا در حال آزمایش و بررسی این هستند که چطور میشه چنین پروژه هایی رو اجرا کرد و کدام راه بهترین راه هست، بنابراین تا به استاندارد‌های ثابتی برسیم راه طولانی ای در پیش داریم. از طرفی بزودی استاندارد جدید جاوا اسکریپت (ECMA6) منتشر میشه، که با انتشارش فریم ورک هایی مثل Ember و Angular رو کاملا به هم خواهد ریخت. مثلا در نسخه جدید آبجکت‌های Observable خواهیم داشت. بنابراین متدهای Angular و Ember برای تشخیص تغییرات به کلی بلا استفاده خواهند بود و بازنویسی‌های اساسی لازم می‌شود.

مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
زبان Razor نیز در ASP.NET Core به همراه بهبودها و اضافات قابل توجهی است که در این قسمت تعدادی از آن‌ها را مانند امکان ارث بری و تزریق وابستگی‌ها، بررسی خواهیم کرد.

نحوه‌ی سفارشی سازی کلاس پایه‌ی تمام Viewهای برنامه و معرفی inherits@

در نگارش‌های پیشین ASP.NET MVC، امکان تعویض کلاس پایه‌ی Viewها، در فایل web.config واقع در پوشه‌ی ریشه‌ی Views وجود داشت. با حذف این فایل و ساده سازی و محول کردن مسئولیت‌های آن به فایل جدید view imports، اینبار برای تعریف کلاس پایه‌ی viewها می‌توان به صورت ذیل عمل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public abstract class MyCustomBaseView<TModel> : RazorPage<TModel>
    {
        public bool IsAuthenticated()
        {
            return Context.User.Identity.IsAuthenticated;
        }
 
#pragma warning disable 1998
        public override async Task ExecuteAsync()
        {
        }
#pragma warning restore 1998
    }
}
به صورت پیش فرض تمام viewهای برنامه از کلاس <RazorPage<T ارث بری می‌کنند؛ که در اینجا T، نوع مدلی است که توسط model@ تنظیم می‌شود. اگر نیاز به سفارشی سازی این کلاس وجود داشت، برای مثال بجای اینکه هربار در viewها مقدار Context.User.Identity.IsAuthenticated را جهت نمایش قسمتی از صفحه، به کاربران اعتبارسنجی شده بررسی کنیم، می‌توان این قطعه کد را به یک کلاس پایه‌ی سفارشی منتقل و از آن در تمام Viewها استفاده کرد که نمونه‌ای از آن‌را در کدهای فوق مشاهده می‌کنید.
پس از تعریف این کلاس، برای ثبت و معرفی آن به فایل ViewImports.cshtml_ مراجعه کنید و این یک سطر را به ابتدای آن اضافه نمائید:
@inherits Core1RtmEmptyTest.StartupCustomizations.MyCustomBaseView<TModel>
inherits@ از تازه‌های razor بوده و جهت تعریف ارث بری‌ها کاربرد دارد. البته ممکن است در حین تعریف فوق، TModel را قرمز رنگ مشاهده کنید که مهم نیست و بیشتر مشکل ReSharper است و برنامه بدون مشکل اجرا می‌شود.
برای نمونه پس از سفارشی سازی صفحه‌ی پایه‌ی تمام Viewها، اکنون یک سطر ذیل را در هر view ایی می‌توان تعریف و استفاده کرد:
 Is Current User Authenticated? @IsAuthenticated()


معرفی functions@

دایرکتیو جدید functions@، بسیار شبیه است به دایرکتیو قدیمی و حذف شده‌ی helper@، که در نگارش‌های پیشین Razor معرفی شده بود:
@functions
 {
    public string Test()
    {
        return message;
    }
 
    readonly string message = "test";
}
ASP.NET Core در حین پردازش یک View، کدهای آن‌را تبدیل به یک کلاس می‌کند و در اینجا تمام کدهای داخل بدنه‌ی functions@ را نیز به صورت اعضای این کلاس تعریف خواهد کرد. به این ترتیب یک چنین فراخوانی‌هایی در View میسر می‌شوند:
 @Test()
<br />
@message


معرفی inject@

توسط دایرکتیو جدید inject@، یک خاصیت عمومی به ASP.NET Core اعلام می‌شود و سپس مقدار دهی آن بر اساس تنظیمات IoC Container برنامه به صورت خودکار صورت خواهد گرفت. برای مثال زمانیکه می‌خواهیم به سرویس توکار HostingEnvironment در یک  View دسترسی پیدا کنیم، می‌توان در ابتدای آن نوشت:
 @inject Microsoft.AspNetCore.Hosting.IHostingEnvironment Host;
در این حالت کد فوق از دیدگاه ASP.NET Core به صورت ذیل تفسیر می‌شود:
 [Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public Microsoft.AspNetCore.Hosting.IHostingEnvironment Host { get; private set; }
این خاصیت عمومی نیز با توجه به از پیش ثبت شده بودن سرویس IHostingEnvironment  و مشخص شدن توسط RazorInjectAttribute، توسط IoC Container آن شناسایی شده و تامین می‌شود.
اکنون برای استفاده‌ی از آن خواهیم داشت:
 <div>
 Running in @Host.EnvironmentName
</div>
البته استفاده‌ی از inject@ شاید به نوعی سؤ استفاده‌ی از الگوی MVC به شما رود؛ از این جهت که اطلاعات مورد نیاز یک View، باید از طریق کنترلر آن تامین شود و خود View نباید به صورت مستقیم درخواست تامین آ‌ن‌ها را بدهد. اما باید دقت داشت که در نهایت View نیاز دارد تا کدها را اجرا کرده و خروجی را تولید کند و برای این منظور، در پشت صحنه سرویس‌های زیادی مانند IUrlHelper ، IViewComponentHelper ، IHtmlHelper و غیره به همین ترتیب در اختیار آن قرار می‌گیرند. به علاوه استفاده‌ی از تزریق وابستگی‌ها بهتر است از روش ارث بری صفحات پایه، از این جهت که انتخاب composition همواره مقدم است بر inheritance و سبب انعطاف پذیری بیشتری نسبت به قبل می‌گردد. داشتن یک صفحه‌ی پایه که بتواند تمام نیازهای انواع و اقسام Viewها را تامین کند، دور از انتظار و گاهی از اوقات، سبب سنگینی بیش از حد پردازش تمام Viewها خواهد شد. اما تزریق سرویس‌هایی اینچنینی جهت تامین نیازهای اولیه و تکراری یک یا چند View خاص، کل برنامه را سنگین نکرده و همچنین انعطاف پذیری بیشتری را در جهت تامین آن‌ها فراهم می‌کند.
به علاوه باید دقت داشت اگر تعریف inject@ فوق را در فایل view import قرار دهیم، این سرویس در اختیار تمام Viewهای برنامه قرار خواهد گرفت و دیگر نیازی به قرار دادن آن در یک کلاس پایه‌ی سفارشی نیست.
یکی از مفیدترین استفاده‌های از قابلیت تزریق سرویس‌ها در Viewها می‌تواند دسترسی به سرویس تامین تنظیمات برنامه باشد (که در مورد نحوه‌ی تامین آن در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» بیشتر بحث شد):
 @inject IOptions<SmtpConfig> Settings;
نظرات مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
یک نکته‌ی تکمیلی: دات نت 6 و معرفی یک تایمر Async جدید

در این مطلب برای انجام کارهای پس زمینه‌ای متناوب و async، مجبور به اختراع تایمرهای خاصی شدیم که در دات نت 6، روش بهتری برای انجام آن ارائه شده‌است. تا پیش از دات نت 6، تایمرهای زیر در فضاهای نام مختلفی تعریف شده‌اند:

- System.Threading.Timer
- System.Timers.Timer
- System.Windows.Forms.Timer
- System.Web.UI.Timer
- System.Windows.Threading.DispatcherTimer

طراحی تمام این تایمرها مبتنی بر callbackها است و رخ‌دادهایی که توسط تایمر، در زمان مشخصی صادر می‌شوند. این تایمرها مشکلات زیر را به همراه دارند:
1- متد callback فراخوانی شده async نیست (زمانی طراحی شده بودند که نوع Task، هنوز وجود خارجی نداشت).
2- اگر درون callback خطایی رخ‌دهد، خاموش سازی تایمر نیاز به عملیات اضافه‌تری دارد.
3- اگر عملیات درون یک callback هنوز به پایان نرسیده باشد، ممکن است این callback مجددا فراخوانی شود.

برای رفع تمام این مشکلات، تایمر جدیدی به نام PeriodicTimer به دات نت 6 اضافه شده‌است که این مزایا را به همراه دارد:
1- تمام async است.
2- تنها یک جریان کاری مشخص را دارد که با فراخوانی دستی متد WaitForNextTickAsync آن، به میزان بازه‌ی زمانی مشخص شده، صبر خواهد شد. وجود تنها یک جریان کاری، مشکلات 2 و 3 تایمرهای قبلی را رفع می‌کند.

یک مثال:
private async Task DoTaskAsync()
{
   using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));   
   while (await timer.WaitForNextTickAsync())
   {
      Console.WriteLine($"Firing at {DateTime.Now}");
   }   
}
این تایمر async، هر 5 ثانیه یکبار، کدهای بدنه‌ی حلقه را اجرا می‌کند.

اگر خواستیم این تایمر، پس از 20 ثانیه به طور کامل متوقف شود، روش کار به صورت زیر است که توسط یک CancellationTokenSource که به عنوان پارامتر متد WaitForNextTickAsync ارسال می‌شود، قابل پیاده سازی است:
private async Task DoTaskAsync()
{
   try
   {
      var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));

      using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));   
      while (await timer.WaitForNextTickAsync(cts.Token))
      {
         Console.WriteLine($"Firing at {DateTime.Now}");
      }   
   }
   catch (OperationCanceledException)
   {
       Console.WriteLine("Operation cancelled");
   }
}
این خاتمه‌ی خودکار، با صدور یک OperationCancelled exception رخ خواهد داد و یا حتی می‌توان متد ()cts.Cancel را نیز در صورت نیاز به صورت دستی در داخل حلقه فراخوانی کرد تا عملیات خاتمه یابد.
مطالب
بررسی Bad code smell ها: کلاس بزرگ
این نوع کد بد بو در دسته بندی «کدهای متورم» قرار می‌گیرد. یکی از نتایج متورم شدن کدها، سخت شدن نگهداری آنهاست. بدیهی به نظر می‌رسد که نگهداری و اعمال تغییرات بر روی یک کلاس بزرگ، دشوار و زمان گیر خواهد بود. علارغم سادگی مفهوم این نوع کد بد بو، این مورد یکی از موارد پر تکرار درمحصول‌ها است.  
کلاس بزرگ کلاسی است که تعداد اعضای آن (فیلد، خصوصیت، متد) زیاد باشند و تعداد خطوط کد زیادی نیز داشته باشد. 

چرا چنین بویی به راه می‌افتد 

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

نشانه‌های این کد بد بو 

نشانه‌هایی که به تشخیص یک کلاس بزرگ کمک می‌کنند به صورت زیر هستند: 
  • تعداد خطوط زیاد: این معیار نسبت به فناوری و زبان برنامه نویسی مورد استفاده درمحصول متفاوت است؛ ولی در حالت کلی زمانیکه یک کلاس تعداد خطوط کدی بیشتر از 100 داشت، مشکلی بوجود آمده است. 
  • تعداد وضعیت‌های داخلی (در تعریف شیء گرایی) زیاد در یک کلاس، نشان دهنده بزرگی یک کلاس هستند.  
  • تعداد پارامترهای زیاد سازنده کلاس نشان دهنده متورم شدن کلاس هستند. معمولا مدیریت کردن تعداد وضعیت‌های داخلی زیاد منجر به دریافت تعداد زیاد پارامتر ورودی در سازنده می‌شوند. اگر قانون مربوط به تعداد پارامترهای یک متد را در نظر داشته باشیم و با فرض اینکه سازنده نیز یک متد است، حداکثر پارامترهای مناسب برای یک سازنده 4 خواهد بود. 
  • متغیرهایی وجود دارند که به صورت دسته‌ای پیشوند یا پسوند خاصی دارند. این پیشوندها یا پسوندها نشان دهنده مواردی هستند که احتمالا می‌توانند به کلاس مخصوص به خود انتقال داده شوند. زیرا از نظر منطقی ارتباطی بین آنها وجود دارد و مربوط به کلاس فعلی نمی‌شوند (زیرا اگر اینگونه بود نیازی به پیشوند یا پسوند نبود).


مشکل این کد بد بو چیست؟ 

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


چگونه این بو را رفع کنیم؟  

دیدگاه کلی برای رفع چنین بویی تقسیم مسئولیت‌های موجود در یک کلاس بزرگ است. این تقسیم می‌تواند به صورت‌های زیر انجام شود:
  • ایجاد کلاسی مستقل برای هریک از مسئولیت‌های موجود در کلاس بزرگ 
  • ایجاد کلاسی پایه (Base class) برای انجام برخی از امور مشترک در کلاس 


جمع بندی 

یکی از نکات مهم در مورد انواع کد بد بو متعلق به دسته کدهای متورم، توجه دایمی به کدهای نوشته شده در محصول است. زیرا کدهای متورم به مرور زمان و به آرامی ایجاد می‌شوند و معمولا توجه کافی به آنها نمی‌شود.  
پاسخ به بازخورد‌های پروژه‌ها
توضیح گام های اجرا شده در پروژه
سلام؛
هدف از انجام این پروژه برای من چسباندن قطعات مختلف یک پازل به هم بودند تا بتوان به یک تصویر خوب رسید.منظور من این است که entity framework و ASP.NET MVC و bootstrap و best practice‌های آن‌ها به تنهایی و جدا از هم به نظر ساده و راحت و خوب بیایند، اما درگیر شدن همه‌ی آن‌ها در یک پروژه‌ی واقعی، واقعا چالش بر انگیز است.
من دانشجو هستم و تقریبا استارت این پروژه را از آبان ماه زدم، اما به دلیل یک سری مشکلات از جمله همین دانشجو بودن، کار به کندی پیش رفت و حتی وقفه‌های چند ماهه در آن پیش اومد. هدف من این بود که اساسا یک سیستم با کیفیت بنویسم و در ابتدای کار هم، کار به خوبی پیش می‌رفت، اما با توجه به مشکلات ذکر شده، عمده کار کدنویسی در تعطیلات عید نوروز صورت گرفت، و کاملا از کدنویسی انجام شده مشهود است ک ههمان قسمت هایی که در عید نوروز کدنویسی شده اند، اصطلاحا سرهم بندی شده اند( به خصوص در کدهای سمت کلاینت)
در مورد گام‌های انجام شده؛ پروژه به این منوال انجام شد:
- تحلیل ساختار بانک اطلاعاتی مورد نیاز
- شروع به تحقیق در مورد امکانات مورد نیاز 
- دعوت همکای برای کار گروهی توسط دوستان ( کسی قبول نکرد البته دی:)
- با توجه به محدودیت‌های یافت شده در تحقیقات، ساختار بانک اطلاعاتی نهایی می‌شود.
- انتخاب فریم ورک‌های مناسب( که در اینجا Entity Framework برای orm و ASP.NET MVC برای کدنویسی سمت سرور و bootstrap برای css و jquery هم برای جاوا اسکریپت)
- تحقیق در مورد best practice‌های موجود در مورد هر یک از فریم ورک‌های فوق
-شروع کدنویسی
در مورد قسمت مدیریت کاربران، هدف طراحی یک سیستم خیلی منعطف بود که قطعا با memebrship خود دات نت امکان پذیر نبود. متاسفانه به دلیل مشکلات پیش اومده این قسمت از پروژه هم سرهم بندی کردم و به یک سیستم ساده اکتفا کردم.
برای پیاده سازی آن هم شما کافیست در گوگل عبارت implement custom membership in asp.net mvc را سرچ کنید. مطمئن باشید کلی مطلب پیدا خوهید کرد که با جمع بندی آن یک سیستم خوب می‌توانید پیاده سازی کنید.
الان همین سیستم پیاده سازی شده در سایت یک باگ دارد که بعد از مدتی remember me آ از کار می‌افتد.کوکی کاربر اعتبار دارد، اما رویداد متناظر آن برای اعتبار سنجی اتفاق نمی‌افتد!
الان هم در حال تحقیق برای پیاده سازی یک سیستم اعتبارسنجی  کامل‌تر و اصولی‌تر و یک پارچه‌تر با ASP.NET MVC  هستم که مقاله‌ی زیر خیلی به من کمک کرد.(امیدوارم برای شما هم مفید باشد)
الان هم برنامه ای برای ارتقا این سیستم دارم و مهمترین تغییر آن را می‌توان به استفاده از angularjs برای نوشتن بخش مدیریتی و پیاده سازی آن به صورت single page دانست.( البته اگر این کمردرد بزاره دی:)
امیدوارم دوستان با بازخوردهای خوب خودشون، در ارتقای سطح کیفی کار کمک کنند.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دهم - کار با فرم‌ها - قسمت اول
هر برنامه‌ی وبی، نیاز به کار با فرم‌های وب را دارد و به همین جهت، AngularJS 2.0 به همراه دو نوع از فرم‌ها است: فرم‌های مبتنی بر قالب‌ها و فرم‌های مبتنی بر مدل‌ها.
کار با فرم‌های مبتنی بر قالب‌ها ساده‌تر است؛ اما کنترل کمتری را بر روی مباحث اعتبارسنجی داده‌های ورودی توسط کاربر، در اختیار ما قرار می‌دهند. اما فرم‌های مبتنی بر مدل‌ها هر چند به همراه اندکی کدنویسی بیشتر هستند، اما کنترل کاملی را جهت اعتبارسنجی ورودی‌های کاربران، ارائه می‌دهند. در این قسمت فرم‌های مبتنی بر قالب‌ها (Template-driven forms) را بررسی می‌کنیم.


ساخت فرم مبتنی بر قالب‌های ثبت یک محصول جدید

در ادامه‌ی مثال این سری، می‌خواهیم به کاربران، امکان ثبت اطلاعات یک محصول جدید را نیز بدهیم. به همین جهت فایل‌های جدید product-form.component.ts و product-form.component.html را به پوشه‌ی App\products برنامه اضافه می‌کنیم (جهت تعریف کامپوننت فرم جدید به همراه قالب HTML آن).
الف) محتوای کامل product-form.component.html
<form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">
                Add Product
            </h3>
        </div>
        <div class="panel-body form-horizontal">
            <div class="form-group">
                <label for="productName" class="col col-md-2 control-label">Name</label>
                <div class="controls col col-md-10">
                    <input ngControl="productName" id="productName" required
                           #productName="ngForm"
                           (change)="log(productName)"
                           minlength="3"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productName"/>
                    <div *ngIf="productName.touched && productName.errors">
                        <label class="text-danger" *ngIf="productName.errors.required">
                            Name is required.
                        </label>
                        <label class="text-danger" *ngIf="productName.errors.minlength">
                            Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.
                        </label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="productCode" class="col col-md-2 control-label">Code</label>
                <div class="controls col col-md-10">
                    <input ngControl="productCode" id="productCode" required
                           #productCode="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productCode"/>
                    <label class="text-danger" *ngIf="productCode.touched && !productCode.valid">
                        Code is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="releaseDate" class="col col-md-2 control-label">Release Date</label>
                <div class="controls col col-md-10">
                    <input ngControl="releaseDate" id="releaseDate" required
                           #releaseDate="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.releaseDate"/>
                    <label class="text-danger" *ngIf="releaseDate.touched && !releaseDate.valid">
                        Release Date is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="price" class="col col-md-2 control-label">Price</label>
                <div class="controls col col-md-10">
                    <input ngControl="price" id="price" required
                           #price="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.price"/>
                    <label class="text-danger" *ngIf="price.touched && !price.valid">
                        Price is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="description" class="col col-md-2 control-label">Description</label>
                <div class="controls col col-md-10">
                    <textarea ngControl="description" id="description" required
                              #description="ngForm"
                              rows="10" type="text" class="form-control"
                              [(ngModel)]="productModel.description"></textarea>
                    <label class="text-danger" *ngIf="description.touched && !description.valid">
                        Description is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="imageUrl" class="col col-md-2 control-label">Image</label>
                <div class="controls col col-md-10">
                    <input ngControl="imageUrl" id="imageUrl" required
                           #imageUrl="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.imageUrl"/>
                    <label class="text-danger" *ngIf="imageUrl.touched && !imageUrl.valid">
                        Image is required.
                    </label>
                </div>
            </div>
        </div>
        <footer class="panel-footer">
            <button [disabled]="!f.valid"
                    type="submit" class="btn btn-primary">
                Submit
            </button>
        </footer>
    </div>
</form>

ب) محتوای کامل product-form.component.ts
import { Component } from 'angular2/core';
import { Router } from 'angular2/router';
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
    //selector: 'product-form',
    templateUrl: 'app/products/product-form.component.html'
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    log(productName): void {
        console.log(productName);
    }
 
    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}

اکنون ریز جزئیات و تغییرات این دو فایل را قدم به قدم بررسی خواهیم کرد.

تا اینجا در فایل product-form.component.html یک فرم ساده‌ی HTML ایی مبتنی بر بوت استرپ 3 را تهیه کرده‌ایم. نکات ابتدایی آن، دقیقا مطابق است با مستندات بوت استرپ 3؛ از لحاظ تعریف form-horizontal و سپس ایجاد یک div با کلاس form-group و قرار دادن المان‌هایی با کلاس‌های form-control در آن. همچنین برچسب‌های تعریف شده‌ی با ویژگی for، در این المان‌ها، جهت بالارفتن دسترسی پذیری به عناصر فرم، اضافه شده‌اند. این مراحل در مورد تمام فرم‌های استاندارد وب صادق هستند و نکته‌ی جدیدی ندارند.

در ادامه تعاریف AngularJS 2.0 را به این فرم اضافه کرد‌ه‌ایم. در اینجا هر کدام از المان‌های ورودی، تبدیل به Controlهای AngularJS 2.0 شده‌اند. کلاس Control، خواص ویژه‌ای را در اختیار ما قرار می‌دهد. برای مثال value یا مقدار این المان چیست؟ وضعیت touched و untouched آن چیست؟ (آیا کاربر فوکوس را به آن منتقل کرده‌است یا خیر؟) آیا dirty است؟ (مقدار آن تغییر کرده‌است؟) و یا شاید هم pristine است؟ (مقدار آن تغییری نکرده‌است). علاوه بر این‌ها دارای خاصیت valid نیز می‌باشد (آیا اعتبارسنجی آن موفقیت آمیز است؟)؛ به همراه خاصیت errors که مشکلات اعتبارسنجی موجود را باز می‌گرداند.
<div class="form-group">
    <label for="description" class="col col-md-2 control-label">Description</label>
    <div class="controls col col-md-10">
        <textarea ngControl="description" id="description" required
                  #description="ngForm"
                  rows="10" type="text" class="form-control"
                  [(ngModel)]="productModel.description"></textarea>
        <label class="text-danger" *ngIf="description.touched && !description.valid">
            Description is required.
        </label>
    </div>
</div>
در اینجا کلاس مفید دیگری به نام ControlGroup نیز درنظر گرفته شده‌است. برای مثال هر فرم، یک ControlGroup است (گروهی متشکل از کنترل‌ها، در صفحه). البته می‌توان یک فرم بزرگ را به چندین ControlGroup نیز تقسیم کرد. تمام خواصی که برای کلاس Control ذکر شدند، در مورد کلاس ControlGroup نیز صادق هستند. با این تفاوت که این‌بار اگر به خاصیت valid آن مراجعه کردیم، یعنی تمام کنترل‌های قرار گرفته‌ی در آن گروه معتبر هستند و نه صرفا یک تک کنترل خاص. به همین ترتیب خاصیت errors نیز تمام خطاهای اعتبارسنجی یک گروه را باز می‌گرداند.
هر دو کلاس Control و ControlGroup از کلاس پایه‌ای به نام AbstractControl مشتق شده‌اند و این کلاس پایه است که خواص مشترک یاد شده را به همراه دارد.

بنابراین برای کار ساده‌تر با یک فرم AngularJS 2.0، کل فرم را تبدیل به یک ControlGroup کرده و سپس هر کدام از المان‌های ورودی را تبدیل به یک Control مجزا می‌کنیم. کار برقراری این ارتباط، با استفاده از دایرکتیو ویژه‌ای به نام ngControl انجام می‌شود. بنابراین دایرکتیو ngControl، با نامی دلخواه و معین، به تمام المان‌های ورودی، انتساب داده شده‌است.
هرچند در این مثال نام ngControl‌ها با مقدار id هر کنترل یکسان درنظر گرفته شده‌است، اما ارتباطی بین این دو نیست. مقدار id جهت استفاده‌ی در DOM کاربرد دارد و مقدار ngControl توسط AngularJS 2.0 استفاده می‌شود. جهت رسیدن به کدهایی یکدست، بهتر است این نام‌ها را یکسان درنظر گرفت؛ اما هیچ الزامی هم ندارد.

برای بررسی جزئیات این اشیاء کنترل، در المان productName، یک متغیر محلی را به نام productName# تعریف کرده‌ایم و آن‌را به دایرکتیو ngControl انتساب داده‌ایم. این انتساب توسط ngForm انجام شده‌است. زمانیکه AngularJS 2.0 یک متغیر محلی تنظیم شده‌ی به ngForm را مشاهده می‌کند، آن‌را به صورت خودکار به ngControl همان المان ورودی متصل می‌کند. سپس این متغیر محلی را به متد log ارسال کرده‌ایم. این متد در کلاس کامپوننت جاری تعریف شده‌است و کار آن نمایش شیء Control جاری در کنسول developer tools مرورگر است.
<input ngControl="productName" id="productName" required
       #productName="ngForm"
       (change)="log(productName)"
       minlength="3"
       type="text" class="form-control"
       [(ngModel)]="productModel.productName"/>


همانطور که در تصویر مشاهده می‌کنید، عناصر یک شیء Control، در کنسول نمایش داده شده‌اند و در اینجا بهتر می‌توان خواصی مانند valid و امثال آن‌را که به همراه این کنترل وجود دارند، مشاهده کرد. برای مثال خاصیت dirty آن true است چون مقدار آن المان ورودی، تغییر کرده‌است.

بنابراین تا اینجا با استفاده از دایرکتیو ngControl، یک المان ورودی را به یک شیء Control متصل کردیم. همچنین نحوه‌ی تعریف یک متغیر محلی را در المانی و سپس ارسال آن را به کلاس متناظر با کامپوننت فرم، نیز بررسی کردیم.


افزودن اعتبارسنجی به فرم ثبت محصولات

به کنترل‌هایی که به صورت فوق توسط ngControl ایجاد می‌شوند، اصطلاحا implicitly created controls می‌گویند؛ یا به عبارتی ایجاد آن‌ها به صورت «ضمنی» توسط AngularJS 2.0 انجام می‌شود که نمونه‌ای از آن‌را در تصویر فوق نیز مشاهده کردید. این نوع کنترل‌های ضمنی، امکانات اعتبارسنجی محدودی را در اختیار دارند؛ که تنها سه مورد هستند:
الف) required
ب) minlength
ج) maxlength

این‌ها ویژگی‌های استاندارد اعتبارسنجی HTML 5 نیز هستند. نمونه‌ای از اعمال این موارد را با افزودن ویژگی required، به المان‌های فرم ثبت محصولات فوق، مشاهده می‌کنید.
سپس نیاز داریم تا خطاهای اعتبارسنجی را در مقابل هر المان ورودی نمایش دهیم.
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"></textarea>
<div class="alert alert-danger" *ngIf="description.touched && !description.valid">
    Description is required.
</div>
پس از افزودن ویژگی required به یک المان، افزودن و نمایش خطاهای اعتبارسنجی، شامل سه مرحله‌ی زیر است:
الف) ایجاد یک div ساده جهت نمایش پیام خطای اعتبار سنجی
ب) افزودن یک متغیر محلی با # و تنظیم شده‌ی به ngForm، جهت دسترسی به شیء کنترل ایجاد شده
ج) استفاده از این متغیر محلی در دایرکتیو ساختاری ngIf* جهت دسترسی به خاصیت valid آن کنترل. بر مبنای مقدار این خاصیت است که تصمیم گرفته می‌شود، پیام اجباری بودن پر کردن فیلد نمایش داده شود یا خیر.
در اینجا یک سری کلاس بوت استرپ 3 هم جهت نمایش بهتر این پیام خطای اعتبارسنجی، اضافه شده‌اند.

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



بهبود شیوه نامه‌ی پیش فرض المان‌های ورودی اطلاعات در AngularJS 2.0

می‌خواهیم اگر اعتبارسنجی یک المان ورودی با شکست مواجه شد، یک حاشیه‌ی قرمز، در اطراف آن نمایش داده شود. این مورد را با توجه به اینکه AngularJS 2.0، شیوه نامه‌های ویژه‌ای را به صورت خودکار به المان‌ها اضافه می‌کند، می‌توان به صورت سراسری به تمام فرم‌ها اضافه کرد. برای این منظور فایل app.component.css واقع در ریشه‌ی پوشه‌ی app را گشوده و تنظیمات ذیل را به آن اضافه کنید:
.ng-touched.ng-invalid{
    border: 1px solid red;
}

ویژگی‌های اضافه شده‌ی در حالت شکست اعتبارسنجی؛ مانند ng-invalid


ویژگی‌های اضافه شده‌ی در حالت موفقیت اعتبارسنجی؛ مانند ng-valid



مدیریت چندین ویژگی اعتبارسنجی یک المان با هم

گاهی از اوقات نیاز است برای یک المان ورودی، چندین نوع اعتبارسنجی مختلف را تعریف کرد. برای مثال فرض کنید که ویژگی‌های required و همچنین minlength، برای نام محصول تنظیم شده‌اند. در این حالت ذکر productName.valid خیلی عمومی است و هر دو حالت اجباری بودن فیلد و حداقل طول آن‌را با هم به همراه دارد:
<div class="alert alert-danger" *ngIf="productName.touched && !productName.valid">
   Name is required.
</div>
بنابراین در این حالت از روش ذیل استفاده می‌شود:
<div *ngIf="productName.touched && productName.errors">
    <div class="alert alert-danger" *ngIf="productName.errors.required">
        Name is required.
    </div>
    <div class="alert alert-danger" *ngIf="productName.errors.minlength">
        Name should be minimum 3 characters.
    </div>
</div>
خاصیت errors نیز یکی دیگر از خواص شیء کنترل است. اگر نال بود، یعنی خطایی وجود ندارد و در غیراینصورت، به ازای هر نوع اعتبارسنجی تعریف شده، خواصی به آن اضافه می‌شوند. بنابراین ذکر productName.errors.required به این معنا است که آیا خاصیت errors، دارای کلیدی به نام required است؟ اگر بله، یعنی این فیلد هنوز پر نشده‌است.
همچنین چون در این حالت productName.touched نیاز است چندین بار تکرار شود، می‌توان آن‌را در یک div محصور کننده‌ی دو div مورد نیاز جهت نمایش خطاهای اعتبارسنجی قرار داد. به علاوه بررسی نال نبودن productName.errors نیز در div محصور کننده صورت گرفته‌است و دیگر نیازی نیست این بررسی را به ngIfهای داخلی اضافه کرد.

نکته 1
اگر علاقمند بودید تا جزئیات خاصیت errors را مشاهده کنید، آن‌را می‌توان توسط pipe توکاری به نام json به صورت موقت نمایش داد و بعد آن‌را حذف کرد:
 <div *ngIf="productName.touched && productName.errors">
  {{ productName.errors | json }}

نکته 2
بجای ذکر مستقیم عدد سه در «minimum 3 characters»، می‌توان این عدد را مستقیما از تعریف ویژگی minlength نیز استخراج کرد:
 Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.


بررسی ngForm

شبیه به ngControl که یک المان ورودی را به یک کنترل AngularJS 2.0 متصل می‌کند، دایرکتیو دیگری نیز به نام ngForm وجود دارد که کل فرم را به شیء ControlGroup بایند می‌کند و برخلاف ngControl، نیازی به ذکر صریح آن وجود ندارد. هر زمانیکه AngularJS 2.0، المان استاندارد فرمی را در صفحه مشاهده می‌کند، این اتصالات را به صورت خودکار برقرار خواهد کرد.
ngForm دارای خاصیتی است به نام ngSumbit که از نوع EventEmitter است (نمونه‌ای از آن را در مبحث کامپوننت‌های تو در تو پیشتر ملاحظه کرده‌اید). بنابراین از آن می‌توان جهت اتصال رخداد submit فرم، به متدی در کلاس کامپوننت خود، استفاده کرد. متد متصل به این رخداد، زمانی فراخوانی می‌شود که کاربر بر روی دکمه‌ی submit کلیک کند:
 <form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
همچنین در اینجا متغیر محلی f جهت دسترسی به شیء ControlGroup و ارسال آن به متد onSubmit تعریف شده‌است (شبیه به متغیرهای محلی دسترسی به ngControl که پیشتر جهت نمایش خطاهای اعتبارسنجی، اضافه کردیم).

پس از تعریف این رخداد و اتصال آن در قالب کامپوننت، اکنون می‌توان متد onSubmit را در کلاس آن نیز اضافه کرد.
onSubmit(form): void {
   console.log(form);
}
فعلا هدف از این متد، نمایش جزئیات شیء form دریافتی، در کنسول developer tools است.



غیرفعال کردن دکمه‌ی submit در صورت وجود خطاهای اعتبارسنجی

در قسمت بررسی ngForm، یک متغیر محلی را به نام f ایجاد کردیم که به شیء ControlGroup فرم جاری اشاره می‌کند. از این متغیر و خاصیت valid آن می‌توان با کمک property binding به خاصیت disabled یک دکمه، آن‌را به صورت خودکار فعال یا غیرفعال کرد:
<button [disabled]="!f.valid"
        type="submit" class="btn btn-primary">
    Submit
</button>
هر زمانیکه کل فرم از لحاظ اعتبارسنجی مشکلی نداشته باشد، دکمه‌ی submit فعال می‌شود و برعکس.



نمایش فرم افزودن محصولات توسط سیستم Routing

با نحوه‌ی تعریف مسیریابی‌ها در قسمت قبل آشنا شدیم. برای نمایش فرم افزودن محصولات، می‌توان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before...
import { ProductFormComponent }  from './products/product-form.component';
 
@Component({
    //same as before…
    template: `
                //same as before…
                    <li><a [routerLink]="['AddProduct']">Add Product</a></li>
               //same as before…
    `,
    //same as before…
})
@RouteConfig([
    //same as before…
    { path: '/addproduct', name: 'AddProduct', component: ProductFormComponent }
])
//same as before...
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن محصولات اضافه شده‌است. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شده‌است.



اتصال المان‌های فرم به مدلی جهت ارسال به سرور

برای اتصال المان‌های فرم به یک مدل، این مدل را به صورت یک خاصیت عمومی، در سطح کلاس کامپوننت فرم، تعریف می‌کنیم:
 productModel = <IProduct>{}; // creates an empty object of an interface
اگر از اینترفیسی مانند IProduct که در قسمت‌های قبل این سری تعریف شد، نیاز است شیء جدیدی ساخته شود، الزاما نیازی نیست تا یک کلاس جدید را از آن مشتق کرد و بعد متغیر new ClassName را تهیه کرد. در TypeScript می‌توان به صورت خلاصه از syntax فوق نیز استفاده کرد.
پس از تعریف خاصیت productModel، اکنون کافی است با استفاده از two-way data binding، آن‌را به المان‌های فرم نسبت دهیم. برای مثال:
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"
          [(ngModel)]="productModel.description"></textarea>
در اینجا با استفاده از ngModel و انقیاد دو طرفه، کار اتصال به خاصیت توضیحات شیء محصول انجام شده‌است. اکنون بلافاصله تغییرات اعمالی به فرم، به مدل متناظر منعکس می‌شود و برعکس. این ngModel را به تمام المان‌های ورودی فرم متصل خواهیم کرد.
پس از تعریف یک چنین اتصالی، دیگر نیازی به مقدار دهی پارامتر onSubmit(f.form) نیست. زیرا شیء productModel، در متد onSumbit در دسترس است و این شیء همواره حاوی آخرین تغییرات اعمالی به المان‌های فرم است.

پس از اینکه فرم را به مدل آن متصل کردیم، فایل product.service.ts را گشوده و متد جدید addProduct را به آن اضافه کنید:
addProduct(product: IProduct): Observable<IProduct> {
    let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
    return this._http.post(this._addProductUrl, JSON.stringify(product), options)
        .map((response: Response) => <IProduct>response.json())
        .do(data => console.log("Product: " + JSON.stringify(data)))
        .catch(this.handleError);
}
کار این متد، ارسال شیء محصول به یک اکشن متد برنامه‌ی ASP.NET MVC جاری است. با جزئیات کار با obsevables درمطلب «دریافت اطلاعات از سرور» پیشتر آشنا شده‌ایم.
نکته‌ی مهم اینجا است که content type پیش فرض ارسالی متد post آن، plain text است و در این حالت ASP.NET MVC شیء JSON دریافتی از کلاینت را پردازش نخواهد کرد. بنابراین نیاز است تا هدر content type را به صورت صریحی در اینجا ذکر نمود؛ در غیراینصورت در سمت سرور، شاهد نال بودن مقادیر دریافتی از کاربران خواهیم بود.
امضای سمت سرور متد دریافت اطلاعات از کاربر، چنین شکلی را دارد (تعریف شده در فایل Controllers\HomeController.cs):
 [HttpPost]
public ActionResult AddProduct(Product product)
{

اشیاء هدرها و تنظیمات درخواست، در متد addProduct سرویس ProductService، در ماژول‌های ذیل تعریف شده‌اند که باید به ابتدای فایل product.service.ts اضافه شوند:
 import { Headers, RequestOptions } from 'angular2/http';

پس از تعریف متد addProduct در سرویس ProductService، اکنون با استفاده از ترزیق این سرویس به سازنده‌ی کلاس فرم ثبت یک محصول جدید، می‌توان متد this._productService.addProduct را جهت ارسال productModel به سمت سرور، در متد onSubmit فراخوانی کرد:
//Same as before…
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
//Same as before…
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    //Same as before… 

    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}
همانطور که ذکر شد، از آنجائیکه شیء productModel حاوی آخرین تغییرات اعمالی توسط کاربر است، اکنون می‌توان پارامتر form متد onSubmit را حذف کرد.
در اینجا پس از فراخوانی متد addProduct، متد subscribe، در انتهای زنجیره، فراخوانی شده‌است. کار آن هدایت کاربر به صفحه‌ی نمایش لیست محصولات است. در اینجا this._router از طریق تزریق وابستگی‌های سرویس مسیریاب به سازنده‌ی کلاس، تامین شده‌است. نمونه‌ی آن‌را در قسمت «افزودن دکمه‌ی back با کدنویسی» مربوطه به مطلب آشنایی با مسیریابی، پیشتر مطالعه کرده‌اید.



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


خلاصه‌ی بحث

فرم‌های template driven در AngularJS 2.0 به این نحو طراحی می‌شوند:
 1) ابتدا فرم HTML را به حالت معمولی آن طراحی می‌کنیم؛ با تمام المان‌های آن.
 2) به تمام المان‌های فرم، دیراکتیو ngControl را متصل می‌کنیم، تا AngularJS 2.0 آن‌را تبدیل به یک کنترل خاص خودش کند. کنترلی که دارای خواصی مانند valid و touched است.
 3) سپس برای دسترسی به این کنترل ایجاد شده‌ی به صورت ضمنی، یک متغیر محلی آغاز شده‌ی با # را به تمام المان‌ها اضافه می‌کنیم.
 4) اعتبارسنجی‌هایی را مانند required  به المان‌های فرم اضافه می‌کنیم.
 5) از متغیر محلی تعریف شده و ngIf* برای بررسی خواصی مانند valid و touched برای نمایش خطاهای اعتبارسنجی کمک گرفته می‌شود.
 6) پس از تعریف فرم، تعریف ngControlها، تعریف متغیر محلی شروع شده‌ی با # و افزودن خطاهای اعتبارسنجی، اکنون نوبت به ارسال این اطلاعات به سرور است. بنابراین رخداد ngSubmit را باید به متدی در کلاس کامپوننت جاری متصل کرد.
 7) اکنون که با کلیک بر روی دکمه‌ی submit فرم، متد onSubmit متصل به ngSubmit فراخوانی می‌شود، نیاز است بین المان‌های فرم HTML و کلاس کامپوننت، ارتباط برقرار کرد. این‌کار را توسط two-way data binding و تعریف ngModel بر روی تمام المان‌های فرم، انجام می‌دهیم. این ngModelها، به یک خاصیت عمومی که متناظر است با وهله‌ای از شیء مدل فرم، متصل هستند. بنابراین این مدل، در هر لحظه، بیانگر آخرین تغییرات کاربر است و از آن می‌توان برای ارسال اطلاعات به سرور استفاده کرد.
 8) پس از اتصال فرم به کلاس متناظر با آن، اکنون سرویس محصولات را تکمیل کرده و به آن متد HTTP Post را جهت ارسال اطلاعات سمت کاربر، به سرور، اضافه می‌کنیم. در اینجا نکته‌ی مهم، تنظیم content type ارسالی به سمت سرور است. در غیراینصورت فریم ورک سمت سرور قادر به تشخیص JSON بودن این اطلاعات نخواهد شد.