مطالب
مهاجرت از SQL Membership به ASP.NET Identity
در این مقاله مهاجرت یک اپلیکیشن وب که توسط SQL Membership ساخته شده است را به سیستم جدید ASP.NET Identity بررسی می‌کنیم. برای این مقاله از یک قالب اپلیکیشن وب (Web Forms) که توسط Visual Studio 2010 ساخته شده است برای ساختن کاربران و نقش‌ها استفاده می‌کنیم. سپس با استفاده از یک SQL Script دیتابیس موجود را به دیتابیسی که ASP.NET Identity نیاز دارد تبدیل می‌کنیم. در قدم بعدی پکیج‌های مورد نیاز را به پروژه اضافه می‌کنیم و صفحات جدیدی برای مدیریت حساب‌های کاربری خواهیم ساخت. بعنوان یک تست، کاربران قدیمی که توسط SQL Membership ساخته شده بودند باید قادر باشند به سایت وارد شوند. همچنین کاربران جدید باید بتوانند بدون هیچ مشکلی در سیستم ثبت نام کنند. سورس کد کامل این مقاله را می‌توانید از این لینک دریافت کنید.


یک اپلیکیشن با SQL Membership بسازید

برای شروع به اپلیکیشنی نیاز داریم که از SQL Membership استفاده می‌کند و دارای داده هایی از کاربران و نقش‌ها است. برای این مقاله، بگذارید پروژه جدیدی توسط VS 2010 بسازیم.

حال با استفاده از ابزار ASP.NET Configuration دو کاربر جدید بسازید: oldAdminUser و oldUser.

نقش جدیدی با نام Admin بسازید و کاربر oldAdminUser را به آن اضافه کنید.

بخش جدیدی با نام Admin در سایت خود بسازید و فرمی بنام Default.aspx به آن اضافه کنید. همچنین فایل web.config این قسمت را طوری پیکربندی کنید تا تنها کاربرانی که در نقش Admin هستند به آن دسترسی داشته باشند. برای اطلاعات بیشتر به این لینک مراجعه کنید.

پنجره Server Explorer را باز کنید و جداول ساخته شده توسط SQL Membership را بررسی کنید. اطلاعات اصلی کاربران که برای ورود به سایت استفاده می‌شوند، در جداول aspnet_Users و aspnet_Membership ذخیره می‌شوند. داده‌های مربوط به نقش‌ها نیز در جدول aspnet_Roles ذخیره خواهند شد. رابطه بین کاربران و نقش‌ها نیز در جدول aspnet_UsersInRoles ذخیره می‌شود، یعنی اینکه هر کاربری به چه نقش هایی تعلق دارد.

برای مدیریت اساسی سیستم عضویت، مهاجرت جداول ذکر شده به سیستم جدید ASP.NET Identity کفایت می‌کند.

مهاجرت به Visual Studio 2013

  • برای شروع ابتدا Visual Studio Express 2013 for Web یا Visual Studio 2013 را نصب کنید.
  • حال پروژه ایجاد شده را در نسخه جدید ویژوال استودیو باز کنید. اگر نسخه ای از SQL Server Express را روی سیستم خود نصب نکرده باشید، هنگام باز کردن پروژه پیغامی به شما نشان داده می‌شود. دلیل آن وجود رشته اتصالی است که از SQL Server Express استفاده می‌کند. برای رفع این مساله می‌توانید SQL Express را نصب کنید، و یا رشته اتصال را طوری تغییر دهید که از LocalDB استفاده کند.
  • فایل web.config را باز کرده و رشته اتصال را مانند تصویر زیر ویرایش کنید.

  • پنجره Server Explorer را باز کنید و مطمئن شوید که الگوی جداول و داده‌ها قابل رویت هستند.
  • سیستم ASP.NET Identity با نسخه 4.5 دات نت فریم ورک و بالا‌تر سازگار است. پس نسخه فریم ورک پروژه را به آخرین نسخه (4.5.1) تغییر دهید.

پروژه را Build کنید تا مطمئن شوید هیچ خطایی وجود ندارد.

نصب پکیج‌های NuGet

در پنجره Solution Explorer روی نام پروژه خود کلیک راست کرده، و گزینه Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده، عبارت "Microsoft.AspNet.Identity.EntityFramework" را وارد کنید. این پکیج را در لیست نتایج انتخاب کرده و آن را نصب کنید. نصب این بسته، نیازمندهای موجود را بصورت خودکار دانلود و نصب می‌کند: EntityFramework و ASP.NET Identity Core. حال پکیج‌های زیر را هم نصب کنید (اگر نمی‌خواهید OAuth را فعال کنید، 4 پکیج آخر را نادیده بگیرید).
  • Microsoft.AspNet.Identity.Owin
  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security.Facebook
  • Microsoft.Owin.Security.Google
  • Microsoft.Owin.Security.MicrosoftAccount
  • Microsoft.Owin.Security.Twitter

مهاجرت دیتابیس فعلی به سیستم ASP.NET Identity

قدم بعدی مهاجرت دیتابیس فعلی به الگویی است، که سیستم ASP.NET Identity به آن نیاز دارد. بدین منظور ما یک اسکریپت SQL را اجرا می‌کنیم تا جداول جدیدی بسازد و اطلاعات کاربران را به آنها انتقال دهد. فایل این اسکریپت را می‌توانید از لینک https://github.com/suhasj/SQLMembership-Identity-OWIN دریافت کنید.
این اسکریپت مختص این مقاله است. اگر الگوی استفاده شده برای جداول سیستم عضویت شما ویرایش/سفارشی-سازی شده باید این اسکریپت را هم بر اساس این تغییرات بروز رسانی کنید.
پنجره Server Explorer را باز کنید. گره اتصال ApplicationServices را باز کنید تا جداول را مشاهده کنید. روی گره Tables کلیک راست کرده و گزینه New Query را انتخاب کنید.

در پنجره کوئری باز شده، تمام محتویات فایل Migrations.sql را کپی کنید. سپس اسکریپت را با کلیک کردن دکمه Execute اجرا کنید.

ممکن است با اخطاری مواجه شوید مبنی بر آنکه امکان حذف (drop) بعضی از جداول وجود نداشت. دلیلش آن است که چهار عبارت اولیه در این اسکریپت، تمام جداول مربوط به Identity را در صورت وجود حذف می‌کنند. از آنجا که با اجرای اولیه این اسکریپت چنین جداولی وجود ندارند، می‌توانیم این خطاها را نادیده بگیریم. حال پنجره Server Explorer را تازه (refresh) کنید و خواهید دید که پنج جدول جدید ساخته شده اند.

لیست زیر نحوه Map کردن اطلاعات از جداول SQL Membership به سیستم Identity را نشان می‌دهد.

  • aspnet_Roles --> AspNetRoles
  • aspnet_Users, aspnet_Membership --> AspNetUsers
  • aspnet_UsersInRoles --> AspNetUserRoles

جداول AspNetUserClaims و AspNetUserLogins خالی هستند. فیلد تفکیک کننده (Discriminator) در جدول AspNetUsers باید مطابق نام کلاس مدل باشد، که در مرحله بعدی تعریف خواهد شد. همچنین ستون PasswordHash به فرم 'encrypted password|password salt|password format' می‌باشد. این شما را قادر می‌سازد تا از رمزنگاری برای ذخیره و بازیابی کلمه‌های عبور استفاده کنید. این مورد نیز در ادامه مقاله بررسی شده است.



ساختن مدل‌ها و صفحات عضویت

بصورت پیش فرض سیستم ASP.NET Identity برای دریافت و ذخیره اطلاعات در دیتابیس عضویت از Entity Framework استفاده می‌کند. برای آنکه بتوانیم با جداول موجود کار کنیم، می‌بایست ابتدا مدل هایی که الگوی دیتابیس را نمایندگی می‌کنند ایجاد کنیم. برای این کار مدل‌های ما یا باید اینترفیس‌های موجود در Identity.Core را پیاده سازی کنند، یا می‌توانند پیاده سازی‌های پیش فرض را توسعه دهند. پیاده سازی‌های پیش فرض در Microsoft.AspNet.Identity.EntityFramework وجود دارند.
در نمونه ما، جداول AspNetRoles, AspNetUserClaims, AspNetLogins و AspNetUserRole ستون هایی دارند که شباهت زیادی به پیاده سازی‌های پیش فرض سیستم Identity دارند. در نتیجه می‌توانیم از کلاس‌های موجود، برای Map کردن الگوی جدید استفاده کنیم. جدول AspNetUsers ستون‌های جدیدی نیز دارد. می‌توانیم کلاس جدیدی بسازیم که از IdentityUser ارث بری کند و آن را گسترش دهیم تا این فیلدهای جدید را پوشش دهد.
پوشه ای با نام Models بسازید (در صورتی که وجود ندارد) و کلاسی با نام User به آن اضافه کنید.

