نظرات مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت اول
در مورد موارد مطرحی که باشه یا نه به این راحتی نمیشه نظر داد و باید کد‌ها رو بررسی کرد  ، یا حداقل من نمیشناسم که صرفا فانکشنال باشه قطعا اگه سورس کد هایی که با زبان haskell یا f# یا سایر زبان هایی که پارادایم اون‌ها فانکشنال هست رو نگاه کنیم میتونیم موارد زیادی رو پیدا کنیم

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




در تکمیل این بحث هم بد نیست لایبرری زیر رو ببینید که یه سری اکستنشن برای سی شارپ که بتونید راحت‌تر قوانین فانکشنال رو پیاده کنید ببینید
مطالب
مدیریت حالت در برنامه‌های Blazor توسط الگوی Observer - قسمت اول
نیاز به مدیریت حالت در برنامه‌های Blazor

«حالت» یا state، شیءای است، حاوی اطلاعاتی که برنامه با آن سر و کار دارد. بنابراین مدیریت حالت، روشی است برای ردیابی و مدیریت داده‌های مورد استفاده‌ی در برنامه و تقریبا تمام برنامه‌ها، به نحوی به آن نیاز دارند. هر کامپوننت در Blazor، دارای state خاص خودش است و این state از سایر کامپوننت‌ها کاملا مستقل و ایزوله‌است. این مورد با بزرگ‌تر شدن برنامه و برقراری ارتباط بین کامپوننت‌ها، مشکل ایجاد می‌کند. برای مثال اگر قرار است در منوی بالای سایت، تعداد محصولات موجود در سبد خرید یک شخص را نمایش دهیم، این تعداد، حاصل تعامل او با چندین کامپوننت مجزا خواهد بود که این‌ها الزاما در یک سلسه مراتب هم قرار نمی‌گیرند و به سادگی نمی‌توان اطلاعات را به صورت آبشاری در بین آن‌ها به اشتراک گذاشت. به همین جهت نیاز به روشی برای مدیریت حالت و به اشتراک گذاری آن در بین کامپوننت‌های مختلف برنامه وجود دارد و خوشبختانه چون Blazor به همراه یک سیستم تزریق وابستگی‌های توکار است، پیاده سازی یک چنین مدیریت کننده‌ای، ساده‌است.


استفاده از الگوی Observer جهت مدیریت حالت برنامه‌های Blazor


زمانیکه همانند تصویر فوق با یک کامپوننت کار می‌کنیم، کاربر همواره کارش از تعامل با یک View آغاز می‌شود. این تعامل سبب صدور رخ‌دادهایی می‌شود که این رخ‌دادها، حالت و state کامپوننت را تغییر می‌دهند. تغییر حالت کامپوننت نیز بلافاصله سبب به‌روز رسانی View می‌شود. در این مثال، حالت کامپوننت، داخل همان کامپوننت نگه‌داری می‌شود؛ مانند فیلدهایی که در قسمت code@ یک کامپوننت Blazor تعریف می‌کنیم و محدود به همان کامپوننت هستند.
با بزرگتر شدن برنامه، زمانی خواهد رسید که نیاز است حالت یک کامپوننت را با کامپوننت‌های دیگر به اشتراک گذاشت. در این حالت باید این state را از داخل کامپوننت مدنظر استخراج کرد و در جائی دیگر قرار داد که عموما به آن state store گفته می‌شود:


در تصویر فوق، در بالای آن یک state store را داریم که محل نگه‌داری و ذخیره سازی حالت اشتراکی بین کامپوننت‌ها است. سپس برای نمونه دو کامپوننت دیگر را داریم که رابطه‌ی بین آن‌ها، همان رابطه‌ی مثلثی است که در تصویر اول این مطلب مشاهده کردیم. برای مثال در اثر تعامل کاربری با View کامپوننت 1، رخ‌دادی صادر خواهد شد. مدیریت این رخ‌داد، سبب تغییر state خواهد شد، اما اینبار این state دیگر داخل کامپوننت 1 قرار ندارد؛ بلکه داخل state store است و این store پس از آگاه شدن از تغییر وضعیت خود، دو کامپوننتی را که از آن تغدیه می‌کنند، جهت به روز رسانی Viewهایشان، مطلع می‌کند. همین چرخه در مورد کامپوننت 2 نیز برقرار است. اگر تعاملی با آن صورت گیرد، در نهایت اثر آن به هر دو کامپوننت متصل به state store اشتراکی، اطلاع رسانی می‌شود تا Viewهای هر دوی آن‌ها به روز رسانی شوند. الگویی را که در اینجا مشاهده می‌کنید، در اصل یک الگوی Observer است:


در الگوی مشاهده‌گر، یک Subject را داریم که تعداد زیادی Observer، مشترک آن هستند. در این مثال ما، Subject، همان State Store است و Observerها دقیقا همان کامپوننت‌های مشترک به آن. Observerها به تغییرات Subject گوش فرا داده و بلافاصله بر اساس آن واکنش مناسبی را نشان می‌دهند.


پیاده سازی الگوی Observer جهت مدیریت حالت برنامه‌های Blazor

زمانیکه یک برنامه‌ی متداول Blazor را توسط قالب پیش‌فرض آن ایجاد می‌کنیم، به همراه یک کامپوننت Counter است:
@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
در این مثال فیلد currentCount، همان حالت کامپوننت جاری است که تنها مختص به آن است. اکنون می‌خواهیم این حالت را با کامپوننتی که منوی سمت چپ صفحه را تشکیل می‌دهد (یعنی Client\Shared\NavMenu.razor) به اشتراک گذاشته و با کلیک بر روی دکمه‌ی این شمارشگر، عدد حاصل را علاوه بر View این کامپوننت، در کنار برچسب منوی آن نیز نمایش دهیم.
بنابراین در قدم اول نیاز به یک State Store اشتراکی را داریم که بتوانیم توسط آن، مقدار جاری currentCount را ذخیره کرده و سپس تغییرات آن‌را جهت به روز رسانی دو View (در کامپوننت‌های Counter و NavMenu)، به مشترکین آن اطلاع رسانی کنیم. به همین جهت ابتدا پوشه‌ی جدید Stores را در ریشه‌ی پروژه‌ی Blazor ایجاد می‌کنیم. نام این پوشه، از این جهت یک اسم جمع است که یک برنامه بنابر نیاز خودش می‌تواند چندین State Store را داشته باشد. سپس داخل این پوشه، پوشه‌ی دیگری را به نام CounterStore، ایجاد می‌کنیم.
در اینجا در ابتدا شیء حالت مدنظر را ایجاد می‌کنیم که برای نمونه بر اساس نیاز برنامه و این مثال، از مقدار نهایی کلیک بر روی دکمه‌ی شمارشگر تشکیل می‌شود:
namespace BlazorStateManagement.Stores.CounterStore
{
    public class CounterState
    {
        public int Count { get; set; }
    }
}
از این حالت، در مخزن حالت جدید زیر استفاده خواهیم کرد:
using System;

namespace BlazorStateManagement.Stores.CounterStore
{
    public interface ICounterStore
    {
        void DecrementCount();
        void IncrementCount();
        CounterState GetState();

        void AddStateChangeListener(Action listener);
        void BroadcastStateChange();
        void RemoveStateChangeListener(Action listener);
    }
}

using System;
namespace BlazorStateManagement.Stores.CounterStore
{
    public class CounterStore : ICounterStore
    {
        private readonly CounterState _state = new();
        private Action _listeners;

        public CounterState GetState()
        {
            return _state;
        }

        public void IncrementCount()
        {
            _state.Count++;
            BroadcastStateChange();
        }

        public void DecrementCount()
        {
            _state.Count--;
            BroadcastStateChange();
        }

        public void AddStateChangeListener(Action listener)
        {
            _listeners += listener;
        }

        public void RemoveStateChangeListener(Action listener)
        {
            _listeners -= listener;
        }

        public void BroadcastStateChange()
        {
            _listeners.Invoke();
        }
    }
}
توضیحات:
- مخزن حالت پیاده سازی شده‌ی بر اساس الگوی مشاهده‌گر، نیاز دارد تا بتواند لیست مشاهده‌گرها را ثبت کند. به همین جهت به همراه متدهای AddStateChangeListener جهت ثبت یک مشاهده‌گر جدید و RemoveStateChangeListener، جهت حذف مشاهده‌گری از لیست موجود است.
- همچنین الگوی مشاهده‌گر باید بتواند تغییرات صورت گرفته‌ی در حالتی را که نگه‌داری می‌کند (CounterState در اینجا)، به مشترکین خود اطلاع رسانی کند. اینکار را توسط متد BroadcastStateChange انجام می‌دهد. هر زمانیکه این متد فراخوانی شود، Actionهایی که به صورت پارامتر به متد AddStateChangeListener ارسال شده‌اند، به صورت خودکار اجرا خواهند شد. این کار سبب می‌شود تا بتوان منطق خاصی را مانند به روز رسانی UI، در سمت کامپوننت‌های مشترک به این مخزن، پیاده سازی کرد.
- در اینجا همچنین متدهایی برای افزایش و کاهش مقدار Count را نیز به همراه اطلاع رسانی به مشترکین، مشاهده می‌کنید.