کلاس User باید کلاس IdentityUser را که در اسمبلی Microsoft.AspNet.Identity.EntityFramework وجود دارد گسترش دهد. خاصیت هایی را تعریف کنید که نماینده الگوی جدول AspNetUser هستند. خواص ID, Username, PasswordHash و SecurityStamp در کلاس IdentityUser تعریف شده اند، بنابراین این خواص را در لیست زیر نمی‌بینید.

  public class User : IdentityUser
    {
        public User()
        {
            CreateDate = DateTime.Now;
            IsApproved = false;
            LastLoginDate = DateTime.Now;
            LastActivityDate = DateTime.Now;
            LastPasswordChangedDate = DateTime.Now;
            LastLockoutDate = DateTime.Parse("1/1/1754");
            FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1/1/1754");
            FailedPasswordAttemptWindowStart = DateTime.Parse("1/1/1754");
        }

        public System.Guid ApplicationId { get; set; }
        public string MobileAlias { get; set; }
        public bool IsAnonymous { get; set; }
        public System.DateTime LastActivityDate { get; set; }
        public string MobilePIN { get; set; }
        public string Email { get; set; }
        public string LoweredEmail { get; set; }
        public string LoweredUserName { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public System.DateTime CreateDate { get; set; }
        public System.DateTime LastLoginDate { get; set; }
        public System.DateTime LastPasswordChangedDate { get; set; }
        public System.DateTime LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public System.DateTime FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public System.DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }
    }

حال برای دسترسی به دیتابیس مورد نظر، نیاز به یک DbContext داریم. اسمبلی Microsoft.AspNet.Identity.EntityFramework کلاسی با نام IdentityDbContext دارد که پیاده سازی پیش فرض برای دسترسی به دیتابیس ASP.NET Identity است. نکته قابل توجه این است که IdentityDbContext آبجکتی از نوع TUser را می‌پذیرد. TUser می‌تواند هر کلاسی باشد که از IdentityUser ارث بری کرده و آن را گسترش می‌دهد.

در پوشه Models کلاس جدیدی با نام ApplicationDbContext بسازید که از IdentityDbContext ارث بری کرده و از کلاس User استفاده می‌کند.

public class ApplicationDbContext : IdentityDbContext<User>
{
        
}

مدیریت کاربران در ASP.NET Identity توسط کلاسی با نام UserManager انجام می‌شود که در اسمبلی Microsoft.AspNet.Identity.EntityFramework قرار دارد. چیزی که ما در این مرحله نیاز داریم، کلاسی است که از UserManager ارث بری می‌کند و آن را طوری توسعه می‌دهد که از کلاس User استفاده کند.

در پوشه Models کلاس جدیدی با نام UserManager بسازید.

public class UserManager : UserManager<User>
{
        
}

کلمه عبور کاربران بصورت رمز نگاری شده در دیتابیس ذخیره می‌شوند. الگوریتم رمز نگاری SQL Membership با سیستم ASP.NET Identity تفاوت دارد. هنگامی که کاربران قدیمی به سایت وارد می‌شوند، کلمه عبورشان را توسط الگوریتم‌های قدیمی SQL Membership رمزگشایی می‌کنیم، اما کاربران جدید از الگوریتم‌های ASP.NET Identity استفاده خواهند کرد.

کلاس UserManager خاصیتی با نام PasswordHasher دارد. این خاصیت نمونه ای از یک کلاس را ذخیره می‌کند، که اینترفیس IPasswordHasher را پیاده سازی کرده است. این کلاس هنگام تراکنش‌های احراز هویت کاربران استفاده می‌شود تا کلمه‌های عبور را رمزنگاری/رمزگشایی شوند. در کلاس UserManager کلاس جدیدی بنام SQLPasswordHasher بسازید. کد کامل را در لیست زیر مشاهده می‌کنید.

public class SQLPasswordHasher : PasswordHasher
{
        public override string HashPassword(string password)
        {
            return base.HashPassword(password);
        }

        public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            string[] passwordProperties = hashedPassword.Split('|');
            if (passwordProperties.Length != 3)
            {
                return base.VerifyHashedPassword(hashedPassword, providedPassword);
            }
            else
            {
                string passwordHash = passwordProperties[0];
                int passwordformat = 1;
                string salt = passwordProperties[2];
                if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
                {
                    return PasswordVerificationResult.SuccessRehashNeeded;
                }
                else
                {
                    return PasswordVerificationResult.Failed;
                }
            }
        }

        //This is copied from the existing SQL providers and is provided only for back-compat.
        private string EncryptPassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bRet = null;

            if (passwordFormat == 1)
            { // MembershipPasswordFormat.Hashed 
                HashAlgorithm hm = HashAlgorithm.Create("SHA1");
                if (hm is KeyedHashAlgorithm)
                {
                    KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
                    if (kha.Key.Length == bSalt.Length)
                    {
                        kha.Key = bSalt;
                    }
                    else if (kha.Key.Length < bSalt.Length)
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                        kha.Key = bKey;
                    }
                    else
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        for (int iter = 0; iter < bKey.Length; )
                        {
                            int len = Math.Min(bSalt.Length, bKey.Length - iter);
                            Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
                            iter += len;
                        }
                        kha.Key = bKey;
                    }
                    bRet = kha.ComputeHash(bIn);
                }
                else
                {
                    byte[] bAll = new byte[bSalt.Length + bIn.Length];
                    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                    bRet = hm.ComputeHash(bAll);
                }
            }

            return Convert.ToBase64String(bRet);
    }
}



دقت کنید تا فضاهای نام System.Text و System.Security.Cryptography را وارد کرده باشید.

متد EncodePassword کلمه عبور را بر اساس پیاده سازی پیش فرض SQL Membership رمزنگاری می‌کند. این الگوریتم از System.Web گرفته می‌شود. اگر اپلیکیشن قدیمی شما از الگوریتم خاصی استفاده می‌کرده است، همینجا باید آن را منعکس کنید. دو متد دیگر نیز بنام‌های HashPassword و VerifyHashedPassword نیاز داریم. این متدها از EncodePassword برای رمزنگاری کلمه‌های عبور و تایید آنها در دیتابیس استفاده می‌کنند.

سیستم SQL Membership برای رمزنگاری (Hash) کلمه‌های عبور هنگام ثبت نام و تغییر آنها توسط کاربران، از PasswordHash, PasswordSalt و PasswordFormat استفاده می‌کرد. در روند مهاجرت، این سه فیلد در ستون PasswordHash جدول AspNetUsers ذخیره شده و با کاراکتر '|' جدا شده اند. هنگام ورود کاربری به سایت، اگر کله عبور شامل این فیلدها باشد از الگوریتم SQL Membership برای بررسی آن استفاده می‌کنیم. در غیر اینصورت از پیاده سازی پیش فرض ASP.NET Identity استفاده خواهد شد. با این روش، کاربران قدیمی لازم نیست کلمه‌های عبور خود را صرفا بدلیل مهاجرت اپلیکیشن ما تغییر دهند.

کلاس UserManager را مانند قطعه کد زیر بروز رسانی کنید.

public UserManager()
            : base(new UserStore<User>(new ApplicationDbContext()))
        {
            this.PasswordHasher = new SQLPasswordHasher();
 }

ایجاد صفحات جدید مدیریت کاربران

قدم بعدی ایجاد صفحاتی است که به کاربران اجازه ثبت نام و ورود را می‌دهند. صفحات قدیمی SQL Membership از کنترل هایی استفاده می‌کنند که با ASP.NET Identity سازگار نیستند. برای ساختن این صفحات جدید به این مقاله مراجعه کنید. از آنجا که در این مقاله پروژه جدید را ساخته ایم و پکیج‌های لازم را هم نصب کرده ایم، می‌توانید مستقیما به قسمت Adding Web Forms for registering users to your application بروید.
چند تغییر که باید اعمال شوند:
  • فایل‌های Register.aspx.cs و Login.aspx.cs از کلاس UserManager استفاده می‌کنند. این ارجاعات را با کلاس UserManager جدیدی که در پوشه Models ساختید جایگزین کنید.
  • همچنین ارجاعات استفاده از کلاس IdentityUser را به کلاس User که در پوشه Models ساختید تغییر دهید.
  • لازم است توسعه دهنده مقدار ApplicationId را برای کاربران جدید طوری تنظیم کند که با شناسه اپلیکیشن جاری تطابق داشته باشد. برای این کار می‌توانید پیش از ساختن حساب‌های کاربری جدید در فایل Register.aspx.cs ابتدا شناسه اپلیکیشن را بدست آورید و اطلاعات کاربر را بدرستی تنظیم کنید.
مثال: در فایل Register.aspx.cs متد جدیدی تعریف کنید که جدول aspnet_Applications را بررسی میکند و شناسه اپلیکیشن را بر اساس نام اپلیکیشن بدست می‌آورد.
private Guid GetApplicationID()
{
    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString))
            {
                string queryString = "SELECT ApplicationId from aspnet_Applications WHERE ApplicationName = '/'"; //Set application name as in database

                SqlCommand command = new SqlCommand(queryString, connection);
                command.Connection.Open();

                var reader = command.ExecuteReader();
                while (reader.Read())
                {
                    return reader.GetGuid(0);
                }

            return Guid.NewGuid();
        }
}


  حال می‌توانید این مقدار را برای آبجکت کاربر تنظیم کنید.
var currentApplicationId = GetApplicationID();

User user = new User() { UserName = Username.Text,
ApplicationId=currentApplicationId, …};
در این مرحله می‌توانید با استفاده از اطلاعات پیشین وارد سایت شوید، یا کاربران جدیدی ثبت کنید. همچنین اطمینان حاصل کنید که کاربران پیشین در نقش‌های مورد نظر وجود دارند.
مهاجرت به ASP.NET Identity مزایا و قابلیت‌های جدیدی را به شما ارائه می‌کند. مثلا کاربران می‌توانند با استفاده از تامین کنندگان ثالثی مثل Facebook, Google, Microsoft, Twitter و غیره به سایت وارد شوند. اگر به سورس کد این مقاله مراجعه کنید خواهید دید که امکانات OAuth نیز فعال شده اند.
در این مقاله انتقال داده‌های پروفایل کاربران بررسی نشد. اما با استفاده از نکات ذکر شده می‌توانید پروفایل کاربران را هم بسادگی منتقل کنید. کافی است مدل‌های لازم را در پروژه خود تعریف کرده و با استفاده از اسکریپت‌های SQL داده‌ها را انتقال دهید.
مطالب
ساخت دیتابیس sqlite با EF6 Code First
تا نسخه EF6 و minor‌های آن به دلیل عدم پشتیبانی داریور sqlite از migration، ساخت دیتابیس با code first ممکن نیست برای همین مجبور هستند از پیاده سازی‌های خودشان و موجود بودن دیتابیس از قبل با استفاده از EF با آن کار کنند که یکی از مثال‌های آن در این آدرس قرار دارد و سعی دارد کلاسی مشابه sqlitehelper در اندروید که کار ساخت دیتابیس و مدیریت نسخه را دارد بسازد و از آن استفاده کند. البته در EF7 این مشکل حل شده است و تیم دات نت تمهیداتی را برای آن اندیشیده‌اند. در این نوشتار قصد داریم با استفاده از یک کتابخانه که توسط آقای مارک سالین نوشته شده است کار ساخت دیتابیس را آسانتر کنیم. این کتابخانه که با دات نت 4 به بعد کار میکند خیلی راحت می‌تواند دیتابیس شما را به روش Code First ایجاد کند.

در حال حاضر این کتابخانه از مفاهیم زیر پشتیبانی می‌کند:

  • تبدیل کلاس به جدول با پشتیبانی از خصوصیت Table
  • تبدیل پراپرتی‌ها به ستون با پشتیبانی از خصوصیت هایی چون Column,Key,MaxLength,Required,Notmapped,DatabaseGenerated,Index
  • پشتیبانی از primarykey و کلید‌های ترکیبی
  • کلید خارجی و روابط یک  به چند و پشتیبانی از cascade on delete
  • فیلد غیر نال


برای شروع ابتدا کتابخانه مورد نظر را از Nuget با دستور زیر دریافت کنید:
Install-Package SQLite.CodeFirst
خود این دستور باعث می‌شود که وابستگی‌هایش از قبیل sqlite provider‌ها نیز دریافت گردند.
solution من شامل سه پروژه است یکی برای مدل‌ها که شامل کلاس‌های زیر برای تهیه یک دفترچه تلفن ساده است:

Person
 public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public virtual ICollection<PhoneBook> Numbers { get; set; }

    }

PhoneBook
  public class PhoneBook
    {
        public int Id { get; set; }

        public string Field{ get; set; }

        public string Number { get; set; }

        public virtual Person Person { get; set; }
    }