پس از این تعریف نیاز است سرویس Store ایجاد شده را به برنامه معرفی کرد:
namespace BlazorStateManagement.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            //...
            builder.Services.AddScoped<ICounterStore, CounterStore>();
            //...
        }
    }
}
با توجه به اینکه در هر دو حالت Blazor Server و همچنین Blazor Wasm، طول عمر Scoped، دقیقا مانند حالت Singleton عمل می‌کند، سرویس ICounterStore و حالت نگهداری شده‌ی توسط آن، تا پایان عمر برنامه (بسته شدن مرورگر یا ریفرش کامل صفحه‌ی جاری)، در حافظه باقی مانده و وهله سازی مجدد نخواهد شد. به همین جهت تزریق آن در کامپوننت‌های مختلف برنامه، دقیقا حالت مخزن داده‌ی اشتراکی را پیدا خواهد کرد. این مورد یکی از مزیت‌های کار با Blazor است که به همراه یک سیستم تزریق وابستگی‌های توکار است.


تغییر کامپوننت‌های برنامه برای استفاده از سرویس ICounterStore

پس از معرفی سرویس ICounterStore به سیستم تزریق وابستگی‌های برنامه، جهت سهولت استفاده‌ی از آن، در ابتدا فضای نام آن‌را به فایل سراسری Client\_Imports.razor اضافه می‌کنیم:
@using BlazorStateManagement.Stores.CounterStore
سپس تغییرات کامپوننت شمارشگر، جهت استفاده‌ی از سرویس ICounterStore، به صورت زیر خواهند بود:
@page "/counter"
@implements IDisposable

@inject ICounterStore CounterStore

<h1>Counter</h1>

<p>Current count: @CounterStore.GetState().Count</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        CounterStore.AddStateChangeListener(UpdateView);
    }

    private void IncrementCount()
    {
        CounterStore.IncrementCount();
    }

    private void UpdateView()
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        CounterStore.RemoveStateChangeListener(UpdateView);
    }
}
توضیحات:
- در اینجا در ابتدا سرویس ICounterStore، به کامپوننت تزریق شده‌است.
- سپس در متد رویدادگران آغازین OnInitialized، با استفاده از متد AddStateChangeListener، مشترک سرویس مخزن حالت شمارشگر شده‌ایم.
- همواره جهت پاکسازی کد و عدم اشتراک بیش از اندازه‌ی به یک مخزن حالت، نیاز است در پایان کار یک کامپوننت، با پیاده سازی implements IDisposable@، کار حذف اشتراک را انجام دهیم. در غیراینصورت هربار که کامپوننت بارگذاری می‌شود، یک اشتراک جدید از این کامپوننت، به مخزن حالتی که طول عمر Singleton دارد، اضافه خواهد شد که نشانی از نشتی حافظه‌است.
- دو قسمت دیگر را هم تغییر داده‌ایم. اینبار با استفاده از متد ()GetState، این Count اشتراکی را نمایش می‌دهیم و همچنین عمل به روز رسانی State را هم توسط متد IncrementCount انجام داده‌ایم.


در ادامه کامپوننت Client\Shared\NavMenu.razor را نیز جهت نمایش مقدار جاری Count، به صورت زیر به روز رسانی می‌کنیم:
@inject ICounterStore CounterStore

<li class="nav-item px-3">
   <NavLink class="nav-link" href="counter">
      <span class="oi oi-plus" aria-hidden="true"></span> Counter: @CounterStore.GetState().Count
   </NavLink>
</li>

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        CounterStore.AddStateChangeListener(() => StateHasChanged());
    }

    // ...
}
توضیحات:
- در اینجا نیز در ابتدا سرویس ICounterStore، به کامپوننت تزریق شده‌است.
- سپس در متد رویدادگران آغازین OnInitialized، با استفاده از متد AddStateChangeListener، مشترک سرویس مخزن حالت شمارشگر شده‌ایم و هربار که متد BroadcastStateChange ای توسط یکی از کامپوننت‌های متصل به مخزن حالت فراخوانی می‌شود (برای مثال در انتهای متد IncrementCount خود سرویس)، سبب اجرای Action آن که در اینجا StateHasChanged است، خواهد شد. فراخوانی StateHasChanged، کار اطلاع رسانی به UI، جهت رندر مجدد را انجام می‌دهد. به این ترتیب مقدار جدید Count توسط CounterStore.GetState().Count@ در منو نیز ظاهر خواهد شد:




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorStateManagement.zip
مطالب
‫نکات نصب برنامه‌های ASP.NET 4.0 بر روی IIS 6

سه نکته مهم حین توزیع برنامه‌های ASP.NET 4.0 بر روی IIS 6.0 نسبت به سایر نگارش‌های قبلی وجود دارند که باید در نظر گرفته شوند:

الف) پس از اتقای برنامه از نگارش‌های قبلی به دات نت 4 (با فرض اینکه دات نت 4 بر روی سرور نصب است)، پیغام 404 یا به عبارتی فایل مورد نظر بر روی سرور یافت نشد را دریافت می‌کنید (با تمام فایل‌های موجود):
در کنسول IIS ، ذیل قسمت Web Services Extensions ، باید دو مورد از حالت prohibited خارج شوند:
  • All unknown ISAPI extensions
  • ASP.NET 4.0

ب) پس از اجرای برنامه پیغام غیر معتبر بودن تگ‌های جدید فایل Web.Config را ملاحظه می‌کنید:
- در برگه‌ی خواص سایت در IIS 6.0 ، اکنون امکان انتخاب ASP.NET 4.0 هم میسر است که حتما باید این مورد انتخاب گردد (تا دات نت سه و نیم این نام تنها ASP.NET 2.0 بود). در غیر اینصورت تگ‌های جدید فایل Web.Config شناخته نخواهند شد.

ج) بلافاصله پس از اجرای برنامه، پیغام Server Application Unavailable قابل مشاهده است:
نکته‌ی مهم دیگری که به همراه برنامه‌های دات نت 4 باید به آن توجه داشت، ضرورت اجرای آن‌ها در یک پروسه جدید است. پروسه جدید در IIS 6.0 به معنای یک Application pool جدید است. به عبارتی اگر هم اکنون بر روی IIS 6.0 شما برای مثال 2 برنامه‌ی دات نت سه و دات نت 4 قصد استفاده از یک Application pool را داشته باشند، پیغام Server Application Unavailable ظاهر خواهد شد، زیرا نمی‌توان این دو نگارش را تحت یک پروسه اجرا کرد. برای حل این مشکل باید یک Application pool جدید و اختصاصی را جهت برنامه‌ی ASP.Net 4.0 خود تعریف و انتساب دهید.

مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

نحوه نصب و راه اندازی LightInject
در پنجره Package Manager Console می‌توانید با نوشتن دستور ذیل، نسخه باینری آن را نصب کنید که به فایل .dll آن Reference میدهد.

PM> Install-Package LightInject
 همچنین می‌توانید توسط دستور ذیل فایل .cs آن را به پروژه اضافه نمایید. 

PM> Install-Package LightInject.Source

 آماده سازی پروژه نمونه 
قبل از شروع کار با LightInject، یک پروژه Windows Forms Application را با ساختار کلاس‌های ذیل ایجاد نمایید. (در مقالات بعدی و پس از آموزش کامل LightInject نحوه استفاده از آن را در ASP.NET MVC نیز آموزش می‌دهیم)
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Family { get; set; }
        public DateTime Birth { get; set; }
    }

    public interface IRepository<T> where T:class
    {
        void Insert(T entity);
        IEnumerable<T> FindAll();
    }

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

    public class PersonRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPersonService
    {
        void Insert(PersonModel entity);
        IEnumerable<PersonModel> FindAll();
    }

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public PersonService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            _personRepository.Insert(entity);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
PersonModel: ساختار داده ای جدول Person در سمت Application، که در لایه Domain Model ایجاد می‌گردد.
توجه: جهت سهولت تست و تسریع کدنویسی از لایه بندی و از کلاس‌های ViewModel استفاده نکردیم.
IRepository: یک Interface عمومی برای تمامی Interface‌های مربوط به Repository که عملیات مربوط به پایگاه داده مثل بروزرسانی و واکشی اطلاعات را انجام می‌دهند.
IPersonRepository: واسط بین لایه Service و لایه Repository می‌باشد.
PersonRepository: پیاده سازی واقعی عملیات مربوط به پایگاه داده برای PersonModel می‌باشد. به کلاسهایی که حاوی پیاده سازی واقعی کد می‌باشند Concrete Class می‌گویند.
IPersonService: واسط بین رابط کاربری و لایه سرویس می‌باشد. رابط کاربری به جای دسترسی مستقیم به PersonService از IPersonService استفاده می‌کند.
PersonService: دریافت درخواست‌های رابط کاربری و بررسی قوانین تجاری، سپس ارسال درخواست به لایه Repository در صورت صحت درخواست، و در نهایت ارسال پاسخ دریافتی به رابط کاربری. در واقع واسطی بین Repository و UI می‌باشد.
پس از ایجاد ساختار فوق کد مربوط به Form1 را بصورت زیر تغییر دهید.
public partial class Form1 : Form
    {
        private readonly IPersonService _personService;
        public Form1(IPersonService personService)
        {
            _personService = personService;
            InitializeComponent();
        }
    }