پروژه بعدی به نام سرویس که جهت پیاده سازی کلاس‌های EF است و دیگری هم یک پروژه‌ی WPF جهت تست برنامه.
در پروژه‌ی سرویس ما یک کلاس به نام Context داریم که مفاهیم مربوط به پیاده سازی Context در آن انجام شده است:
public class Context:DbContext
    {
        public Context():base("constr")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            var initializer = new InitialDb(modelBuilder);
            Database.SetInitializer(initializer);        
        }

        public DbSet<PhoneBook> PhoneBook { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
تا به الان چیز جدیدی نداشتیم و همه چیز طبق روال صورت گرفته است؛ ولی دو نکته‌ی مهم در این کد نهفته است:

 اول اینکه در سطر اول متد بازنویسی شده onModelCreating، قرارداد مربوط به نامگذاری جداول را حذف می‌کنیم چرا که در صورت نبودن این خط، اسامی که کلاس sqllite برای آن در نظر خواهد گرفت با اسامی که برای انجام عملیات CURD استفاده می‌شوند متفاوت خواهد بود. برای مثال برای Person جدولی به اسم People خواهد ساخت ولی برای درج، آن را در جدول Person انجام می‌دهد که به خاطر نبودن جدول با خطای چنین جدولی موجود نیست روبرو می‌شویم.

نکته‌ی دوم اینکه در همین کلاس Context ما یک پیاده سازی جدید بر روی کلاس InitialDb داشته ایم که در زیر نمونه کد آن را می‌بینید:
 public class InitialDb:SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists<Context>
    {
        public InitialDb(DbModelBuilder modelBuilder) : base(modelBuilder)
        {
        }

        protected override void Seed(Context context)
        {
            var person = new Person()
            {
                FirstName = "ali",
                LastName = "yeganeh",
                Numbers = new List<PhoneBook>()
                {
                    new PhoneBook()
                    {
                        Field = "Work",
                        Number = "031551234"
                    },
                    new PhoneBook()
                    {
                        Field = "Mobile",
                        Number = "09123456789"
                    },
                    new PhoneBook()
                    {
                        Field = "Home",
                        Number = "031554321"
                    }
                }
            };

            context.Persons.Add(person);
            base.Seed(context);
        }
    }
در این کد کلاس InitialDb از کلاس SqliteCreateDatabaseIfNotExists ارث بری کرده‌است و متد seed آن را هم بازنویسی کرده‌ایم. کلاس SqliteCreateDatabaseIfNotExists برای زمانی کاربر دارد که اگر دیتابیس موجود نیست آن را ایجاد کند، در غیر اینصورت خیر. به غیر از آن، کلاس دیگری به نام SqliteDropCreateDatabaseAlways هم وجود دارد که با هر بار اجرا، جداول قبلی را حذف و مجددا آن‌ها را ایجاد میکند.
سپس در پروژه‌ی اصلی WPF در فایل AppConfig رشته اتصالی مورد نظر را وارد نمایید:
  <connectionStrings>
    <add name="constr" connectionString="data source=.\phonebook.sqlite;foreign keys=true" providerName="System.Data.SQLite" />
  </connectionStrings>
نکته‌ی مهم اینکه با افزودن کتابخانه از طریق nuget فایل app.config به روز می‌شود؛ ولی به نظر می‌رسد که تنظیمات به درستی انجام نمی‌شوند. در صورتیکه به مشکل زیر برخوردید و نتوانستید برنامه را اجرا کنید، کد زیر را که قسمتی از فایل app.config است، مطالعه فرمایید و موارد مربوط به آن را اصلاح کنید:

خطا:
The ADO.NET provider with invariant name 'System.Data.SQLite' is either not registered in the machine or application config file, or could not be loaded

قسمتی از فایل app.config:
<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <remove invariant="System.Data.SQLite" />
       <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
  </system.data>

کد Load پروژه WPF:
 public MainWindow()
        {
            InitializeComponent();
           var context=new Context();

            var list= context.Persons.ToList();

            var s = "";

            foreach (var person in list)
            {
                s += person.FirstName + " " + person.LastName +
            " has these numbers:" + Environment.NewLine;

                foreach (var number in person.Numbers)
                {
                    s += number.Field + " : " + number.Number + Environment.NewLine;
                }

                s += Environment.NewLine;
            }
           MessageBox.Show(s);


        }



دانلود مثال
 
مطالب
تشخیص خودکار کدهای تکراری در یک پروژه Visual Studio


آیا می‌دانید چند درصد از کدهای یک پروژه شما در قسمت‌های مختلف آن تکراری هستند و تا چه حد نیاز به refactoring کدهای موجود جهت مدیریت و نگهداری ساده‌تر از آن پروژه وجود دارد؟
اخیرا پروژه سورس بازی در سایت CodePlex به نام Clone detective ارائه شده است که این کار را به صورت خودکار با یکپارچه شدن با Visual studio برای شما انجام می‌دهد. این افزودنی از آدرس زیر قابل دریافت است:
http://www.codeplex.com/CloneDetectiveVS


بهترین آموزش نحوه استفاده از آن هم از طریق ویدیوی زیر در دسترس است:
مشاهده

در نگارش فعلی آن تنها پروژه‌های سی شارپ پشتیبانی می‌شوند و در نگارش‌های آتی آن قرار است VB.net و CPP نیز افزوده شوند.

به چه دلیلی به این ابزار نیاز داریم؟
فرض کنید کلاسی را جهت انجام مقصودی خاص توسعه داده‌اید. در کلاسی دیگر برای اتمام آن، 15 سطر از یکی از توابع کلاس اول را کپی کرده و مورد استفاده قرار داده‌اید. در یک پروژه بزرگ از این موارد شاید زیاد رخ دهد (خصوصا در یک کار تیمی که ممکن است قسمتی از کار شما به‌عنوان پایه اولیه کاری دیگر مورد استفاده قرار گیرد). پس از مدتی، تغییراتی را در کلاس اول ایجاد کرده و یک سری از عیوب آن 15 سطر را که جزئی از یک تابع است برطرف خواهید کرد. بسیار هم خوب! آیا این پایان کار است؟ خیر!
آیا این مورد به کل پروژه منتقل شده است؟ آیا نگهداری یک پروژه بزرگ که دارای قسمت‌های تکراری زیادی است کار ساده‌ای است؟

علاوه بر ابزار فوق، برنامه دیگری نیز جهت تشخیص کدهای تکراری در یک پروژه به نام Simian موجود است. Simian را از آدرس زیر می‌توانید دریافت کنید:
http://www.redhillconsulting.com.au/products/simian/overview.html
این ابزار به صورت یک افزودنی VS.net ارائه نشده است اما می‌توان از طریق منوی tools آنرا به مجموعه ابزارهای مورد استفاده اضافه کرد. نحوه انجام اینکار به صورت مصور در وبلاگ زیر بیان شده است:
مشاهده

همچنین از ابزارهای دیگری از این دست می‌توان به برنامه رایگان CCFinder‌ اشاره کرد: (ثبت نام دریافت آن رایگان است)
http://www.ccfinder.net


مطالب
مروری بر مفاهیم مقدماتی NoSQL
هدف از این مبحث، آشنایی با مفاهیم پایه‌ای اغلب بانک‌های اطلاعاتی NoSQL است که به صورت مشترکی در تمام آن‌ها بکار رفته است. برای مثال بانک‌های اطلاعاتی NoSQL چگونه مباحث یکپارچگی اطلاعات را مدیریت می‌کنند؟ نحوه ایندکس نمودن اطلاعات در آن‌ها چگونه است؟ چگونه از اطلاعات کوئری می‌گیرند؟ الگوریتم‌های محاسباتی مانند MapReduce چیستند و چگونه در اینگونه بانک‌های اطلاعاتی بکار رفته‌‌اند؟ همچنین الگوهای Sharding و Partitioning  که در اغلب بانک‌های اطلاعاتی NoSQL مشترکند، به چه نحوی پیاده سازی شده‌اند.


لیست مشترکات بانک‌های اطلاعاتی NoSQL

قبل از اینکه بخواهیم وارد ریز جزئیات بانک‌های اطلاعاتی NoSQL شویم، نیاز است لیست و سرفصلی از مفاهیم اصلی و مشترک بین اینگونه بانک‌های اطلاعاتی را تدارک ببینیم که شامل موارد ذیل می‌شود:

الف) Non-Relational یا غیر رابطه‌ای
از کلمه NoSQL عموما اینطور برداشت می‌شود که در اینجا دیگر خبری از SQL نویسی نیست که در عمل برداشت نادرستی است. شاید جالب باشد که بدانید، تعدادی از بانک‌های اطلاعاتی NoSQL از زبان SQL نیز به عنوان اینترفیسی برای نوشتن کوئری‌های مرتبط، پشتیبانی می‌کنند.
کلمه NoSQL بیشتر به Non-Relational یا غیر رابطه‌ای بودن اینگونه بانک‌های اطلاعاتی بر می‌گردد. مباحثی مانند مدل‌های داده‌ای نرمال شده، اتصالات و Join جداول، در دنیای NoSQL وجود خارجی ندارند.

ب) Non-schematized/schema free یا بدون اسکیما
مفهوم مهم و مشترک دیگری که در بین بانک‌های اطلاعاتی NoSQL وجود دارد، بدون اسکیما بودن اطلاعات آن‌ها است. به این معنا که با حرکت از رکورد یک به رکورد دو،  ممکن است با دو ساختار داده‌ای متفاوت مواجه شوید.

ج) Eventual consistency یا عاقبت یک دست شدن
عاقبت یک دست شدن، به معنای دریافت دستوری از شما و نحوه پاسخ دادن به آن (یا حتی پاسخ ندادن به آن) از طرف بانک اطلاعاتی NoSQL است. برای مثال، زمانیکه یک رکورد جدید را اضافه می‌کنید، یا اطلاعات موجودی را به روز رسانی خواهید کرد، اغلب بانک‌های اطلاعاتی NoSQL این دستور را بسیار سریع دریافت و پردازش خواهند کرد. اما تفاوت است بین دریافت پیام و پردازش واقعی آن در اینجا.
اکثر بانک‌های اطلاعاتی NoSQL، پردازش و اعمال واقعی دستورات دریافتی را با یک تاخیر انجام می‌دهند. به این ترتیب می‌توان خیلی سریع به بانک اطلاعاتی اعلام کرد که چه می‌خواهیم و بانک اطلاعاتی بلافاصله مجددا کنترل را به شما بازخواهد گرداند. اما اعمال و انتشار واقعی این دستور، مدتی زمان خواهد برد.

د) Open source یا منبع باز بودن
اغلب بانک‌های اطلاعاتی NoSQL موجود، منبع باز هستند که علاوه بر بهره بردن از مزایای اینگونه پروژه‌ها، استفاده کنندگان سورس باز دیگری را نیز ترغیب به استفاده از آن‌ها کرده‌اند.

ه) Distributed یا توزیع شده
هرچند امکان پیاده سازی توزیع شده بانک‌های اطلاعاتی رابطه‌ای نیز وجود دارد، اما نیاز به تنظیمات قابل توجهی برای حصول این امر می‌باشد. در دنیای NoSQL، توزیع شده بودن جزئی از استاندارد تهیه اینگونه بانک‌های اطلاعاتی است و بر اساس این مدل ذهنی شکل گرفته‌اند. به این معنا که اطلاعات را می‌توان بین چندین سیستم تقسیم کرد، که حتی این سیستم‌ها ممکن است فواصل جغرافیایی قابل توجهی نیز با یکدیگر داشته باشند.

و) Web scale یا مناسب برای برنامه‌های تحت وب پر کاربر
امروزه بسیاری از کمپانی‌های بزرگ اینترنتی، برای مدیریت تعداد بالایی از کاربران همزمان خود، مانند فیس‌بوک، یاهو، گوگل، Linkedin، مایکروسافت و غیره، نیاز به بانک‌های اطلاعاتی پیدا کرده‌اند که باید در مقابل این حجم عظیم درخواست‌ها و همچنین اطلاعاتی که دارند، بسیار بسیار سریع پاسخ دهند. به همین جهت بانک‌های اطلاعاتی NoSQL ابداع شده‌اند تا بتوان برای این نوع سناریوها پاسخی را ارائه داد.
و نکته مهم دیگر اینجا است که خود این کمپانی‌های بزرگ اینترنتی، بزرگترین توسعه دهنده‌های بانک‌های اطلاعاتی NoSQL نیز هستند.