توضیحات
در کد فوق به منظور ارتباط با سرویس از IPersonService استفاده نمودیم که به عنوان پارامتر ورودی برای سازنده Form1 تعریف شده است. حتما با Dependency Inversion و انواع Dependency Injection آشنا هستید که به سراغ مطالعه این مقاله آمدید و علت این نوع کدنویسی را هم می‌دانید. بنابراین توضیح بیشتری در این مورد نمی‌دهم.
حال اگر برنامه را اجرا کنید در Program.cs با خطای عدم وجود سازنده بدون پارامتر برای Form1 مواجه می‌شوید که کد آن را باید به صورت زیر تغییر می‌دهیم.
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonRepository, PersonRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
        }
توضیحات
کلاس ServiceContainer وظیفه‌ی Register کردن یک کلاس را برای یک Interface دارد. زمانی که می‌خواهیم Form1 را نمونه سازی نماییم و Application را راه اندازی کنیم، باید نمونه ای را از جنس IPersonService ایجاد نموده و به سازنده‌ی Form1 ارسال نماییم. با رعایت اصل DIP، نمونه سازی واقعی یک کلاس لایه دیگر، نباید در داخل کلاس‌های لایه جاری انجام شود. برای این منظور از شیء container استفاده نمودیم و توسط متد GetInstance، نمونه‌ای از جنس IPersonService را ایجاد نموده و به Form1 پاس دادیم. حال container از کجا متوجه می‌شود که چه کلاسی را برای IPersonService نمونه سازی نماید؟
در خطوط قبلی توسط متد Register، کلاس PersonService را برای IPersonService ثبت نمودیم. container نیز برای نمونه سازی به کلاس هایی که برایش Register نمودیم مراجعه می‌نماید و نمونه سازی را انجام می‌دهد. جهت استفاده از PersonService به پارامتر ورودی IPersonRepository برای سازنده‌ی آن نیاز داریم که کلاس PersonRepository را برای IPersonRepository ثبت کردیم.
حال اگر برنامه را اجرا کنید، به درستی اجرا خواهد شد. برنامه را متوقف کنید و به کد موجود در Program.cs مراجعه نموده و دو خط مربوط به Register را Comment نمایید. سپس برنامه را اجرا کنید و خطای تولید شده را ببینید. این خطا بیان می‌کند که امکان نمونه سازی برای IPersonService را ندارد. چون قبلا هیچ کلاسی را برای آن Register نکرده ایم.
Named Services
در برخی مواقع، بیش از یک کلاس وجود دارند که ممکن است از یک Interface ارث بری نمایند. در این حالت و در زمان Register، باید به ServiceContainer بگوییم که کدام کلاس را باید نمونه سازی نماید. برای بررسی این موضوع، کلاسهای زیر را به ساختار پروژه اضافه نمایید.
    public class WorkerModel:PersonModel
    {
        public ManagerModel Manager { get; set; }
    }

    public class ManagerModel:PersonModel
    {
        public IEnumerable<WorkerModel> Workers { get; set; }
    }

    public class WorkerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class ManagerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public WorkerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var worker = entity as WorkerModel;
            _personRepository.Insert(worker);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public ManagerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var manager = entity as ManagerModel;
            _personRepository.Insert(manager);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
دو کلاس Manager و Worker به همراه سرویس‌ها و Repository هایشان اضافه شده اند که از IPersonService و IPersonRepository مشتق شده اند.
حال کد کلاس Program را به صورت زیر تغییر می‌دهیم
...
 var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
توضیحات
در کد فوق، چون WorkerService بعد از PersonService ثبت یا Register شده است، LightInject در زمان ارسال پارامتر به Form1، نمونه ای از کلاس WorkerService را ایجاد میکند. اما اگر بخواهیم از کلاس PersonService نمونه سازی نماید باید کد را به صورت زیر تغییر دهیم.
...
            container.Register<IPersonService, PersonService>("PersonService");
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
همانطور که مشاهده می‌نمایید، در زمان Register نامی را به آن اختصاص دادیم که در زمان نمونه سازی از این نام استفاده شده است.
اگر در زمان ثبت، نامی را به نمونه‌ی مورد نظر اختصاص داده باشیم، و فقط یک Register برای آن Interface معرفی نموده باشیم، در زمان نمونه سازی، LightInject آن نمونه را به عنوان سرویس پیش فرض در نظر می‌گیرد.
  container.Register<IPersonService, PersonService>("PersonService");
  Application.Run(new Form1(container.GetInstance<IPersonService>()));
در کد فوق، چون برای IPersonService فقط یک کلاس برای نمونه سازی معرفی شده است، با فراخوانی متد GetInstance، حتی بدون ذکر نام، نمونه ای را از کلاس PersonService ایجاد می‌کند.
IEnumerable<T>
زمانی که چند کلاس را که از یک Interface مشتق شده اند، با هم Register می‌نمایید، LightInject این قابلیت را دارد که این کلاس‌های Register شده را در قالب یک لیست شمارشی برگردانید.
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetInstance<IEnumerable<IPersonService>>();
در کد فوق لیستی با دو آیتم ایجاد می‌شود که یک آیتم از نوع PersonService و دیگری از نوع WorkerService می‌باشد. همچنین از کد زیر نیز می‌توانید استفاده کنید:
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetAllInstances<IPersonService>();
به جای متد GetInstance از متد GetAllInstances استفاده شده است.
LightInject از Collection‌های زیر نیز پشتیبانی می‌نماید:
  • Array
  • ICollection<T>
  • IList<T>
  • IReadOnlyCollection<T>
  • IReadOnlyList<T>
Values
توسط LightInject می‌توانید مقادیر ثابت را نیز تعریف کنید
            container.RegisterInstance<string>("SomeValue");
            var value = container.GetInstance<string>();
متغیر value با رشته "SomeValue" مقداردهی می‌گردد. اگر چندین ثابت رشته ای داشته باشید می‌توانید نام جداگانه ای را به هر کدام اختصاص دهید و در زمان فراخوانی مقدار به آن نام اشاره کنید.
            container.RegisterInstance<string>("SomeValue","String1");
            container.RegisterInstance<string>("OtherValue","String2");
            var value = container.GetInstance<string>("String2");
متغیر value با رشته "OtherValue" مقداردهی می‌گردد.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوم - معرفی کامپوننت‌ها
در قسمت قبل، پیشنیازهای کار با AngularJS 2.0 مرور و دریافت شدند. اگر مطالب آن‌را پیگیری کرده باشید، هم اکنون باید در پوشه‌ی node_modules واقع در ریشه‌ی پروژه، تمام اسکریپت‌های لازم جهت شروع به کار با AngularJS 2.0 موجود باشند.


تعاریف مداخل فایل index.html یک سایت AngularJS 2.0