نحوه مدیریت یکپارچگی اطلاعات در بانک‌های اطلاعاتی NoSQL

مدیریت یکپارچگی اطلاعات بانک‌های اطلاعاتی NoSQL به علت ذات و طراحی توزیع شده آن‌ها، با نحوه مدیریت یکپارچگی اطلاعات بانک‌های اطلاعاتی رابطه‌ای متفاوت است. اینجا است که تئوری خاصی به نام CAP مطرح می‌شود که شامل یکپارچگی یا Consistency به همراه Availability یا دسترسی پذیری (همیشه برقرار بودن) و partition tolerance یا توزیع پذیری است. در تئوری CAP مطرح می‌شود که هر بانک اطلاعاتی خاص، تنها دو مورد از سه مورد مطرح شده را می‌تواند با هم پوشش دهد.
به این ترتیب بانک‌های اطلاعاتی رابطه‌ای عموما دو مورد C و P یا یکپارچگی (Consistency) و partition tolerance یا میزان تحمل تقسیم شدن اطلاعات را ارائه می‌دهند. اما بانک‌های اطلاعاتی NoSQL از این تئوری، تنها دو مورد A و P را پوشش می‌دهند (دسترسی پذیری و توزیع پذیری مطلوب).
بنابراین مفهومی به نام ACID که در بانک‌های اطلاعاتی رابطه‌ای ضامن یکپارچگی اطلاعات آن‌ها است، در دنیای NoSQL وجود خارجی ندارد. کلمه ACID مخفف موارد ذیل است:
Atomicity، Consistency، Isolation و Durability
ACID در بانک‌های اطلاعاتی رابطه‌ای تضمین شده است. در این نوع سیستم‌ها، با ایجاد تراکنش‌ها، مباحث ایزوله سازی و یکپارچگی اطلاعات به نحو مطلوبی مدیریت می‌گردد؛ اما دنیای NoSQL، دسترسی پذیری را به یکپارچگی ترجیح داده است و به همین جهت پیشتر مطرح شد که مفهوم «Eventual consistency یا عاقبت یک دست شدن» در این نوع بانک‌های اطلاعاتی در پشت صحنه بکار گرفته می‌شود. یک مثال دنیای واقعی از عاقبت یک دست شدن اطلاعات را حتما در مباحث DNS مطالعه کرده‌اید. زمانیکه یک رکورد DNS اضافه می‌شود یا به روز خواهد شد، اعمال این دستورات در سراسر دنیا به یکباره و همزمان نیست. هرچند اعمال این اطلاعات جدید در یک نود شبکه ممکن است آنی باشد، اما پخش و توزیع آن در سراسر سرورهای DNS دنیا، مدتی زمان خواهد برد (گاهی تا یک روز یا بیشتر).
به همین جهت است که بانک‌های اطلاعاتی رابطه‌ای در حجم‌های عظیم اطلاعات و تعداد کاربران همزمان بالا، کند عمل می‌کنند. حجم اطلاعات بالا است، مدتی زمان خواهد برد تا تغییرات اعمال شوند، و چون مفهوم ACID در این نوع بانک‌های اطلاعاتی تضمین شده است، کاربران باید مدتی منتظر بمانند و نمونه‌ای از آن‌ها را با dead lockهای شایع، احتمالا پیشتر بررسی یا تجربه کرده‌اید. در مقابل، بانک‌های اطلاعاتی NoSQL بجای یکپارچگی، دسترسی پذیری را اولویت اول خود می‌دانند و نه یکپارچگی اطلاعات را. در یک بانک اطلاعاتی NoSQL، دستور ثبت اطلاعات دریافت می‌شود (این مرحله آنی است)، اما اعمال نهایی آن آنی نیست و مدتی زمان خواهد برد تا تمام اطلاعات در کلیه سرورها یک دست شوند.



نحوه مدیریت Indexing اطلاعات در بانک‌های اطلاعاتی NoSQL

اغلب بانک‌های اطلاعاتی NoSQL تنها بر اساس اطلاعات کلیدهای اصلی جداول آن‌ها index می‌شوند (البته نام خاصی به نام «جدول»، بسته به نوع بانک اطلاعاتی NoSQL ممکن است متفاوت باشد، اما منظور ظرف دربرگیرنده تعدادی رکورد است در اینجا). این ایندکس نیز از نوع clustered است. به این معنا که اطلاعات به صورت فیزیکی، بر همین مبنا ذخیره و مرتب خواهند شد.
یک مثال: بانک اطلاعاتی NoSQL خاصی به نام Hbase که بر فراز Hadoop distributed file system طراحی شده است، دقیقا به همین روش عمل می‌کند. این فایل سیستم، تنها از روش Append only برای ذخیره سازی اطلاعات استفاده می‌کند و در آن مفهوم دسترسی اتفاقی یا random access پیاده سازی نشده است. در این حالت، تمام نوشتن‌ها در بافر، لاگ می‌شوند و در بازه‌های زمانی متناوب و مشخصی سبب باز تولید فایل‌های موجود و مرتب سازی مجدد آن‌ها از ابتدا خواهند شد. دسترسی به این اطلاعات پس از تکمیل نوشتن، به علت مرتب سازی فیزیکی که صورت گرفته، بسیار سریع است. همچنین مصرف کننده سیستم نیز چون بلافاصله پس از ثبت اطلاعات در بافر سیستم، کنترل را به دست می‌گیرد، احساس کار با سیستمی را خواهد داشت که بسیار سریع است.
به علاوه Indexهای دیگری نیز وجود دارند که بر اساس کلیدهای اصلی جداول تولید نمی‌شوند و به آن‌ها ایندکس‌های ثانویه یا secondary indexes نیز گفته می‌شود و تنها تعداد محدودی از بانک‌های اطلاعاتی NoSQL از آن‌ها پشتیبانی می‌کنند. این مساله هم از اینجا ناشی می‌شود که با توجه به بدون اسکیما بودن جداول بانک‌های اطلاعاتی NoSQL، چگونه می‌توان اطلاعاتی را ایندکس کرد که ممکن است در رکورد دیگری، ساختار متناظر با آن اصلا وجود خارجی نداشته باشد.



نحوه پردازش Queries در بانک‌های اطلاعاتی NoSQL

بانک‌های اطلاعاتی NoSQL عموما از زبان کوئری خاصی پشتیبانی نمی‌کنند. در اینجا باید به اطلاعات به شکل فایل‌هایی که حاوی رکوردها هستند نگاه کرد. به این ترتیب برای پردازش و یافتن اطلاعات درون این فایل‌ها، نیاز به ایجاد برنامه‌هایی است که این فایل‌ها را گشوده و بر اساس منطق خاصی، اطلاعات مورد نظر را استخراج کنند. گاهی از اوقات زبان SQL نیز پشتیبانی می‌شود ولی آنچنان عمومیت ندارد. الگوریتمی که در این برنامه‌ها بکار گرفته می‌شود، Map Reduce نام دارد.
Map Reduce به معنای نوشتن کدی است، با دو تابع. اولین تابع اصطلاحا Map step یا مرحله نگاشت نام دارد. در این مرحله کوئری به قسمت‌های کوچکتری خرد شده و بر روی سیستم‌های توزیع شده به صورت موازی اجرا می‌شود. مرحله بعد Reduce step نام دارد که در آن، نتیجه دریافتی حاصل از کوئری‌های اجرا شده بر روی سیستم‌های مختلف، با هم یکی خواهند شد.
این روش برای نمونه در سیستم Hadoop بسیار مرسوم است. Hadoop دارای یک فایل سیستم توزیع شده است (که پیشتر در مورد آن بحث شد) به همراه یک موتور Map Reduce توکار. همچنین رده دیگری از بانک‌های اطلاعاتی NoSQL، اصطلاحا Wide column store نام دارند (مانند Hbase) که عموما به همراه Hadoop بکارگرفته می‌شوند. موتور Map Reduce متعلق به Hadoop بر روی جداول Hbase اجرا می‌شوند.
به علاوه Amazon web services دارای سرویسی است به نام Elastic map reduce یا EMR که در حقیقت مجموعه‌ی پردازش ابری است که بر مبنای Hadoop کار می‌کند. این سرویس قادر است با بانک‌های اطلاعاتی NoSQL دیگر و یا حتی بانک‌های اطلاعاتی رابطه‌ای نیز کار کند.
بنابراین MapReduce، یک بانک اطلاعاتی نیست؛ بلکه یک روش پردازش اطلاعات است که فایل‌ها را به عنوان ورودی دریافت کرده و یک فایل را به عنوان خروجی تولید می‌کند. از آنجائیکه بسیاری از بانک‌های اطلاعاتی NoSQL کار عمده‌اشان، ایجاد و تغییر فایل‌ها است، اغلب جداول اطلاعات آن‌ها ورودی و خروجی‌های معتبری برای یک موتور Map reduce به حساب می‌آیند.
در این بین، افزونه‌ای برای Hadoop به نام Hive طراحی شده است که با ارائه HiveSQL، امکان نوشتن کوئری‌هایی SQL مانند را بر فراز موتور‌های Map reduce ممکن می‌سازد. این افزونه با Hive tables خاص خودش و یا با Hbase سازگار است.