پروژه‌ای که در اینجا در حال استفاده است یک برنامه‌ی ASP.NET MVC 5.x است؛ اما الزامی هم به استفاده‌ی از آن وجود ندارد. یا یک فایل index.html را به ریشه‌ی پروژه اضافه کنید و یا فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
 
    <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <link href="~/app/app.component.css" rel="stylesheet"/>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
 
    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="~/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
    <script src="~/node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="~/node_modules/systemjs/dist/system-polyfills.js"></script>
 
    <script src="~/node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="~/node_modules/systemjs/dist/system.src.js"></script>
    <script src="~/node_modules/rxjs/bundles/Rx.js"></script>
    <script src="~/node_modules/angular2/bundles/angular2.dev.js"></script>
 
    <!-- Required for http -->
    <script src="~/node_modules/angular2/bundles/http.dev.js"></script>
 
    <!-- Required for routing -->
    <script src="~/node_modules/angular2/bundles/router.dev.js"></script> 
 
    <!-- 2. Configure SystemJS -->
    <script>
        System.config({
            packages: {
                app: {
                    format: 'register',
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/main')
              .then(null, console.error.bind(console));
    </script>
</head>
<body>
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
 
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا ابتدا تعاریف مداخل بوت استرپ و css‌های سفارشی برنامه را مشاهده می‌کنید.
سپس کتابخانه‌های جاوا اسکریپتی مورد نیاز جهت کار با AngularJS 2.0 به ترتیبی که ذکر شده‌، باید تعریف شوند.
ذکر /~ در ابتدای آدرس‌ها، مختص به ASP.NET MVC است. اگر از آن استفاده نمی‌کنید، نیازی به ذکر آن هم نیست.
در ادامه تعاریف System.JS ذکر شده‌است. System.JS کار بارگذاری ماژول‌های برنامه را به عهده دارد. به این ترتیب دیگر نیازی نیست تا به ازای هر قسمت جدید برنامه، مدخلی را در اینجا اضافه کرد و کار بارگذاری آن‌ها خودکار خواهد بود. فرمت register ایی که در اینجا ذکر شده‌است، تا ماژول‌های استاندارد با فرمت ES 6 را نیز پشتیبانی می‌کند. همچنین با ذکر و تنظیم پسوند پیش فرض به js، دیگر نیازی نخواهد بود تا در حین تعریف importها در قسمت‌های مختلف برنامه، پسوند فایل‌ها را به صورت صریح ذکر کرد. مبحث improtها مرتبط است به مفاهیم ماژول‌ها در ES 6 و همچنین TypeScript.
سطر System.import کار بارگذاری اولین ماژول برنامه را از پوشه‌ی app قرار گرفته در ریشه‌ی سایت انجام می‌دهد. این ماژول main نام دارد.


نوشتن اولین کامپوننت AngularJS 2.0

برنامه‌های AngularJS 2.0 متشکل هستند از تعدادی کامپوننت و سرویس:


 و هر کامپوننت تشکیل شده‌است از:


- یک قالب یا Template: با استفاده از HTML تعریف می‌شود و کار تشکیل View و نحوه‌ی رندر کامپوننت را مشخص می‌کند. در این Viewها با استفاده از امکانات binding و directives موجود در AngularJS 2.0 کار دسترسی به داده‌ها صورت می‌گیرد.
یک کلاس: کار این کلاس که توسط TypeScript تهیه می‌شود، فراهم آوردن کدهای مرتبط با قالب است. برای مثال این کلاس حاوی تعدادی خاصیت خواهد بود که از اطلاعات آن‌ها در View مرتبط استفاده می‌شود. همچنین این کلاس می‌تواند حاوی متدهای مورد نیاز در View نیز باشد؛ برای مثال متدی که کار نمایش یا مخفی سازی یک تصویر را با کلیک بر روی دکمه‌ای انجام می‌دهد.
- متادیتا: متادیتا (یا decorator در اینجا) به AngularJS 2.0 اعلام می‌کند که این کلاس تعریف شده، صرفا یک کلاس ساده نیست و باید به آن به صورت یک کامپوننت نگاه شود.

در ذیل، کدهای یک کامپوننت نمونه‌ی AngularJS 2.0 را مشاهده می‌کنید:
import { Component } from 'angular2/core';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <div>My First Component</div>
    </div>
    `
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در انتهای کدها، یک کلاس را مشاهده می‌کنید که کار تعریف خواص و متدهای مورد نیاز توسط View را انجام می‌دهد.
بلافاصله در بالای این کلاس، متد decorator ایی را به نام Component مشاهده می‌کنید. این متادیتا است که به AngularJS 2.0 اعلام می‌کند، کلاس AppComponent تعریف شده، یک کامپوننت است و نه تنها یک کلاس ساده.
در متد Component تعریف شده، قالب یا template نحوه‌ی رندر این کامپوننت را مشاهده می‌کنید.
در ابتدای این ماژول نیز کار import تعاریف مرتبط با متد ویژه‌ی Component، از هسته‌ی AngularJS 2.0 انجام شده‌است تا کامپایلر TypeScript بتواند این فایل ts را کامپایل کند.


مروری بر نحوه‌ی تعریف class در TypeScript

مرور جامع کلاس‌ها در TypeScript را در مطلب «مبانی TypeScript؛ کلاس‌ها» می‌توانید مطالعه کنید. در اینجا جهت یادآوری، خلاصه‌ای از آن‌را که نیاز داریم، بررسی خواهیم کرد:
- جهت تعریف یک کلاس، ابتدا واژه‌ی کلیدی class به همراه نام کلاس ذکر می‌شوند.
- در AngularJS 2.0 مرسوم است که نام کلاس را به صورت نام ویژگی مدنظر به همراه پسوند Component ذکر کنیم؛ مانند AppComponent مثال فوق. این نام pascal case است و با حروف بزرگ شروع می‌شود.
- همچنین مرسوم است در برنامه‌های AngularJS 2.0، کامپوننت ریشه‌ی سایت نیز AppComponent نامیده شود.
- در مثال فوق، واژه‌ی کلیدی export را نیز پیش از واژه‌ی کلیدی class مشاهده می‌کنید. به این ترتیب این کلاس خارج از ماژولی که در آن تعریف می‌شود، قابل دسترسی خواهد بود. اکنون این کلاس و فایل، تبدیل به ماژولی خواهند شد که توسط module loader معرفی شده‌ی در ابتدای بحث یا همان System.JS به صورت خودکار بارگذاری می‌شود و دیگر نیازی به تعریف مدخل script متناظر با آن در فایل index.html نخواهد بود.
- در بدنه‌ی کلاس، کار تعریف متدها و خواص مورد نیاز View صورت می‌گیرند. برای نمونه در اینجا تنها یک خاصیت «عنوان صفحه» تعریف شده‌است. در جاوا اسکریپت مرسوم است که نام خواص را camel case شروع شده با حروف کوچک تعریف کنیم. سپس نوع این خاصیت به صورت رشته‌ای تعریف شده‌است و در آخر مقدار پیش فرض این خاصیت ذکر گردیده‌است.
البته باید دقت داشت که الزامی به ذکر نوع خاصیت، در TypeScript وجود ندارد. همینقدر که مقدار پیش فرض این خاصیت رشته‌ای است، بر اساس ویژگی به نام Type inference در TypeScript، نوع این خاصیت نیز رشته‌ای درنظر گرفته خواهد شد و دیگر نمی‌توان برای مثال یک عدد را به آن انتساب داد.
- سطح دسترسی خواص تعریف شده‌ی در یک کلاس TypeScript به صورت پیش فرض public است. بنابراین در اینجا نیازی به ذکر صریح آن نبوده‌است.


مروری بر متادیتا یا decorator یک کلاس در AngularJS 2.0

خوب، تا اینجا کلاس AppComponent تعریف و همچنین export شد تا توسط system.js به صورت خودکار بارگذاری شود. اما این کلاس به خودی خود صرفا یک کلاس TypeScript ایی است و توسط AngularJS شناسایی نمی‌شود. برای معرفی این کلاس به صورت یک کامپوننت، از یک تزئین کننده یا decorator ویژه به نام Component استفاده می‌شود که بلافاصله در بالای تعریف کلاس قرار می‌گیرد؛ چیزی شبیه به data annotations یا attributes در زبان #C.
یک decorator متدی است که اطلاعاتی اضافی را به یک کلاس، اعضاء و متدهای آن و یا حتی آرگومان‌های آن متدها، الصاق می‌کند. این ویژگی قرار است به صورت استاندارد در ES 2016 یا نگارش بعدی جاوا اسکریپت حضور داشته باشد و در حال حاضر توسط TypeScript پشتیبانی شده و در نهایت به کدهای ES 5 قابل اجرای در تمام مرورگرها ترجمه می‌شود.
یک decorator همیشه با @ شروع می‌شود و AngularJS 2.0 به همراه تعدادی decorator توکار است؛ مانند Component. از آنجائیکه decorator یک متد است، همیشه به همراه یک جفت پرانتز () ذکر می‌شود و در انتهای آن نیازی به ذکر سمی‌کالن نیست. در اینجا تزئین کننده‌ی Component یک شیء را می‌پذیر که به همراه تعدادی خاصیت است. به همین جهت پارامتر آن به صورت {} ذکر شده‌است.
خاصیت selector یک کامپوننت مشخص می‌کند که نام directive متناظر با این کامپوننت چیست:
 selector: 'pm-app',
 از این نام، به صورت یک المان جدید و سفارشی HTML جهت تعریف این کامپوننت استفاده خواهیم کرد. برای مثال اگر به کدهای ابتدای بحث دقت کنید، نام pm-app به صورت ذیل و به شکل یک تگ جدید HTML استفاده شده‌است:
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
همچنین یک کامپوننت همواره به همراه یک قالب است که نحوه‌ی رندر آن‌را مشخص می‌کند:
  template:`
 <div><h1>{{pageTitle}}</h1>
<div>My First Component</div>
 </div>
 `
 در اینجا از back tick مربوط به ES 6.0 که توسط TypeScript نیز پشتیبانی می‌شود، جهت تعریف یک رشته‌ی چندسطری جاوا اسکریپتی، استفاده شده‌است.
همچنین {{}} به معنای تعریف data binding است. به این ترتیب مقداری که قرار است به صورت تگ h1 رندر شود، از خاصیت pageTitle کلاس مزین شده‌ی توسط این ویژگی یا decorator تامین خواهد شد؛ یعنی مقدار پیش فرض خاصیت pageTitle در کلاس AppComponent.


import اطلاعات مورد نیاز جهت کامپایل یک فایل TypeScript

تا اینجا مفاهیم موجود در کلاس AppComponent را به همراه متادیتای آن بررسی کردیم. اما این متادیتای جدید کامپوننت، به صورت پیش فرض ناشناخته‌است:


همانطور که مشاهده می‌کنید، در اینجا ذیل کامپوننت، خط قرمزی جهت یادآوری عدم تعریف آن، کشیده شده‌است. در TypeScript و یا ES 6، پیش از استفاده از یک کلاس یا متد خارجی، نیاز است به module loader اعلام کنیم تا آن‌را باید از کجا تامین کند. اینکار توسط عبارت import انجام می‌شود که شبیه به عبارت using در زبان سی‌شارپ است. عبارت import جزئی از استاندارد ES 6 است و همچنین در TypeScript نیز پشتیبانی می‌شود. به این ترتیب امکان دسترسی به ویژگی‌های export شده‌ی از سایر ماژول‌ها را در ماژول فعلی (فایل فعلی در حال کار) خواهیم یافت. نمونه‌ی آن‌را با ذکر واژه‌ی کلیدی export پیش از کلاس AppComponent پیشتر ملاحظه کردید.
این ماژول‌های خارجی می‌توانند سایر ماژول‌ها و فایل‌های ts نوشته شده‌ی توسط خودمان و یا حتی اجزای AngularJS 2.0 باشند. طراحی AngularJS 2.0 نیز ماژولار است و از ماژول‌هایی مانند angular2/core، angular2/animation، angular2/http و angular2/router تشکیل شده‌است.
برای نمونه متادیتای کامپوننت، در ماژول angular2/core قرار دارد. به همین جهت نیاز است در ابتدای ماژول فعلی آن‌را import کرد:
import { Component } from 'angular2/core';
کار با ذکر واژه‌ی کلیدی import شروع می‌شود. سپس جزئی را که نیاز داریم داخل {} ذکر کرده و در آخر مسیر یافتن آن‌را مشخص می‌کنیم.


ساخت کامپوننت ریشه‌ی یک برنامه‌ی AngularJS 2.0

در ابتدای بحث که تعاریف مداخل مورد نیاز جهت اجرای یک برنامه‌ی AngularJS 2.0 ذکر شدند، عنوان شد که system.js به دنبال ماژول آغازین app/main می‌گردد.
بنابراین در ریشه‌ی پروژه، پوشه‌ی جدیدی را به نام app ایجاد کنید.
سپس یک فایل TypeScript جدید را به نام app.component.ts به این پوشه اضافه کنید. قالب این نوع فایل‌ها در add new item و با جستجو typescript در دسترس است و یا حتی یک فایل متنی ساده را هم با پسوند ts ایجاد کنید، کافی است.


نامگذاری این فایل‌ها هم مرسوم است به صورت ذکر نام ویژگی به همراه یک دات و سپس ذکر کامپوننت صورت گیرد. در اینجا چون قصد داریم کامپوننت ریشه‌ی برنامه را ایجاد کنیم، نام ویژگی آن app خواهد بود و نام کامل فایل به این ترتیب app.component.ts می‌شود.
سپس محتوای این فایل را به دقیقا معادل کدهای قسمت «نوشتن اولین کامپوننت AngularJS 2.0» ابتدای بحث تغییر دهید (همان import، متادیتا و کلاس AppComponent).

تا اینجا کامپوننت ریشه‌ی برنامه ایجاد شد. اما چگونه باید از آن استفاده کنیم و چگونه AngularJS 2.0 آن‌را شناسایی می‌کند؟ به این فرآیند آغازین شناسایی و بارگذاری ماژول‌ها، اصطلاحا bootstrapping می‌گویند. تنها صفحه‌ی واقعی موجود در یک برنامه‌ی تک صفحه‌ای وب، همان فایل index.html است و سایر صفحات و محتوای آن‌ها به محتوای این صفحه‌ی آغازین اضافه یا کم خواهند شد.
<div>
    @RenderBody()
    <pm-app>Loading App...</pm-app>
</div>
در اینجا برای نمایش اولین کامپوننت تهیه شده، نام تگ selector آن که توسط متادیتای کلاس AppComponent تعریف شد، در body فایل index.html به نحو فوق به صورت یک تگ سفارشی جدید اضافه می‌شود. به آن directive نیز می‌گویند.
خوب، اکنون module loader یا system.js چگونه این pm-app یا کامپوننت ریشه‌ی برنامه را شناسایی می‌کند؟
 System.import('app/main')
این شناسایی توسط سطر System.import تعریف شده‌ی در فایل index.html انجام می‌شود. در اینجا system.js، در پوشه‌ی app واقع در ریشه‌ی سایت، به دنبال ماژول راه اندازی به نام main می‌گردد. یعنی نیاز است فایل TypeScript جدیدی را به نام main.ts به ریشه‌ی پوشه‌ی app اضافه کنیم. محتوای این فایل ویژه‌ی بوت استرپ AngularJS 2.0 به صورت ذیل است:
/// <reference path="../node_modules/angular2/typings/browser.d.ts" />
 
import { bootstrap } from "angular2/platform/browser";
 
// Our main component
import { AppComponent } from "./app.component";
 
bootstrap(AppComponent);
این فایل ویژه را نیز مانند کلاس AppComponent که پیشتر بررسی کردیم، نیاز است از انتها به ابتدا بررسی کرد.
در انتهای این فایل متد bootstrap مربوط به AngularJS 2.0 را مشاهده می‌کنید. کار آن بارگذاری اولین ماژول و کامپوننت برنامه یا همان AppComponent است.
در ادامه نیاز است AppComponent را به این ماژول معرفی کرد؛ در غیراینصورت کامپایل نخواهد شد. برای این منظور سطر import این کلاس را از فایل app.component، مشاهده می‌کنید. در اینجا نیازی به ذکر پسوند ts. فایل app.component نیست.
سپس نیاز است محل تعریف متد بوت استرپ را نیز مشخص کنیم. این متد در ماژول angular2/platform/browser قرار دارد که به عنوان اولین import این فایل ذکر شده‌است.
سطر اول، مربوط است به تعریف فایل‌های d.ts. مربوط به TypeScript جهت شناسایی نوع‌های مرتبط با AngularJS 2.0. اگر اینکار صورت نگیرد، خطاهای ذیل را در حین کامپایل فایل‌های TypeScript دریافت خواهید کرد:
 node_modules\angular2\src\core\application_ref.d.ts(171,81): error TS2304: Build: Cannot find name 'Promise'.
node_modules\angular2\src\core\change_detection\differs\default_keyvalue_differ.d.ts(23,15): error TS2304: Build: Cannot find name 'Map'.
تهیه فایل main.ts تنها یکبار صورت می‌گیرد و دیگر با آن کاری نخواهیم داشت.

تا اینجا پوشه‌ی app واقع در ریشه‌ی سایت، یک چنین شکلی را پیدا می‌کند:



و اکنون اگر برنامه را اجرا کنیم (فشردن دکمه‌ی F5)، خروجی آن در مرورگر به صورت ذیل خواهد بود:

با توجه به اینکه در حال کار با یک برنامه‌ی جاوا اسکریپتی هستیم، باز نگه داشتن developer tools مرورگر، جهت مشاهده‌ی خطاهای احتمالی ضروری است.

در اینجا اگر خطایی وجود داشته باشد، یا اطلاعات اضافی مدنظر باشد، در console لاگ خواهند شد. برای مثال در اینجا عنوان شده‌است که برنامه در حالت توسعه در حال اجرا است. بهتر است برای ارائه‌ی نهایی، متد enableProdMode را در فایل index.html فراخوانی کنید.

همچنین در اینجا می‌توان HTML نهایی تزریق شده‌ی به صفحه را بهتر مشاهده کرد:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part2.zip
برای اجرای آن، ابتدا به فایل project.json مراجعه کرده و یکبار آن‌را ذخیره کنید تا وابستگی‌های اسکریپتی پروژه از اینترنت دریافت شوند (این موارد در قسمت قبل مرور شدند). سپس یکبار هم پروژه را Build کنید تا تمام فایل‌های ts آن کامپایل شوند و در آخر، اجرای نهایی پروژه.


خلاصه‌ی بحث

یک برنامه‌ی AngularJS 2.0 متشکل است از تعدادی کامپوننت. بنابراین کلاسی را ایجاد خواهیم کرد تا کدهای پشتیبانی کننده‌ی View این کامپوننت را تولید کند. سپس این کلاس را با متادیتایی مزین کرده و توسط آن تگ سفارشی ویژه‌ی این کامپوننت و تگ‌های HTML تشکیل دهنده‌ی این کامپوننت را به AngularJS 2.0 معرفی می‌کنیم. در اینجا در صورت نیاز وابستگی‌های تعریف این متادیتا را توسط واژه‌ی کلیدی import دریافت می‌کنیم. نام این کلاس بهتر است Pascal case بوده و با حروف بزرگ شروع شود و همچنین به صورت نام ویژگی ختم شده‌ی به کلمه‌ی Component باشد. در اینجا حتما نیاز است این کلاس export شود تا توسط module loader قابل استفاده و بارگذاری گردد. اگر View این کامپوننت نیاز به دریافت اطلاعاتی دارد، این اطلاعات به صورت خواصی در کلاس کامپوننت تعریف می‌شوند. این خواص تعریف شده‌ی با سطح دسترسی عمومی، مرسوم است به صورت camel case تعریف شوند و حروف اول آن‌ها کوچک باشد.
مطالب
یکی کردن اسمبلی‌های یک پروژه‌ی WPF
فرض کنید پروژه‌ی WPF شما از چندین پروژه‌ی ‍Class library و اسمبلی‌های جانبی دیگر، تشکیل شده‌است. اکنون نیاز است جهت سهولت توزیع آن، تمام این فایل‌ها را با هم یکی کرده و تبدیل به یک فایل EXE نهایی کنیم. مایکروسافت ابزاری را به نام ILMerge، برای یک چنین کارهایی تدارک دیده‌است؛ اما این برنامه با WPF سازگار نیست. در ادامه قصد داریم اسمبلی‌های جانبی را تبدیل به منابع مدفون شده در فایل EXE برنامه کرده و سپس آن‌ها را در اولین بار اجرای برنامه، به صورت خودکار بارگذاری و در برنامه مورد استفاده قرار دهیم.

یک مثال جهت بازتولید کدهای این مطلب
الف) یک پروژه‌ی WPF جدید را به نام MergeAssembliesIntoWPF ایجاد کنید.
ب) یک پروژه‌ی Class library جدید را به نام MergeAssembliesIntoWPF.ViewModels به این Solution اضافه کنید. از آن برای تعریف ViewModelهای برنامه استفاده خواهیم کرد.
برای نمونه کلاس ذیل را به آن اضافه کنید:
namespace MergeAssembliesIntoWPF.ViewModels
{
    public class ViewModel1
    {
        public string Data { set; get; }

        public ViewModel1()
        {
            Data = "Test";
        }
    }
}
ج) یک پروژه‌ی WPF User control library را نیز به نام MergeAssembliesIntoWPF.Shell به این Solution اضافه کنید. از آن برای تعریف Viewهای برنامه کمک خواهیم گرفت.
به این پروژه ارجاعی را به اسمبلی قسمت (ب) اضافه نموده و برای نمونه User control ذیل را به نام View1.xaml به آن اضافه نمائید:
<UserControl x:Class="MergeAssembliesIntoWPF.Shell.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             xmlns:VM="clr-namespace:MergeAssembliesIntoWPF.ViewModels;assembly=MergeAssembliesIntoWPF.ViewModels"
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <VM:ViewModel1 x:Key="ViewModel1" />
    </UserControl.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel1}}">
        <TextBlock Text="{Binding Data}" />
    </Grid>
</UserControl>
در پروژه اصلی Solution (قسمت الف)، ارجاعاتی را به دو اسمبلی قسمت‌های ب و ج اضافه کنید. سپس MainWindow.xaml آن‌را به نحو ذیل تغییر داده و برنامه را اجرا کنید:
<Window x:Class="MergeAssembliesIntoWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:V="clr-namespace:MergeAssembliesIntoWPF.Shell;assembly=MergeAssembliesIntoWPF.Shell"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <V:View1 x:Key="View1" />
    </Window.Resources>
    <Grid>
        <V:View1 />
    </Grid>
</Window>
تا اینجا باید متن Test در پنجره اصلی برنامه ظاهر شود.


ب) مدفون کردن خودکار اسمبلی‌های جانبی برنامه در فایل EXE آن
فایل csproj پروژه اصلی را خارج از VS.NET باز کنید. در انتهای آن سطر ذیل قابل مشاهده است:
 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
پس از این سطر، چند سطر ذیل را اضافه کنید:
  <Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>
این task جدید MSBuild سبب خواهد شد تا با هر بار Build برنامه، اسمبلی‌هایی که در ارجاعات برنامه دارای خاصیت Copy local مساوی true هستند، به صورت خودکار به صورت یک resource جدید در فایل exe برنامه مدفون شوند. عموما ارجاعاتی که دستی اضافه می‌شوند، مانند دو اسمبلی یاد شده در ابتدای بحث، دارای خاصیت Copy local=true نیز هستند.
پس از این تغییر نیاز است یکبار پروژه را بسته و مجددا باز کنید. اکنون پروژه را build کنید و جهت اطمینان بیشتر آن‌را برای مثال توسط ILSpy مورد بررسی قرار دهید:


همانطور که مشاهده می‌کنید، دو اسمبلی مورد استفاده در برنامه به صورت خودکار در قسمت منابع فایل EXE مدفون شده‌اند.
اگر به مسیر LogicalName تنظیمات فوق دقت کنید، DestinationSubDirectory نیز ذکر شده‌است. علت این است که بسیاری از اسمبلی‌های بومی سازی شده WPF با نام‌هایی یکسان اما در پوشه‌هایی مانند fa، fr و امثال آن ذخیره می‌شوند. به همین جهت نیاز است بین این‌ها تمایز قائل شد.


ج) بارگذاری خودکار اسمبلی‌ها در AppDomain برنامه

تا اینجا اسمبلی‌های جانبی را در فایل EXE مدفون کرده‌ایم. اکنون نوبت به بارگذاری آن‌ها در AppDomain برنامه است. برای اینکار نیاز است تا روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve را تحت نظر قرار داده و اسمبلی‌هایی را که برنامه درخواست می‌کند، در همینجا از منابع خوانده و به AppDomain اضافه کرد.
انجام اینکار در برنامه‌های WinForms ساده‌است. فقط کافی است به متد Program.Main برنامه مراجعه کرده و تعریف یاد شده را به ابتدای متد Main اضافه کرد. اما در WPF هرچند فایل App.xaml.cs به نظر نقطه‌ی آغازین برنامه است، اما در واقع اینطور نیست. برای نمونه، پوشه‌ی obj\Debug برنامه را گشوده و فایل App.g.i.cs آن‌را بررسی کنید. در اینجا می‌توانید همان رویه شبیه به برنامه‌های WinForm را در متد Program.Main آن، مشاهده کنید. بنابراین نیاز است کنترل این مساله را راسا در دست بگیریم:
using System;
using System.Globalization;
using System.Reflection;

namespace MergeAssembliesIntoWPF
{
    public class Program
    {
        [STAThreadAttribute]
        public static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            App.Main();
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {
            var executingAssembly = Assembly.GetExecutingAssembly();
            var assemblyName = new AssemblyName(args.Name);

            var path = assemblyName.Name + ".dll";
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }

            using (var stream = executingAssembly.GetManifestResourceStream(path))
            {
                if (stream == null)
                    return null;

                var assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                return Assembly.Load(assemblyRawBytes);
            }
        }
    }
}
کلاس Program را با تعاریف فوق به پروژه خود اضافه نمائید. در اینجا Program.Main مورد نیاز خود را تدارک دیده‌ایم. کار آن مدیریت روال رخدادگردان AppDomain.CurrentDomain.AssemblyResolve برنامه پیش از شروع به هر کاری است. در روال رخداد گردان OnResolveAssembly، برنامه اعلام می‌کند که به چه اسمبلی خاصی نیاز دارد. ما آن‌را از قسمت منابع خوانده و سپس توسط متد Assembly.Load آن‌را در AppDomain برنامه بارگذاری می‌کنیم.
پس از اینکه کلاس فوق را اضافه کردید، نیاز است کلاس Program اضافه شده را به عنوان Startup object برنامه نیز معرفی کنید:

انجام اینکار ضروری است؛ در غیراینصورت با متد Main موجود در فایل App.g.i.cs تداخل می‌کند.
اکنون برای آزمایش برنامه، یکبار آن‌را Build کرده و بجز فایل Exe، مابقی فایل‌های موجود در پوشه‌ی bin را حذف کنید. سپس برنامه را خارج از VS.NET اجرا کنید. کار می‌کند!
MergeAssembliesIntoWPF.zip
 
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت دوم
در قسمت قبل معماری اپلیکیشن‌های N-Tier و بروز رسانی موجودیت‌های منفصل توسط Web API را بررسی کردیم. در این قسمت بروز رسانی موجودیت‌های منفصل توسط WCF را بررسی می‌کنیم.

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

سناریویی را در نظر بگیرید که در آن عملیات CRUD توسط WCF پیاده سازی شده اند و دسترسی داده‌ها با مدل Code-First انجام می‌شود. فرض کنید مدل اپلیکیشن مانند تصویر زیر است.

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

  • در ویژوال استودیو پروژه جدیدی از نوع Class Library بسازید و نام آن را به Recipe2 تغییر دهید.
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • سه کلاس با نام‌های Post, Comment و Recipe2Context به پروژه اضافه کنید. کلاس‌های Post و Comment موجودیت‌های مدل ما هستند که به جداول متناظرشان نگاشت می‌شوند. کلاس Recipe2Context آبجکت DbContext ما خواهد بود که بعنوان درگاه عملیاتی EF عمل می‌کند. دقت کنید که خاصیت‌های لازم WCF یعنی DataContract و DataMember در کلاس‌های موجودیت‌ها بدرستی استفاده می‌شوند. لیست زیر کد این کلاس‌ها را نشان می‌دهد.
[DataContract(IsReference = true)]
public class Post
{
    public Post()
    {
        comments = new HashSet<Comments>();
    }
    
    [DataMember]
    public int PostId { get; set; }
    [DataMember]
    public string Title { get; set; }
    [DataMember]
    public virtual ICollection<Comment> Comments { get; set; }
}

[DataContract(IsReference=true)]
public class Comment
{
    [DataMember]
    public int CommentId { get; set; }
    [DataMember]
    public int PostId { get; set; }
    [DataMember]
    public string CommentText { get; set; }
    [DataMember]
    public virtual Post Post { get; set; }
}

public class EFRecipesEntities : DbContext
{
    public EFRecipesEntities() : base("name=EFRecipesEntities") {}

    public DbSet<Post> posts;
    public DbSet<Comment> comments;
}
  • یک فایل App.config به پروژه اضافه کنید و رشته اتصال زیر را به آن اضافه نمایید.
<connectionStrings>
  <add name="Recipe2ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • حال یک پروژه WCF به Solution جاری اضافه کنید. برای ساده نگاه داشتن مثال جاری، نام پیش فرض Service1 را بپذیرید. فایل IService1.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void Cleanup();
    [OperationContract]
    Post GetPostByTitle(string title);
    [OperationContract]
    Post SubmitPost(Post post);
    [OperationContract]
    Comment SubmitComment(Comment comment);
    [OperationContract]
    void DeleteComment(Comment comment);
}
  • فایل Service1.svc.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. بیاد داشته باشید که پروژه Recipe2 را ارجاع کنید و فضای نام آن را وارد نمایید. همچنین کتابخانه EF 6 را باید به پروژه اضافه کنید.
public class Service1 : IService
{
    public void Cleanup()
    {
        using (var context = new EFRecipesEntities())
        {
            context.Database.ExecuteSqlCommand("delete from [comments]");
            context. Database.ExecuteSqlCommand ("delete from [posts]");
        }
    }

    public Post GetPostByTitle(string title)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Configuration.ProxyCreationEnabled = false;
            var post = context.Posts.Include(p => p.Comments).Single(p => p.Title == title);
            return post;
        }
    }

    public Post SubmitPost(Post post)
    {
        context.Entry(post).State =
            // if Id equal to 0, must be insert; otherwise, it's an update
            post.PostId == 0 ? EntityState.Added : EntityState.Modified;
        context.SaveChanges();
        return post;
    }

    public Comment SubmitComment(Comment comment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Comments.Attach(comment);
            if (comment.CommentId == 0)
            {
                // this is an insert
                context.Entry(comment).State = EntityState.Added);
            }
            else
            {
                // set single property to modified, which sets state of entity to modified, but
                // only updates the single property – not the entire entity
                context.entry(comment).Property(x => x.CommentText).IsModified = true;
            }
            context.SaveChanges();
            return comment;
        }
    }

    public void DeleteComment(Comment comment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(comment).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}


  • در آخر پروژه جدیدی از نوع Windows Console Application به Solution جاری اضافه کنید. از این اپلیکیشن بعنوان کلاینتی برای تست سرویس WCF استفاده خواهیم کرد. فایل program.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. روی نام پروژه کلیک راست کرده و گزینه Add Service Reference را انتخاب کنید، سپس ارجاعی به سرویس Service1 اضافه کنید. رفرنسی هم به کتابخانه کلاس‌ها که در ابتدای مراحل ساختید باید اضافه کنید.
class Program
{
    static void Main(string[] args)
    {
        using (var client = new ServiceReference2.Service1Client())
        {
            // cleanup previous data
            client.Cleanup();
            // insert a post
            var post = new Post { Title = "POCO Proxies" };
            post = client.SubmitPost(post);
            // update the post
            post.Title = "Change Tracking Proxies";
            client.SubmitPost(post);
            // add a comment
            var comment1 = new Comment { CommentText = "Virtual Properties are cool!", PostId = post.PostId };
            var comment2 = new Comment { CommentText = "I use ICollection<T> all the time", PostId = post.PostId };
            comment1 = client.SubmitComment(comment1);
            comment2 = client.SubmitComment(comment2);
            // update a comment
            comment1.CommentText = "How do I use ICollection<T>?";
            client.SubmitComment(comment1);
            // delete comment 1
            client.DeleteComment(comment1);
            // get posts with comments
            var p = client.GetPostByTitle("Change Tracking Proxies");
            Console.WriteLine("Comments for post: {0}", p.Title);
            foreach (var comment in p.Comments)
            {
                Console.WriteLine("\tComment: {0}", comment.CommentText);
            }
        }
    }
}
اگر اپلیکیشن کلاینت (برنامه کنسول) را اجرا کنید با خروجی زیر مواجه می‌شوید.

Comments for post: Change Tracking Proxies
Comment: I use ICollection<T> all the time


شرح مثال جاری

ابتدا با اپلیکیشن کنسول شروع می‌کنیم، که کلاینت سرویس ما است. نخست در یک بلاک {} using وهله ای از کلاینت سرویس مان ایجاد می‌کنیم. درست همانطور که وهله ای از یک EF Context می‌سازیم. استفاده از بلوک‌های using توصیه می‌شود چرا که متد Dispose بصورت خودکار فراخوانی خواهد شد، چه بصورت عادی چه هنگام بروز خطا. پس از آنکه وهله ای از کلاینت سرویس را در اختیار داشتیم، متد Cleanup را صدا می‌زنیم. با فراخوانی این متد تمام داده‌های تست پیشین را حذف می‌کنیم. در چند خط بعدی، متد SubmitPost را روی سرویس فراخوانی می‌کنیم. در پیاده سازی فعلی شناسه پست را بررسی می‌کنیم. اگر مقدار شناسه صفر باشد، خاصیت State موجودیت را به Added تغییر می‌دهید تا رکورد جدیدی ثبت کنیم. در غیر اینصورت فرض بر این است که چنین موجودیتی وجود دارد و قصد ویرایش آن را داریم، بنابراین خاصیت State را به Modified تغییر می‌دهیم. از آنجا که مقدار متغیرهای int بصورت پیش فرض صفر است، با این روش می‌توانیم وضعیت پست‌ها را مشخص کنیم. یعنی تعیین کنیم رکورد جدیدی باید ثبت شود یا رکوردی موجود بروز رسانی گردد. رویکردی بهتر آن است که پارامتری اضافی به متد پاس دهیم، یا متدی مجزا برای ثبت رکوردهای جدید تعریف کنیم. مثلا رکوردی با نام InsertPost. در هر حال، بهترین روش بستگی به ساختار اپلیکیشن شما دارد.

اگر پست جدیدی ثبت شود، خاصیت PostId با مقدار مناسب جدید بروز رسانی می‌شود و وهله پست را باز می‌گردانیم. ایجاد و بروز رسانی نظرات کاربران مشابه ایجاد و بروز رسانی پست‌ها است، اما با یک تفاوت اساسی: بعنوان یک قانون، هنگام بروز رسانی نظرات کاربران تنها فیلد متن نظر باید بروز رسانی شود. بنابراین با فیلدهای دیگری مانند تاریخ انتشار و غیره اصلا کاری نخواهیم داشت. بدین منظور تنها خاصیت CommentText را بعنوان Modified علامت گذاری می‌کنیم. این امر منجر می‌شود که Entity Framework عبارتی برای بروز رسانی تولید کند که تنها این فیلد را در بر می‌گیرد. توجه داشته باشید که این روش تنها در صورتی کار می‌کند که بخواهید یک فیلد واحد را بروز رسانی کنید. اگر می‌خواستیم فیلدهای بیشتری را در موجودیت Comment بروز رسانی کنیم، باید مکانیزمی برای ردیابی تغییرات در سمت کلاینت در نظر می‌گرفتیم. در مواقعی که خاصیت‌های متعددی می‌توانند تغییر کنند، معمولا بهتر است کل موجودیت بروز رسانی شود تا اینکه مکانیزمی پیچیده برای ردیابی تغییرات در سمت کلاینت پیاده گردد. بروز رسانی کل موجودیت بهینه‌تر خواهد بود.

برای حذف یک دیدگاه، متد Entry را روی آبجکت DbContext فراخوانی می‌کنیم و موجودیت مورد نظر را بعنوان آرگومان پاس می‌دهیم. این امر سبب می‌شود که موجودیت مورد نظر بعنوان Deleted علامت گذاری شود، که هنگام فراخوانی متد SaveChanges اسکریپت لازم برای حذف رکورد را تولید خواهد کرد.

در آخر متد GetPostByTitle یک پست را بر اساس عنوان پیدا کرده و تمام نظرات کاربران مربوط به آن را هم بارگذاری می‌کند. از آنجا که ما کلاس‌های POCO را پیاده سازی کرده ایم، Entity Framework آبجکتی را بر می‌گرداند که Dynamic Proxy نامیده می‌شود. این آبجکت پست و نظرات مربوط به آن را در بر خواهد گرفت. متاسفانه WCF نمی‌تواند آبجکت‌های پروکسی را مرتب سازی (serialize) کند. اما با غیرفعال کردن قابلیت ایجاد پروکسی‌ها (ProxyCreationEnabled=false) ما به Entity Framework می‌گوییم که خود آبجکت‌های اصلی را بازگرداند. اگر سعی کنید آبجکت پروکسی را سریال کنید با پیغام خطای زیر مواجه خواهید شد:

The underlying connection was closed: The connection was closed unexpectedly 

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

در این قسمت دیدیم چگونه می‌توانیم از آبجکت‌های POCO برای مدیریت عملیات CRUD توسط WCF استفاده کنیم. از آنجا که هیچ اطلاعاتی درباره وضعیت موجودیت‌ها روی کلاینت ذخیره نمی‌شود، متدهایی مجزا برای عملیات CRUD ساختیم. در قسمت‌های بعدی خواهیم دید چگونه می‌توان تعداد متدهایی که سرویس مان باید پیاده سازی کند را کاهش داد و چگونه ارتباطات بین کلاینت و سرور را ساده‌تر کنیم.

اشتراک‌ها
لاگین کردن در یک سایت و وارد شدن در سایتی دیگر

زمانی که برای احراز هویت از ASP.NET Identity استفاده می‌کنید، ممکن است برای شما هم پیش آمده باشد که اگر در یکی از سایت‌های خود لاگین کرده باشید و سایتی دیگر را که از ASP.NET Identity استفاده می‌کند، باز کنید بدون اینکه لازم باشد دوباره لاگین کنید، با همان نام کاربری سایت اول در سایت دوم هم وارد شده اید (بر روی یک دومین یا هاست شده‌ی در یک سیستم). برای نمونه میتوانید دو برنامه asp.net mvc ایجاد کنید، سپس یکی را اجرا کنید و پس از ثبت نام در آن و لاگین کردن، برنامه دوم را اجرا کنید، احتمالا مشاهده می‌کنید که به صورت خودکار وارد شده‌اید.

دلیل و راه حل آن ساده است، AspNet.ApplicationCookie در هر دوسایت هم نام است، آن را تغییر دهید:

public partial class Startup
{
    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            CookieName = "MyCookieName",

        });
    }
}
لاگین کردن در یک سایت و وارد شدن در سایتی دیگر
پاسخ به بازخورد‌های پروژه‌ها
خدمات پرداخت قبض در پروژه
بله مثل اینکه در مورد بانک پارسیان حق با شماست.
ولی من در مورد سایر بانک‌ها در مستندات چیزی در مورد پرداخت قبوض ندیدم. اگر برای سایر بانک‌ها هم مستنداتی در دست دارید لطفا قرار بدید.

جایی از مستندات محرمانه بودنش ذکر نشده، اما امیدوارم کار غیراخلاقی انجام نداده باشم
خیر غیر اخلاقی نیست چون API تولید شده تا همه استفاده کنند.

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

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

ساخت تم اضافی در کنار تم اصلی

ساخت تم اضافی در انگیولار متریال ۲ بسیار ساده است. شما می‌توانید با استفاده مجدد از تابع angular-material-theme داخل یک کلاس CSS، صاحب یک تم اضافی دیگر شوید. برای نمونه در اینجا فایل my-custom-theme.scss را باز کرده و به شکل زیر تغییر می‌دهیم.

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

/*تعریف تم اضافی در کنار تم اصلی*/
$alternate-primary: mat-palette($mat-light-blue);
$alternate-accent: mat-palette($mat-yellow, 500);
$alternate-warn: mat-palette($mat-red, 500);
$alternate-theme: mat-dark-theme($alternate-primary, $alternate-accent);
.alternate-theme {
  @include angular-material-theme($alternate-theme);
}

با این‌کار در کنار تم روشن اصلی، یک تم مشکی به صورت اضافی داخل کلاس CSS به نام alternate-theme تعریف کرده‌ایم. در این حالت تمامی کامپوننتهایی که داخل المنت با کلاس alternate-theme قرار گرفته‌اند، از تم مشکی تعریف شده استفاده خواهند کرد.

با تغییر فایل app.component.html به شکل زیر: 

<md-card>
  <md-card-header>
    <md-card-title>تم اصلی</md-card-title>
  </md-card-header>
  <button md-raised-button color="primary">
    Primary
  </button>
  <button md-raised-button color="accent">
    Accent
  </button>
  <button md-raised-button color="warn">
    Warning
  </button>
</md-card>

<div>
  <md-card>
    <md-card-header>
      <md-card-title>تم اضافی</md-card-title>
    </md-card-header>
    <md-card-content>
      <button md-raised-button color="primary">
        Primary
      </button>
      <button md-raised-button color="accent">
        Accent
      </button>
      <button md-raised-button color="warn">
        Warning
      </button>
    </md-card-content>
  </md-card>
</div>

تصویر زیر را در خروجی خواهید داشت.

 

به همین روش می‌توانید تعداد دلخواهی از تم‌ها را بسازید. همچنین می‌توانید هر تم اضافی را در یک فایل Sass تعریف کنید و از این طریق تم‌های مختلف را از هم جدا کنید. در این حالت به این نکته توجه داشته باشید که نباید mat-core@ در سرتاسر برنامه بیش از یکبار بارگذاری شده باشد. 

ساخت تم به ازای هر کامپوننت

با استفاده از mixin به نام angular-material-theme خروجی تولید شده بر روی تمامی کامپوننت‌های انگیولار متریال ۲ اعمال خواهد شد. اگر از تمامی کامپوننت‌های انگیولار متریال ۲ استفاده نمی‌کنید، می‌توانید برای کاهش حجم فایل CSS تولید شده از mixin مخصوص به هر کامپوننت استفاده کنید. همچنین برای ساخت تم‌های متفاوت به ازای هر کامپوننت نیز می‌توانید از این روش استفاده کنید. 

برای این کار تمامی مراحلی که برای ساخت تم مورد نیاز بود، باید طی شود. فقط به جای استفاده از mixin به نام angular-material-theme بایستی به طریق زیر عمل شود. 

اول: بارگذاری mixin با نام mat-core-them. این mixin تمامی استایل‌های مشترک رفتاری (مانند موج (ripple) در هنگام کلیک) برای کامپوننت‌ها را در بر دارد. این mixin خروجی تابع mat-light-theme یا mat-dark-theme را به عنوان ورودی دریافت می‌کند.

دوم: بارگذاری mixin مربوط به هر کامپوننت. برای مثال برای دکمه از mixin به نام mat-button-theme و برای checkbox از mixin به نام mat-checkbox-theme می‌توانید استفاده کنید. در زیر لیست mixin‌ها به ازای کامپوننت‌های مختلف ذکر شده است. 

mat-autocomplete-theme
mat-button-theme
mat-button-toggle-theme
mat-card-theme
mat-checkbox-theme
mat-chips-theme
mat-datepicker-theme
mat-dialog-theme
mat-grid-list-theme
mat-icon-theme
mat-input-theme
mat-list-theme
mat-menu-theme
mat-progress-bar-theme
mat-progress-spinner-theme
mat-radio-theme
mat-select-theme
mat-sidenav-theme
mat-slide-toggle-theme
mat-slider-theme
mat-tabs-theme
mat-toolbar-theme
mat-tooltip-theme

در مثال زیر می‌خواهیم تمامی کامپوننت‌ها به جز کامپوننت دکمه، تم سبز(در گروه Primary) و دکمه‌ها نیز تم آبی داشته باشند. کافی است کدهای زیر را در فایل Sass خود وارد کنید. 

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

با توجه به اینکه mat-material-theme در داخل خود mat-button-theme را بارگذاری می‌کند دو نتیجه زیر را می‌توان گرفت.

اول: اگر mat-material-theme بعد از هر کدام از mixinهای مربوط به کامپوننت‌ها نوشته شود، تمامی Cssهای تولید شده به ازای کامپوننت را دوباره نویسی کرده و عملا هیچ کدام کارایی نخواهند داشت. برای مثال کافی است فایل Sass خود را به شکل زیر تغییر دهید. در این صورت تم مربوط به دکمه کاریی نخواهد داشت. 

@import '~@angular/material/theming';
@include mat-core();

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-ligth-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

دوم: همانطور که گفتیم mat-button-theme در mat-material-theme قبلا بارگذاری شده است. با بارگذاری دوباره توسط mat-button-theme کدهای CSS که قبلا برای دکمه تولید شده‌اند را از نو دوباره می‌نویسد و این باعث بزرگ شدن حجم فایل Css تولید شده خواهد شد. پس بهتر است هنگام استفاده از mixinهای مختص کامپوننت‌ها از mat-material-theme استفاده نکنیم. 

  جهت بهبود کدهای قبلی بهتر است از کدهای زیر استفاده کنیم. 
@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);

@include mat-core-theme($my-app-theme);
@include mat-autocomplete-theme($my-app-theme);

@include mat-button-theme($button-theme);

@include mat-button-toggle-theme($my-app-theme);
@include mat-card-theme($my-app-theme);
@include mat-checkbox-theme($my-app-theme);
@include mat-chips-theme($my-app-theme);
@include mat-datepicker-theme($my-app-theme);
@include mat-dialog-theme($my-app-theme);
@include mat-grid-list-theme($my-app-theme);
@include mat-icon-theme($my-app-theme);
@include mat-input-theme($my-app-theme);
@include mat-list-theme($my-app-theme);
@include mat-menu-theme($my-app-theme);
@include mat-progress-bar-theme($my-app-theme);
@include mat-progress-spinner-theme($my-app-theme);
@include mat-radio-theme($my-app-theme);
@include mat-select-theme($my-app-theme);
@include mat-sidenav-theme($my-app-theme);
@include mat-slide-toggle-theme($my-app-theme);
@include mat-slider-theme($my-app-theme);
@include mat-tabs-theme($my-app-theme);
@include mat-toolbar-theme($my-app-theme);
@include mat-tooltip-theme($my-app-theme);

تعویض تم از طریق کد

فرض کنید یک تم پیش فرض و یک تم اضافی به نام alternate-theme دارید. برای تعویض تم از طریق کد کافی است کلاس المنت پدر در صفحه html خود را از طریق [ngClass] با نام تم، مقدار دهی کنید. کدهای داخل app.component.ts را به شکل زیر تغییر می‌دهیم. 

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  themes = [
    {value: 'alternate-theme', text: 'تم مشکی'},
    {value: '', text: 'تم سفید'},
  ];
  activeTheme = '';
}

آرایه‌ای جهت نمایش در کامپوننت md-select با دو مقدار تم پیش فرض و تم با نام 'alternate-theme تعریف می‌کنیم. همچنین متغیری با نام activeTheme را تعریف می‌کنیم. این متغیر در هر لحظه نام تم اعمال شده را در خود نگهداری می‌کند. مقدار اولیه این متغیر تم اصلی است.

کامپوننت md-select را به شکل زیر به فایل app.component.html به تگ main اضافه می‌کنیم. 

<md-select dir="rtl" [(ngModel)]="activeTheme" placeholder="تعویض تم">
    <md-option *ngFor="let theme of themes" [value]="theme.value">
            {{ theme.text }}
    </md-option>
</md-select>
حالا کافی است کل تگهای موجود در app.component.html را داخل یک تگ div به شکل زیر قرار دهیم. 
<div [ngClass]="activeTheme">
بعد از اجرای برنامه می‌توانید با تعویض تم از طریق کامپوننت md-select، تم صفحه را تعویض کنید. 

کدهای این قسمت را، از اینجا دریافت کنید: ساخت تم سفارشی در انگولار متریال ۲ - بخش دوم