آشنایی مقدماتی با مفاهیمی مانند الگوهای Sharding و Partitioning در بانک‌های اطلاعاتی NoSQL

Sharding (شاردینگ تلفظ می‌شود) یک الگوی تقسیم اطلاعات بر روی چندین سرور است که اساس توزیع شده بودن بانک‌های اطلاعاتی NoSQL را تشکیل می‌دهد. این نوع تقسیم اطلاعات، از کوئری‌هایی به نام Fan-out پشتیبانی می‌کند. به این معنا که شما کوئری خود را به نود اصلی ارسال می‌کنید و سپس به کمک موتور‌های Map reduce، این کوئری بر روی سرورهای مختلف اجرا شده و نتیجه نهایی جمع آوری خواهد شد. به این ترتیب تقسیم اطلاعات، صرفا به معنای قرار دادن یک سری فایل بر روی سرورهای مختلف نیست، بلکه هر کدام از این سرورها به صورت مستقل نیز قابلیت پردازش اطلاعات را دارند.
امکان تکثیر و همچنین replication هر کدام از سرورها نیز وجود دارد که قابلیت بازیابی سریع و مقاومت در برابر خرابی‌ها و مشکلات را افزایش می‌دهند.
از آنجائیکه Shardها را می‌توان در سرورهای بسیار متفاوت و گسترده‌ای از لحاظ جغرافیایی قرار داد، هر Shard می‌تواند همانند مفاهیم CDN نیز عمل کند؛ به این معنا که می‌توان Shard مورد نیاز سروری خاص را در محلی نزدیک‌تر به او قرار داد. به این ترتیب سرعت عملیات افزایش یافته و همچنین بار شبکه نیز کاهش می‌یابد.
مطالب
مقدمه‌ای بر Docker
Docker به صورت ساده، پلتفرمی است که به سادگی قابلیت ساخت، انتقال و اجرا کردن Image‌ها را در اختیار دارد و همچنین به صورت native درون سرور‌های لینوکسی و ویندوزی اجرا میشود؛ به علاوه اینکه در محیط محلی، برای تست نیز بر روی ماشین‌های ویندوزی و مک از طریق virtual machine قابل اجراست.

دو مفهوم اساسی در محیط Docker وجود دارند که دانستن آن‌ها ضروری است: Image و Container
image عملا چیزی است که از آن برای Build یک Container استفاده می‌شود. image دارای یک سری فایل‌های لازم و اساسی است که باعث می‌شود بر روی یک Operation System اجرا شود؛ مثل Ubuntu یا Windows. بنابراین شما Application Framework خود را خواهید داشت و همچنین Databaseی که با آن کار میکند. بنابراین قابلیت استفاده از زبان‌ها و فریم ورک‌های مختلف چون Asp.net Core, Nodejs, Python و غیره را خواهد داشت. یک image به خودی خود غیر قابل استفاده است تا زمانیکه بر روی یک Container توزیع شده باشد، تا قابلیت اجرا پیدا کند. بنابراین نقطه‌ی شروع اصلی اجرایی یک برنامه با Container مربوط به آن میباشد.
به صورت خلاصه Image یک template از نوع Readonly است که ترکیبی از لایه‌های File System می‌باشد، به همراه فایل‌های share شده‌ی دیگر (از قبیل فریم ورک‌ها و ...) که میتوانند یک Docker Container Instance را تولید نمایند.
Container یک محیط امن و ایزوله است که به وسیله‌ی image ساخته شده است و میتواند اجرا، متوقف، منتقل و یا حذف شود (بطور قابل ملاحظه‌ای اجرا کردن و متوقف کردن آن سریع میباشد).


تفاوت Docker Containers و Virtual Machines

Virtual Machines همیشه بر روی Host Operation System اجرا میشوند (که می‌تواند بر روی ویندوز یا لینوکس باشد) و بعد از آن اجرای Guest OS بر روی سطحی به نام Hypervisor. پس میتوان گفت یک کپی کامل از سیستم عامل است که که بر روی hypervisor اجرا میشود و خودش نیز بر روی سخت افزار اجرا میشود. بنابراین میتوان مثل شکل زیر، یک App داشت که عملا یک سری باینری و کتابخانه است و اگر قرار باشد بر روی سیستم عامل‌های مختلفی کار کند، احتیاج به کپی کردن کل آن می‌باشد و بطور واضحی زمان و هزینه‌ی بیشتری برای بالا آوردن آن لازم است.
اما بر خلاف آن، داکر با استفاده از ابزاری به نام Docker Engine کار میکند که میتواند Container‌های مختلفی از OS‌های مختلف را اجرا نماید و نیازی به کپی گرفتن از کل سیستم عامل برای اجرای هر container نخواهد بود.


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


چطور Docker میتواند سریعتر از Virtual Machine‌ها عمل کند ؟

داکر از چیزی به نام Copy On Write استفاده میکند؛ به معنای کپی کردن همزمان با نوشتن. همانطور که گفته شد هر Container از یک Image ساخته میشود و عملا Imageها همان FileSystem‌های از قبل تولید شده هستند و هر کدام از لایه‌ای از کتابخانه‌ها استفاده میکنند که برای اجرای برنامه‌های کاربردی مورد استفاده قرار می‌گیرند. سرور آپاچی را در نظر بگیرید، به عنوان یک فایل image که FileSystem بر روی آن ذخیره شده‌است. با نصب Php یک لایه بر روی لایه دیگر ایجاد شده و فقط تغییرات جدید به آن اضافه خواهند شد و حال اگر بخواهید تغییری را بر روی source code خود بدهید، عملا فقط آن تغییر به Image و FileSystem اضافه خواهد شد. این معماری لایه لایه باعث تولید یک FileSystem بصورت read-only میشود که شامل لایه‌های متفاوتی است و سبب کم حجم شدن آن، بالا رفتن سرعت آن می‌شود و همچنین با استفاده از Caching، قدرت زیادی را بدان می‌بخشد.


پس همانطور که در شکل فوق مشاهده میکنید، هر image از لایه‌های مختلفی تشکیل شده است و توانایی به اشتراک گذاشتن این لایه‌های متمایز از یکدیگر در Container‌ها وجود دارد.


بنابراین طبق شکل فوق، بحث را اینگونه خلاصه میکنیم که هر Image از ترکیبی از لایه‌هایی از نوع read-only تشکیل شده است و با اضافه شدن Container، عملا یک لایه‌ی دیگری که قابلیت read/write را دارد بر روی آن اضافه میشود و درون آن source code میتواند قرار گیرد و اینکه بر مبنای شکل زیر میبینید که قابلیت به اشتراک گذاری Image layer‌ها به Container‌های مختلف تعبیه شده است که باعث میشود لایه‌ی نصب شده بر روی سیستم، بصورت اشتراکی قابل استفاده‌ی مجدد باشد و فضای دیسک کمتری، به علاوه سرعت اجرای بالاتری را داشته باشد. هر لایه یک مقدار هش شده‌ی یکتایی را در اختیار دارد تا از لایه‌های دیگر تمیز داده شود و قابل شناسایی باشد.




داکر در شبکه چگونه کار میکند؟

ضمنا نکته‌ی قابل توجه که در مقاله‌های بعدی به صورت عملی به آن میپردازیم این است که با استفاده از داکر میتوانیم وب سرورهایی را بر روی Container‌های مختلفی داشته باشیم که همگی بر روی پورت بطور مثال 80 هستند؛ طوری که درون هر Container بدلیل ایزوله بودن پروسس‌های مخصوص Container مربوط به خود، به پورت‌های باز داخل آن شبکه دسترسی دارند و میتوانند پورت در نظر گرفته شده‌ی درون Container را با پورت دیگری بیرون Container به اصطلاح Expose نمایند.
ضمن اینکه نکته‌ی دیگری که وجود دارد، ارتباط Container‌ها با یکدیگر است. برای مثال یک Container برای Database و دیگری برای WebApp میباشد که باید به همدیگر link شده تا قابل استفاده گردند و عملا نیازی به نوشتن ip یکدیگر در این حالت وجود ندارد. البته راه‌های دیگری از قبیل Compose کردن نیز وجود دارد که در ادامه بیشتر با آن‌ها آشنا خواهیم شد.


Docker Volume چیست؟

بحث دیگری که وجود دارد، Volumeها هستند که قسمتی از FileSystem‌ها میباشند و بصورت ساده، مثال کاربردی‌اش میتواند قسمتی از یک سیستم و دایرکتوری خاصی را بر روی Container خاصی Map کردن باشد و عملا داخل آن دایرکتوری میتواند source code بوده باشد (یکی از راه‌های ممکن برای map کردن source code به container) و بر روی Container ایجاد شود.
فوایدی که با استفاده از Volume‌ها میتوان به آن رسید از قبیل موارد زیر میباشند:
قابلیت به اشتراک گذاری یک Volume بین Container‌های مختلف که به شدت میتواند قابل استفاده باشد.
Data Volume‌ها ماندگار هستند. یعنی حتی بعد از اینکه Container مربوطه را حذف نمایید، volume مربوط به آن بطور اتوماتیک حذف نمیشود (مگر اینکه خودتان دستور حذف کردن آن را وارد نمایید). پس عملا قابلیت استفاده‌ی مجدد را نیز خواهد داشت.

طبق شکل فوق ما میتوانیم درون یک container یک volume داشته باشیم. وقتی ما چیزی را درون آن مینویسیم عملا داریم در قسمت خاصی به نام Docker Host عمل write کردن را انجام میدهیم که باعث میشود داکر متوجه آن شود. وقتی اسمی را به یک Volume انتساب میدهیم همانند /var/www، در واقع یک اسم مستعار (alias) میباشد که اشاره میکند به این Docker host موجود. در ادامه بیشتر با Volume‌ها آشنا خواهیم شد. 


DockerFile و ساخت image‌ها چگونه است؟

روش دیگر برای اجرای source code در داکر، ساخت یک image اختصاصی از آن و اجرا کردن آن بر روی یک container مجزا است.  با استفاده از DockerFile میتوانید image‌های خود را build کرده که عملا هر image در آخر باید به یک سیستم عامل برسد و همانطور که گفته شد به صورت لایه‌ای کار میکنند و مراتب اجرای آن از قبیل working directory و expose کردن بر روی پورتی خاص، همچنین استفاده از Environment Variable‌ها میباشد و همچنین با استفاده از DockerHub (که نسخه‌ی enterprise نیز دارد) میتوان image‌های ساخته شده را بر روی cloud نگه داشت و همه‌ی اعضای تیم از یک image بخصوص استفاده کنند؛ برای مثال همه‌ی اعضای تیم از یک نسخه‌ی Nodejs استفاده کنند و اشتباها بر روی ماشین‌های توسعه‌ی مختلف برنامه نویسان، از نسخه‌های مختلفی استفاده نشود و همچنین روند به‌روز رسانی به سادگی انجام گیرد.


مزایای Docker برای برنامه نویسان

فرض کنید که یک App Service از Azure تهیه کرده باشید. تست‌های unit, integration, acceptance را انجام داده و با خیال راحت Container خود را از طریق برای مثال Visual studio team service بر روی App service به صورت انتشار از طریق مدل Continuous Integration و  Continuous Deployment داشته باشید. پس عملا داکر به Devops بودن محیط و چابک بودن تیم توسعه کمک شایانی کرده و فرآیند‌های سخت و زمانبر انتقال Codeها از محیط توسعه به محیط انتشار را تسریع میبخشد.
بنابراین از داکر به راحتی میتوان در محیط Production نیز استفاده کرد و مزایای فوق العاده ای را برای برنامه نویسان ارائه کرده است. بطور مثال فرض کنید در تولید نرم‌افزار یک Web server ، تعدادی Database و یک Caching server که کانفیگ کردن، اجرا و ... به صورت عادی بسیار صعب و مشکل ساز بوده را به راحتی میتوان اجرا نمود. ضمن اینکه ممکن است هر کدام از ابزارهایی که استفاده شده، فقط مخصوص سیستم عاملی خاص باشد که قاعدتا احتیاج به بالا آوردن Virtual Machine خواهید بود و در سناریو‌های خاصی مثل سیستم هایی با معماری Microservice که هر کدام از این ریز سرویس‌ها ممکن است زبان، فریم ورک، دیتابیس و ... مخصوص به خود را داشته باشند، عملا کار بسیار سخت و پر هزینه خواهد بود (ضمن اینکه استفاده‌ی همزمان از چند Virtual Machine در کنار هم در محیط توسعه، حجم زیادی از memory و disk سیستم شما را خواهد گرفت و شما را مجبور به ارتقای سیستم خود خواهد کرد!).

مشکل دیگری که Docker آن را حل کرده، Conflict‌های ورژن‌های مختلف ابزار‌های مورد استفاده است. به راحتی میتوان Containerی از Image‌ها را به صورت ایزوله با ورژن‌های مختلفی ایجاد کرد تا بطور کامل برنامه نویسان را از مشکل همیشگی به‌روزرسانی‌ها و Role-back کردن‌ها آسوده خاطر نماید. 

از آنجایی که داکر قابلیت اجرای در محیط production را نیز دارد، عملا محیط Development با محیط Production تفاوتی ندارد و این جمله‌ی معروف که «در سیستم من کار میکند اما در نسخه‌ی انتشار داده شده خیر» دیگر اتفاق نخواهد افتاد.

به راحتی میتوانید از یک Image خاص، Containerهای ایزوله‌ی متفاوتی را ساخته و همگی آنها را در کنار هم اجرا نمود و مورد تست و ارزیابی قرار داد.


Dokcer hub

مخرنی است از هزاران Image آماده از قبیل سیستم عامل، فریم ورک و... که قابلیت استفاده‌ی مجدد خواهد داشت. همچنین شما میتوانید Image‌های خود را نیز بدان اضافه نموده تا دیگران از آن استفاده نمایند. استفاده از مخزن‌های public آن رایگان میباشد. از آنجایی که Docker یک محصول متن باز و رایگان است، یک بخش از درآمد‌های آن از فروش اختصاصی مخزن‌ها در DokcerHub میباشد (چیزی شبیه به Private Repository در Github).
بیشتر از این به مفاهیم نمیپردازیم. برای مطالعه‌ی بیشتر، کتاب فوق العاده‌ی Mastering Docker را پیشنهاد میکنم. 


شروع به کار با Docker

بعد از نصب کردن نسخه‌ی رسمی Docker و باز کردن ترمینال مربوطه، اولین دستوراتی را که باید با آن آشنا باشیم، شامل موارد زیر میباشد:

لیست Image‌های کش شده‌ی بر روی سیستم:
 docker images
لیست container‌های در حال اجرای بر روی ماشین محلی:
 docker ps
بعد از تست کردن دو دستور فوق مشاهده میکنید که هیچ image و containerی بر روی سیستم شما وجود ندارد.

برای آزمایش کردن و نصب اولین image، دستور زیر را وارد میکنیم (میتوانید اطلاعات بیشتری از imageها را در dockerHub پیدا کنید). من در اینجا  kitematic/hello-world-nginx را به عنوان image از مخزن dokcerhub، بر روی سیستم خود pull کرده‌ام (این یک نسخه‌ی بسیار سبک از کانتینر nginx میباشد).
 docker pull kitematic/hello-world-nginx
بعد از اجرای دوباره‌ی دستور docker images مشاهده میکنید که image مربوطه بر روی سیستم شما نصب شده است.
حال وقت اجرای این image و توزیع آن بر روی container میباشد که با استفاده از دستور زیر است:
 docker run -p 80:80 kitematic/hello-world-nginx
پرچم p- برای مقدار دهی پورت خارجی و داخلی میباشد و بعد از آن هم که نام image مربوطه برای اجرای container میباشد (فلگ‌های خیلی بیشتر و تخصصی‌تری در رابطه با اجرا وجود دارند که در ادامه بیشتر مورد بحث قرار می‌گیرند) .

بعد از اجرای این دستور میتوانید با وارد کردن ip مربوط به virtual machine ساخته شده بر روی سیستم خود (اگر از مک یا ویندوز استفاده میکنید احتمالا 192.168.99.100 خواهد بود) که البته با دستور docker-machine ip میتوانید آن را پیدا کنید و وارد کردن آن بر روی مرورگر خود، تصویری مثل زیر را مشاهده کنید:

بدین معناست که container شما اجرا شده و قابلیت مورد استفاده قرار گرفتن را خواهد داشت. حال اگر دستور docker ps را مجددا وارد نمایید، اطلاعات این container را از نوع id, status port و غیره، مشاهده خواهید کرد.
پاسخ به بازخورد‌های پروژه‌ها
NetSqlAzMan
آیا میتوان این پروژه هارو در سطح بیزینس هم اعمال کرد؟
نظرات اشتراک‌ها
به روز رسانی فایل OPML وبلاگ‌های IT ایرانی؛ اردیبهشت 93
این فایل XML به روز شده، به صورت یک پروژه‌ی سورس باز در GitHub قرار گرفت: IranianITBloggers  
اگر فکر می‌کنید لینکی در آن نباید باشد و یا باید اضافه شود، آن‌را به صورت یک pull request جدید ارسال کنید.
نظرات مطالب
برنامه نویسی اندروید با Xamarin.Android - قسمت سوم
با تشکر از زحمات شما. اگر مثال هر قسمت را به صورت یک فایل zip ارسال کنید (یا حتی در GitHub قرار بدید)، خیلی بهتر خواهد شد (البته فقط سورس منهای فایل‌های باینری).
نظرات مطالب
استفاده از چندین Context در EF 6 Code first
خسته نباشید. سناریوی بنده این است که میخوام سایتی طراحی کنم که کاملا ماژولار باشد یعنی هر بخش رو به صورت user controller طراحی و در هسته اصلی لود کنم و هر کدوم از این کنترلرها جداول مخصوص به خودشون رو دارن توی بانک اطلاعاتی که خوب طبیعتا کلاس POCO و DBContext مخصوص هر ماژول باید توی سورس کد خود ماژول نوشته بشه. با فرض اینکه بعد از کامپایل پروژه دیگه دسترسی به migration نداریم میتونین بنده رو راهنمایی کنین که چطور میتونم با این روشی که فرمودید جداول جداگانه هر کنترلر یا ماژول رو به بانک اضافه کنم بدون دسترسی به migration ؟ خودم هر راهی به ذهنم رسید انجام دادم ولی همچنان ارور تغییر در کلاس‌های POCO را میدهد سایت.
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
پیشنیازها
- آشنایی با Angular CLI
- آشنایی با مسیریابی‌ها در Angular

همچنین اگر پیشتر Angular CLI را نصب کرده‌اید، قسمت «به روز رسانی Angular CLI» ذکر شده‌ی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.1.2@ استفاده شده‌است.


فناوری‌های مختلف کار با فرم‌ها در Angular

Angular (که خلاصه شده‌ی نام تمام نگارش‌های پس از Angular 2 است)، به همراه دو فناوری توکار کار با فرم‌ها است:
الف) فرم‌های مبتنی بر قالب‌ها یا Template driven forms
در اینجا عمده‌ی کار تعاریف فرم‌ها، در قالب‌های HTML ایی کامپوننت‌ها به همراه data binding انجام می‌شود. کار با آن ساده‌تر است و به همراه حداقل کدنویسی در قسمت کامپوننت‌های برنامه است؛ چون two-way data binding بسیاری از مسایل را به صورت خودکار مدیریت می‌کند. همچنین این روش برای کسانیکه با Angular 1.x کار کرده باشند، آشناتر است.
ب) فرم‌های واکنشی یا Reactive forms
در اینجا نیز همانند حالت الف کار تعریف ابتدایی فرم در قالب‌های HTML ایی کامپوننت‌ها انجام می‌شود. اما در اینجا نیاز است مدل فرم را توسط کدهای TypeScript کامپوننت نیز ایجاد کرد و با قالب HTML ایی هماهنگ نمود (به همین جهت به آن model driven forms هم می‌گویند). مزیت این روش نسبت به حالت الف، سادگی Unit testing و همچنین امکان تعریف اعتبارسنجی‌های پیچیده‌است. به علاوه در این حالت می‌توان فرم‌های پویایی را نیز طراحی کرد.

ما در این سری حالت Template driven forms را بررسی خواهیم کرد.


ایجاد ساختار اولیه‌ی مثال این سری

در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام angular-template-driven-forms-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 > ng new angular-template-driven-forms-lab --routing
به علاوه، قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید، تا بوت استرپ نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
 > npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب، به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد؛ به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],

سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا زمانیکه کاربر ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد.
همچنین مدیریت مسیریابی آدرس‌های ناموجود در سایت نیز با تعریف ** صورت گرفته‌است.

زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <a class="navbar-brand">{{title}}</a>
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/']">Home</a>
      </li>
    </ul>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد:



افزودن ماژول فرم‌ها به برنامه

پس از ایجاد ساختار اولیه برنامه، اولین کاری را که در جهت استفاده‌ی از فرم‌های مبتنی بر قالب‌ها باید انجام داد، افزودن ماژول فرم‌ها به ماژول اصلی برنامه است. برای این منظور فایل src\app\app.module.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ]
در اینجا ابتدا FormsModule، از ماژول Angular مرتبط با آن دریافت شده و سپس به لیست imports ماژول اصلی برنامه اضافه گردیده‌است.


ایجاد ماژول و کامپوننت فرم ثبت نام کارمندان

در ادامه می‌خواهیم فرم ثبت نام یک کارمند را تکمیل کنیم. بنابراین ماژول جدید کارمندان را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 >ng g m Employee -m app.module --routing
با این خروجی
 installing module
  create src\app\employee\employee-routing.module.ts
  create src\app\employee\employee.module.ts
  update src\app\app.module.ts
اگر به سطر آخر آن دقت کنید، فایل app.module.ts را نیز به صورت خودکار به روز رسانی کرده‌است:
import { EmployeeRoutingModule } from './employee/employee-routing.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    EmployeeRoutingModule
  ]
در اینجا EmployeeRoutingModule را به انتهای لیست imports افزوده‌است که نیاز به اندکی تغییر دارد و باید EmployeeRoutingModule را پیش از AppRoutingModule تعریف کرد. علت این است که AppRoutingModule، دارای تعریف مسیریابی ** یا catch all است که آن‌را جهت مدیریت مسیرهای یافت نشده به برنامه افزوده‌ایم. بنابراین اگر ابتدا AppRoutingModule تعریف شود و سپس EmployeeRoutingModule، هیچگاه فرصت به پردازش مسیریابی‌های ماژول کارمندان نمی‌رسد؛ چون مسیر ** پیشتر برنده شده‌است.
همچنین برای اینکه کامپوننت‌های این ماژول نیز در حین مسیریابی در دسترس باشند، نیاز است بجای EmployeeRoutingModule، خود EmployeeModule را ذکر کرد که حاوی تعاریف مسیریابی (ذکر EmployeeRoutingModule در قسمت imports آن) نیز می‌باشد. بنابراین فایل app.module.ts چنین تعاریفی را پیدا می‌کند:
import { EmployeeModule } from './employee/employee.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,    
    EmployeeModule,
    AppRoutingModule 
  ]

در ادامه کامپوننت جدید ثبت یک کارمند را به این ماژول اضافه می‌کنیم:
 >ng g c employee/employee-register
با این خروجی
 installing component
  create src\app\employee\employee-register\employee-register.component.css
  create src\app\employee\employee-register\employee-register.component.html
  create src\app\employee\employee-register\employee-register.component.spec.ts
  create src\app\employee\employee-register\employee-register.component.ts
  update src\app\employee\employee.module.ts
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول کارمندان، جهت درج این کامپوننت جدید، در قسمت declarations فایل employee.module.ts نیز به صورت خودکار انجام شده‌است:
import { EmployeeRegisterComponent } from './employee-register/employee-register.component';

@NgModule({
  declarations: [EmployeeRegisterComponent]

در ادامه می‌خواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src\app\employee\employee-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف می‌کنیم:
import { EmployeeRegisterComponent } from './employee-register/employee-register.component';

const routes: Routes = [
  { path: 'register', component: EmployeeRegisterComponent }
];
ابتدا کامپوننت ثبت کارمندان import شده و سپس آرایه‌ی Routes مسیری را به این کامپوننت تعریف کرده‌است.

سپس می‌خواهیم لینکی را به این مسیریابی جدید اضافه کنیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink آن‌را اضافه می‌کنیم:
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/']">Home</a>
      </li>
      <li>
        <a [routerLink]="['/register']">Register</a>
      </li>
    </ul>
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد (البته می‌توان پنجره‌ی کنسول ng serve را باز نگه داشت تا کار watch را به صورت خودکار انجام دهد؛ این روش سریعتر و به همراه build تدریجی است):



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


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.