مطالب
مراحل ارتقاء پروژه‌های Angular از نسخه‌ی 6.0 به 7.0
مراحل ارتقاء پروژه‌های Angular از نگارش 6 به 7 آن به شرح زیر هستند:
1- به روز رسانی Angular CLI
ابتدا نیاز است نگارش قبلی را حذف و سپس نگارش جدید را نصب کنید:
npm uninstall -g @angular/cli
npm cache verify
# if npm version is < 5 then use `npm cache clean` 
npm install -g @angular/cli@latest
البته Angular 7 پشتیبانی از  Node 10 را اضافه کرده است (بیشتر؛ دانلود Node). بنابراین پیش از اجرای دستورات فوق بهتر است NodeJS خود را نیز به روز کنید:
npm i -g npm

2- به روز رسانی RxJS (اگر در نگارش 6 آن‌را تکمیل نکرده‌اید)
1-تعویض کردن HttpModule با HttpClientModule و سرویس Http با سرویس HttpClient
2-حذف کردن ویژگی‌های منسوخ شده از RxJS 6 با اجرای دستور زیر:
npm install -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json
3-حذف rxjs-compat بعد از بروزرسانی RxJS 6
 
3- به روز رسانی TypeScript
Angular 7 از تایپ اسکریپت 3.1 استفاده می‌کند (بیشتر). به همین جهت نیاز است وابستگی‌های سراسری سیستم خود را مانند TypeScript، پس از نصب CLI جدید، به نحو زیر به روز کنید:
npm update -g

4- به روز رسانی وابستگی‌های اصلی پروژه
برای به‌‌روز رسانی به نسخه 7 (core framework و CLI)، دستورات زیر را اجرا کنید:
ng update @angular/cli
ng update @angular/core
ng update rxjs
و اگر از Angular Material نیز استفاده می‌کنید، نیاز به اجرای دستور زیر را نیز خواهید داشت:
ng update @angular/material
اگر در اینجا خطای  peer dependency را مشاهده کردید، از سوئیچ force-- در انتهای دستورات، استفاده کنید.
و یا به صورت خلاصه دستور زیر تمام مراحل فوق را به صورت یکجا انجام می‌دهد:
 ng update --all --force
 
5- به روز رسانی Service worker 

اگر شما از Service worker  مربوط به Angular استفاده می‌کنید، بجای versionedFiles از files استفاده کنید. رفتار همان است و تغییر نکرده‌است.
 
6- به روز رسانی وابستگی‌های ثالث پروژه
برای به روز رسانی سایر وابستگی‌های پروژه‌، می‌توان از بسته‌ی npm-check-updates استفاده کرد:
npm install npm-check-updates -g
ncu -u
npm install
دستور دوم تمام شماره نگارش‌های بسته‌های موجود در فایل package.json را به صورت خودکار به آخرین نگارش آن‌ها روز رسانی می‌کند و دستور سوم این بسته‌های جدید را دریافت و نصب خواهد کرد.  
مطالب
ASP.NET MVC و Identity 2.0 : مفاهیم پایه
در تاریخ 20 مارچ 2014 تیم ASP.NET نسخه نهایی Identity 2.0 را منتشر کردند . نسخه جدید برخی از ویژگی‌های درخواست شده پیشین را عرضه می‌کند و در کل قابلیت‌های احراز هویت و تعیین سطح دسترسی ارزنده ای را پشتیبانی می‌کند. این فریم ورک در تمام اپلیکیشن‌های ASP.NET می‌تواند بکار گرفته شود.

فریم ورک Identity در سال 2013 معرفی شد، که دنباله سیستم ASP.NET Membership بود. سیستم قبلی گرچه طی سالیان استفاده می‌شد اما مشکلات زیادی هم بهمراه داشت. بعلاوه با توسعه دنیای وب و نرم افزار، قابلیت‌های مدرنی مورد نیاز بودند که باید پشتیبانی می‌شدند. فریم ورک Identity در ابتدا سیستم ساده و کارآمدی برای مدیریت کاربران بوجود آورد و مشکلات پیشین را تا حد زیادی برطرف نمود. بعنوان مثال فریم ورک جدید مبتنی بر EF Code-first است، که سفارشی کردن سیستم عضویت را بسیار آسان می‌کند و به شما کنترل کامل می‌دهد. یا مثلا احراز هویت مبتنی بر پروتوکل OAuth پشتیبانی می‌شود که به شما اجازه استفاده از فراهم کنندگان خارجی مانند گوگل، فیسبوک و غیره را می‌دهد.

نسخه جدید این فریم ورک ویژگی‌های زیر را معرفی می‌کند (بعلاوه مواردی دیگر):

  • مدل حساب‌های کاربری توسعه داده شده. مثلا آدرس ایمیل و اطلاعات تماس را هم در بر می‌گیرد
  • احراز هویت دو مرحله ای (Two-Factor Authentication) توسط اطلاع رسانی ایمیلی یا پیامکی. مشابه سیستمی که گوگل، مایکروسافت و دیگران استفاده می‌کنند
  • تایید حساب‌های کاربری توسط ایمیل (Account Confirmation)
  • مدیریت کاربران و نقش‌ها (Administration of Users & Roles)
  • قفل کردن حساب‌های کاربری در پاسخ به Invalid log-in attempts
  • تامین کننده شناسه امنیتی (Security Token Provider) برای بازتولید شناسه‌ها در پاسخ به تغییرات تنظیمات امنیتی (مثلا هنگام تغییر کلمه عبور)
  • بهبود پشتیبانی از Social log-ins
  • یکپارچه سازی ساده با Claims-based Authorization

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

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

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

در این مقاله با مقدار قابل توجهی کد مواجه خواهید شد. لازم نیست تمام جزئیات آنها را بررسی کنید، تنها با ساختار کلی این فریم ورک آشنا شوید. کامپوننت‌ها را بشناسید و بدانید که هر کدام در کجا قرار گرفته اند، چطور کار می‌کنند و اجزای کلی سیستم چگونه پیکربندی می‌شوند. گرچه، اگر به برنامه نویسی دات نت (#ASP.NET, C) تسلط دارید و با نسخه قبلی Identity هم کار کرده اید، درک کدهای جدید کار ساده ای خواهد بود.

Identity 2.0 با نسخه قبلی سازگار نیست

اپلیکیشن هایی که با نسخه 1.0 این فریم ورک ساخته شده اند نمی‌توانند بسادگی به نسخه جدید مهاجرت کنند. قابلیت هایی جدیدی که پیاده سازی شده اند تغییرات چشمگیری در معماری این فریم ورک بوجود آورده اند، همچنین API مورد استفاده در اپلیکیشن‌ها نیز دستخوش تغییراتی شده است. مهاجرت از نسخه 1.0 به 2.0 نیاز به نوشتن کدهای جدید و اعمال تغییرات متعددی دارد که از حوصله این مقاله خارج است. فعلا همین قدر بدانید که این مهاجرت نمی‌تواند بسادگی در قالب Plug-in and play صورت پذیرد!

شروع به کار : پروژه مثال‌ها را از NuGet دریافت کنید

در حال حاظر (هنگام نوشتن این مقاله) قالب پروژه استانداردی برای اپلیکیشن‌های ASP.NET MVC که ا ز Identity 2.0 استفاده کنند وجود ندارد. برای اینکه بتوانید از نسخه جدید این فریم ورک استفاده کنید، باید پروژه مثال را توسط NuGet دریافت کنید. ابتدا پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب Empty را در دیالوگ تنظیمات انتخاب کنید.

کنسول Package Manager را باز کنید و با اجرای فرمان زیر پروژه مثال‌ها را دانلود کنید.

PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
پس از آنکه NuGet کار خود را به اتمام رساند، ساختار پروژه ای مشابه پروژه‌های استاندارد MVC مشاهده خواهید کرد. پروژه شما شامل قسمت‌های Models, Views, Controllers و کامپوننت‌های دیگری برای شروع به کار است. گرچه در نگاه اول ساختار پروژه بسیار شبیه به پروژه‌های استاندارد ASP.NET MVC به نظر می‌آید، اما با نگاهی دقیق‌تر خواهید دید که تغییرات جدیدی ایجاد شده‌اند و پیچیدگی هایی نیز معرفی شده اند.

پیکربندی Identity : دیگر به سادگی نسخه قبلی نیست
به نظر من یکی از مهم‌ترین نقاط قوت فریم ورک Identity یکی از مهم‌ترین نقاط ضعفش نیز بود. سادگی نسخه 1.0 این فریم ورک کار کردن با آن را بسیار آسان می‌کرد و به سادگی می‌توانستید ساختار کلی و روند کارکردن کامپوننت‌های آن را درک کنید. اما همین سادگی به معنای محدود بودن امکانات آن نیز بود. بعنوان مثال می‌توان به تایید حساب‌های کاربری یا پشتیبانی از احراز هویت‌های دو مرحله ای اشاره کرد.

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

در هر دو نسخه، فایلی بنام Startup.cs در مسیر ریشه پروژه خواهید یافت. در این فایل کلاس واحدی بنام Startup تعریف شده است که متد ()ConfigureAuth را فراخوانی می‌کند. چیزی که در این فایل مشاهده نمی‌کنیم، خود متد ConfigureAuth است. این بدین دلیل است که مابقی کد کلاس Startup در یک کلاس پاره ای (Partial) تعریف شده که در پوشه App_Start قرار دارد. نام فایل مورد نظر Startup.Auth.cs است که اگر آن را باز کنید تعاریف یک کلاس پاره ای بهمراه متد ()ConfigureAuth را خواهید یافت. در یک پروژه که از نسخه Identity 1.0 استفاده می‌کند، کد متد ()ConfigureAuth مطابق لیست زیر است.
public partial class Startup
{
    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")
        });

        // Use a cookie to temporarily store information about a 
        // user logging in with a third party login provider
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Uncomment the following lines to enable logging 
        // in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");
 
        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");

        //app.UseGoogleAuthentication();
    }
}
در قطعه کد بالا پیکربندی لازم برای کوکی‌ها را مشاهده می‌کنید. همچنین کدهایی بصورت توضیحات وجود دارد که به منظور استفاده از تامین کنندگان خارجی مانند گوگل، فیسبوک، توییتر و غیره استفاده می‌شوند. حال اگر به کد این متد در نسخه Identity 2.0 دقت کنید خواهید دید که کد بیشتری نوشته شده است.
public partial class Startup {
    public void ConfigureAuth(IAppBuilder app) {
        // Configure the db context, user manager and role 
        // manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

        // Enable the application to use a cookie to store information for the 
        // signed in user and to use a cookie to temporarily store information 
        // about a user logging in with a third party login provider 
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider {
                // Enables the application to validate the security stamp when the user 
                // logs in. This is a security feature which is used when you 
                // change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator
                    .OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager))
            }
        });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enables the application to temporarily store user information when 
        // they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(
            DefaultAuthenticationTypes.TwoFactorCookie, 
            TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such 
        // as phone or email. Once you check this option, your second step of 
        // verification during the login process will be remembered on the device where 
        // you logged in from. This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(
            DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

        // Uncomment the following lines to enable logging in 
        // with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");

        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");

        //app.UseGoogleAuthentication();
    }
}
اول از همه به چند فراخوانی متد app.CreatePerOwinContext بر می‌خوریم. با این فراخوانی‌ها Callback هایی را رجیستر می‌کنیم که آبجکت‌های مورد نیاز را بر اساس نوع تعریف شده توسط type arguments وهله سازی می‌کنند. این وهله‌ها سپس توسط فراخوانی متد ()context.Get قابل دسترسی خواهند بود. این به ما می‌گوید که حالا Owin بخشی از اپلیکیشن ما است و فریم ورک Identity 2.0 از آن برای ارائه قابلیت هایش استفاده می‌کند.

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

تا اینجا پیکربندی‌های اساسی برای اپلیکیشن شما انجام شده است و می‌توانید از اپلیکیشن خود استفاده کنید. بکارگیری فراهم کنندگان خارجی در حال حاضر غیرفعال است و بررسی آنها نیز از حوصله این مقاله خارج است. این کلاس پیکربندی‌های اساسی Identity را انجام می‌دهد. کامپوننت‌های پیکربندی و کدهای کمکی دیگری نیز وجود دارند که در کلاس IdentityConfig.cs تعریف شده اند.

پیش از آنکه فایل IdentityConfig.cs را بررسی کنیم، بهتر است نگاهی به کلاس ApplicationUser بیاندازیم که در پوشه Models قرار گرفته است.


کلاس جدید ApplicationUser در Identity 2.0
اگر با نسخه 1.0 این فریم ورک اپلیکیشنی ساخته باشید، ممکن است متوجه شده باشید که کلاس پایه IdentityUser محدود و شاید ناکافی باشد. در نسخه قبلی، این فریم ورک پیاده سازی IdentityUser را تا حد امکان ساده نگاه داشته بود تا اطلاعات پروفایل کاربران را معرفی کند.
public class IdentityUser : IUser
{
    public IdentityUser();
    public IdentityUser(string userName);

    public virtual string Id { get; set; }
    public virtual string UserName { get; set; }

    public virtual ICollection<IdentityUserRole> Roles { get; }
    public virtual ICollection<IdentityUserClaim> Claims { get; }
    public virtual ICollection<IdentityUserLogin> Logins { get; }

    public virtual string PasswordHash { get; set; }
    public virtual string SecurityStamp { get; set; }
}
بین خواص تعریف شده در این کلاس، تنها Id, UserName و Roles برای ما حائز اهمیت هستند (از دید برنامه نویسی). مابقی خواص عمدتا توسط منطق امنیتی این فریم ورک استفاده می‌شوند و کمک شایانی در مدیریت اطلاعات کاربران به ما نمی‌کنند.

اگر از نسخه Identity 1.0 استفاده کرده باشید و مطالعاتی هم در این زمینه داشته باشید، می‌دانید که توسعه کلاس کاربران بسیار ساده است. مثلا برای افزودن فیلد آدرس ایمیل و اطلاعات دیگر کافی بود کلاس ApplicationUser را ویرایش کنیم و از آنجا که این فریم ورک مبتنی بر EF Code-first است بروز رسانی دیتابیس و مابقی اپلیکیشن کار چندان مشکلی نخواهد بود.

با ظهور نسخه Identity 2.0 نیاز به برخی از این سفارشی سازی‌ها از بین رفته است. گرچه هنوز هم می‌توانید بسادگی مانند گذشته کلاس ApplicationUser را توسعه و گسترش دهید، تیم ASP.NET تغییراتی بوجود آورده اند تا نیازهای رایج توسعه دهندگان را پاسخگو باشد.

اگر به کد کلاس‌های مربوطه دقت کنید خواهید دید که کلاس ApplicationUser همچنان از کلاس پایه IdentityUser ارث بری می‌کند، اما این کلاس پایه پیچیده‌تر شده است. کلاس ApplicationUser در پوشه Models و در فایلی بنام IdentityModels.cs تعریف شده است. همانطور که می‌بینید تعاریف خود این کلاس بسیار ساده است.
public class ApplicationUser : IdentityUser {
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager) {
        // Note the authenticationType must match the one 
        // defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = 
            await manager.CreateIdentityAsync(this, 
                DefaultAuthenticationTypes.ApplicationCookie);

        // Add custom user claims here
        return userIdentity;
    }
}
حال اگر تعاریف کلاس IdentityUser را بازیابی کنید (با استفاده از قابلیت Go To Definition در VS) خواهید دید که این کلاس خود از کلاس پایه دیگری ارث بری می‌کند. اگر به این پیاده سازی دقت کنید کاملا واضح است که ساختار این کلاس به کلی نسبت به نسخه قبلی تغییر کرده است.
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
    where TLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey>
    where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey>
    where TClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey>
{
    public IdentityUser();

    // Used to record failures for the purposes of lockout
    public virtual int AccessFailedCount { get; set; }
    // Navigation property for user claims
    public virtual ICollection<TClaim> Claims { get; }
    // Email
    public virtual string Email { get; set; }
    // True if the email is confirmed, default is false
    public virtual bool EmailConfirmed { get; set; }
    // User ID (Primary Key)
    public virtual TKey Id { get; set; }
    // Is lockout enabled for this user
    public virtual bool LockoutEnabled { get; set; }
    // DateTime in UTC when lockout ends, any 
    // time in the past is considered not locked out.
    public virtual DateTime? LockoutEndDateUtc { get; set; }

    // Navigation property for user logins
    public virtual ICollection<TLogin> Logins { get; }
    // The salted/hashed form of the user password
    public virtual string PasswordHash { get; set; }
    // PhoneNumber for the user
    public virtual string PhoneNumber { get; set; }
    // True if the phone number is confirmed, default is false
    public virtual bool PhoneNumberConfirmed { get; set; }
    // Navigation property for user roles
    public virtual ICollection<TRole> Roles { get; }

    // A random value that should change whenever a users 
    // credentials have changed (password changed, login removed)
    public virtual string SecurityStamp { get; set; }
    // Is two factor enabled for the user
    public virtual bool TwoFactorEnabled { get; set; }
    // User name
    public virtual string UserName { get; set; }
}
اول از همه آنکه برخی از خواص تعریف شده هنوز توسط منطق امنیتی فریم ورک استفاده می‌شوند و از دید برنامه نویسی مربوط به مدیریت اطلاعات کاربران نیستند. اما به هر حال فیلد‌های Email و PhoneNumber نیاز به ویرایش تعریف پیش فرض موجودیت کاربران را از بین می‌برد.

اما از همه چیز مهم‌تر امضا (Signature)ی خود کلاس است. این آرگومانهای جنریک چه هستند؟ به امضای این کلاس دقت کنید.
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
    where TLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey>
    where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey>
    where TClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey>
نسخه جدید IdentityUser انواع آرگومانهای جنریک را پیاده سازی می‌کند که انعطاف پذیری بسیار بیشتری به ما می‌دهند. بعنوان مثال بیاد بیاورید که نوع داده فیلد Id در نسخه 1.0 رشته (string) بود. اما در نسخه جدید استفاده از آرگومانهای جنریک (در اینجا TKey) به ما اجازه می‌دهد که نوع این فیلد را مشخص کنیم. همانطور که مشاهده می‌کنید خاصیت Id در این کلاس نوع داده TKey را باز می‌گرداند.
public virtual TKey Id { get; set; }
یک مورد حائز اهمیت دیگر خاصیت Roles است که بصورت زیر تعریف شده.
public virtual ICollection<TRole> Roles { get; }
همانطور که می‌بینید نوع TRole بصورت جنریک تعریف شده و توسط تعاریف کلاس IdentityUser مشخص می‌شود. اگر به محدودیت‌های پیاده سازی این خاصیت دقت کنید می‌بینید که نوع این فیلد به <IdentityUserRole<TKey محدود (constraint) شده است، که خیلی هم نسبت به نسخه 1.0 تغییری نکرده. چیزی که تفاوت چشمگیری کرده و باعث breaking changes می‌شود تعریف خود IdentityUserRole است.

در نسخه Identity 1.0 کلاس IdentityUserRole بصورت زیر تعریف شده بود.
public class IdentityUserRole 
{
      public IdentityUserRole();
      public virtual IdentityRole Role { get; set; }
      public virtual string RoleId { get; set; }
      public virtual IdentityUser User { get; set; }
      public virtual string UserId { get; set; }
}
این کلاس را با پیاده سازی نسخه Identity 2.0 مقایسه کنید.
public class IdentityUserRole<TKey> 
{
    public IdentityUserRole();
    public virtual TKey RoleId { get; set; }
    public virtual TKey UserId { get; set; }
}
پیاده سازی پیشین ارجاعاتی به آبجکت هایی از نوع IdentityRole و IdentityUser داشت. پیاده سازی نسخه 2.0 تنها شناسه‌ها را ذخیره می‌کند. اگر در اپلیکیشنی که از نسخه 1.0 استفاده می‌کند سفارشی سازی هایی انجام داده باشید (مثلا تعریف کلاس Role را توسعه داده باشید، یا سیستمی مانند Group-based Roles را پیاده سازی کرده باشید) این تغییرات سیستم قبلی شما را خواهد شکست.

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

از آنجا که کلاس ApplicationUser از IdentityUser ارث بری می‌کند، تمام خواص و تعاریف این کلاس پایه در ApplicationUser قابل دسترسی هستند.


کامپوننت‌های پیکربندی Identity 2.0 و کدهای کمکی
گرچه متد ()ConfigAuth در کلاس Startup، محلی است که پیکربندی Identity در زمان اجرا صورت می‌پذیرد، اما در واقع کامپوننت‌های موجود در فایل IdentityConfig.cs هستند که اکثر قابلیت‌های Identity 2.0 را پیکربندی کرده و نحوه رفتار آنها در اپلیکیشن ما را کنترل می‌کنند.

اگر محتوای فایل IdentityConfig.cs را بررسی کنید خواهید دید که کلاس‌های متعددی در این فایل تعریف شده اند. می‌توان تک تک این کلاس‌ها را به فایل‌های مجزایی منتقل کرد، اما برای مثال جاری کدها را بهمین صورت رها کرده و نگاهی اجمالی به آنها خواهیم داشت. بهرحال در حال حاظر تمام این کلاس‌ها در فضای نام ApplicationName.Models قرار دارند.


Application User Manager و Application Role Manager
اولین چیزی که در این فایل به آنها بر می‌خوریم دو کلاس ApplicationUserManager و ApplicationRoleManager هستند. آماده باشید، مقدار زیادی کد با انواع داده جنریک در پیش روست!
public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options, 
        IOwinContext context)
    {
        var manager = new ApplicationUserManager(
            new UserStore<ApplicationUser>(
                context.Get<ApplicationDbContext>()));

        // Configure validation logic for usernames
        manager.UserValidator = 
            new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6, 
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;

        // Register two factor authentication providers. This application uses 
        // Phone and Emails as a step of receiving a code for verifying 
        // the user You can write your own provider and plug in here.
        manager.RegisterTwoFactorProvider("PhoneCode", 
            new PhoneNumberTokenProvider<ApplicationUser>
        {
            MessageFormat = "Your security code is: {0}"
        });

        manager.RegisterTwoFactorProvider("EmailCode", 
            new EmailTokenProvider<ApplicationUser>
        {
            Subject = "SecurityCode",
            BodyFormat = "Your security code is {0}"
        });

        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();

        var dataProtectionProvider = options.DataProtectionProvider;

        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = 
                new DataProtectorTokenProvider<ApplicationUser>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }

        return manager;
    }

    public virtual async Task<IdentityResult> AddUserToRolesAsync(
        string userId, IList<string> roles)
    {
        var userRoleStore = (IUserRoleStore<ApplicationUser, string>)Store;
        var user = await FindByIdAsync(userId).ConfigureAwait(false);

        if (user == null)
        {
            throw new InvalidOperationException("Invalid user Id");
        }

        var userRoles = await userRoleStore
            .GetRolesAsync(user)
            .ConfigureAwait(false);

        // Add user to each role using UserRoleStore
        foreach (var role in roles.Where(role => !userRoles.Contains(role)))
        {
            await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
        }

        // Call update once when all roles are added
        return await UpdateAsync(user).ConfigureAwait(false);
    }

    public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(
        string userId, IList<string> roles)
    {
        var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
        var user = await FindByIdAsync(userId).ConfigureAwait(false);

        if (user == null)
        {
            throw new InvalidOperationException("Invalid user Id");
        }

        var userRoles = await userRoleStore
            .GetRolesAsync(user)
            .ConfigureAwait(false);

        // Remove user to each role using UserRoleStore
        foreach (var role in roles.Where(userRoles.Contains))
        {
            await userRoleStore
                .RemoveFromRoleAsync(user, role)
                .ConfigureAwait(false);
        }

        // Call update once when all roles are removed
        return await UpdateAsync(user).ConfigureAwait(false);
    }
}
قطعه کد بالا گرچه شاید به نظر طولانی بیاید، اما در کل، کلاس ApplicationUserManager توابع مهمی را برای مدیریت کاربران فراهم می‌کند: ایجاد کاربران جدید، انتساب کاربران به نقش‌ها و حذف کاربران از نقش ها. این کلاس بخودی خود از کلاس <UserManager<ApplicationUser ارث بری می‌کند بنابراین تمام قابلیت‌های UserManager در این کلاس هم در دسترس است. اگر به متد ()Create دقت کنید می‌بینید که وهله ای از نوع ApplicationUserManager باز می‌گرداند. بیشتر تنظیمات پیکربندی و تنظیمات پیش فرض کاربران شما در این متد اتفاق می‌افتد.

مورد حائز اهمیت بعدی در متد ()Create فراخوانی ()<context.Get<ApplicationDBContext است. بیاد بیاورید که پیشتر نگاهی به متد ()ConfigAuth داشتیم که چند فراخوانی CreatePerOwinContext داشت که توسط آنها Callback هایی را رجیستر می‌کردیم. فراخوانی متد ()<context.Get<ApplicationDBContext این Callback‌‌ها را صدا می‌زند، که در اینجا فراخوانی متد استاتیک ()ApplicationDbContext.Create خواهد بود. در ادامه بیشتر درباره این قسمت خواهید خواهند.

اگر دقت کنید می‌بینید که احراز هویت، تعیین سطوح دسترسی و تنظیمات مدیریتی و مقادیر پیش فرض آنها در متد ()Create انجام می‌شوند و سپس وهله ای از نوع خود کلاس ApplicationUserManager بازگشت داده می‌شود. همچنین سرویس‌های احراز هویت دو مرحله ای نیز در همین مرحله پیکربندی می‌شوند. اکثر پیکربندی‌ها و تنظیمات نیازی به توضیح ندارند و قابل درک هستند. اما احراز هویت دو مرحله ای نیاز به بررسی عمیق‌تری دارد. در ادامه به این قسمت خواهیم پرداخت. اما پیش از آن نگاهی به کلاس ApplicationRoleManager بیاندازیم.
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
    public ApplicationRoleManager(IRoleStore<IdentityRole,string> roleStore)
        : base(roleStore)
    {
    }

    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, 
        IOwinContext context)
    {
        var manager = new ApplicationRoleManager(
            new RoleStore<IdentityRole>(
                context.Get<ApplicationDbContext>()));

        return manager;
    }
}
مانند کلاس ApplicationUserManager مشاهده می‌کنید که کلاس ApplicationRoleManager از <RoleManager<IdentityRole ارث بری می‌کند. بنابراین تمام قابلیت‌های کلاس پایه نیز در این کلاس در دسترس هستند. یکبار دیگر متدی بنام ()Create را مشاهده می‌کنید که وهله ای از نوع خود کلاس بر می‌گرداند.


سرویس‌های ایمیل و پیامک برای احراز هویت دو مرحله ای و تایید حساب‌های کاربری
دو کلاس دیگری که در فایل IdentityConfig.cs وجود دارند کلاس‌های EmailService و SmsService هستند. بصورت پیش فرض این کلاس‌ها تنها یک wrapper هستند که می‌توانید با توسعه آنها سرویس‌های مورد نیاز برای احراز هویت دو مرحله ای و تایید حساب‌های کاربری را بسازید.
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        return Task.FromResult(0);
    }
}

public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        return Task.FromResult(0);
    }
}
دقت کنید که هر دو این کلاس‌ها قرارداد واحدی را بنام IIdentityMessageService پیاده سازی می‌کنند. همچنین قطعه کد زیر را از متد ()ApplicationUserManager.Create بیاد آورید.
// Register two factor authentication providers. This application uses 
// Phone and Emails as a step of receiving a code for verifying 
// the user You can write your own provider and plug in here.
manager.RegisterTwoFactorProvider("PhoneCode", 
    new PhoneNumberTokenProvider<ApplicationUser>
{
    MessageFormat = "Your security code is: {0}"
});

manager.RegisterTwoFactorProvider("EmailCode", 
    new EmailTokenProvider<ApplicationUser>
{
    Subject = "SecurityCode",
    BodyFormat = "Your security code is {0}"
});

manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
همانطور که می‌بینید در متد ()Create کلاس‌های EmailService و SmsService وهله سازی شده و توسط خواص مرتبط به وهله ApplicationUserManager ارجاع می‌شوند.


کلاس کمکی SignIn
هنگام توسعه پروژه مثال Identity، تیم توسعه دهندگان کلاسی کمکی برای ما ساخته‌اند که فرامین عمومی احراز هویت کاربران و ورود آنها به اپلیکیشن را توسط یک API ساده فراهم می‌سازد. برای آشنایی با نحوه استفاده از این متد‌ها می‌توانیم به کنترلر AccountController در پوشه Controllers مراجعه کنیم. اما پیش از آن بگذارید نگاهی به خود کلاس SignInHelper داشته باشیم.
public class SignInHelper
{
    public SignInHelper(
        ApplicationUserManager userManager, 
        IAuthenticationManager authManager)
    {
        UserManager = userManager;
        AuthenticationManager = authManager;
    }

    public ApplicationUserManager UserManager { get; private set; }
    public IAuthenticationManager AuthenticationManager { get; private set; }

    public async Task SignInAsync(
        ApplicationUser user, 
        bool isPersistent, 
        bool rememberBrowser)
    {
        // Clear any partial cookies from external or two factor partial sign ins
        AuthenticationManager.SignOut(
            DefaultAuthenticationTypes.ExternalCookie, 
            DefaultAuthenticationTypes.TwoFactorCookie);

        var userIdentity = await user.GenerateUserIdentityAsync(UserManager);

        if (rememberBrowser)
        {
            var rememberBrowserIdentity = 
                AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);

            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity, 
                rememberBrowserIdentity);
        }
        else
        {
            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity);
        }
    }

    public async Task<bool> SendTwoFactorCode(string provider)
    {
        var userId = await GetVerifiedUserIdAsync();

        if (userId == null)
        {
            return false;
        }

        var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);

        // See IdentityConfig.cs to plug in Email/SMS services to actually send the code
        await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);

        return true;
    }

    public async Task<string> GetVerifiedUserIdAsync()
    {
        var result = await AuthenticationManager.AuthenticateAsync(
            DefaultAuthenticationTypes.TwoFactorCookie);

        if (result != null && result.Identity != null 
            && !String.IsNullOrEmpty(result.Identity.GetUserId()))
        {
            return result.Identity.GetUserId();
        }

        return null;
    }

    public async Task<bool> HasBeenVerified()
    {
        return await GetVerifiedUserIdAsync() != null;
    }

    public async Task<SignInStatus> TwoFactorSignIn(
        string provider, 
        string code, 
        bool isPersistent, 
        bool rememberBrowser)
    {
        var userId = await GetVerifiedUserIdAsync();

        if (userId == null)
        {
            return SignInStatus.Failure;
        }

        var user = await UserManager.FindByIdAsync(userId);

        if (user == null)
        {
            return SignInStatus.Failure;
        }

        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }

        if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
        {
            // When token is verified correctly, clear the access failed 
            // count used for lockout
            await UserManager.ResetAccessFailedCountAsync(user.Id);
            await SignInAsync(user, isPersistent, rememberBrowser);

            return SignInStatus.Success;
        }

        // If the token is incorrect, record the failure which 
        // also may cause the user to be locked out
        await UserManager.AccessFailedAsync(user.Id);

        return SignInStatus.Failure;
    }

    public async Task<SignInStatus> ExternalSignIn(
        ExternalLoginInfo loginInfo, 
        bool isPersistent)
    {
        var user = await UserManager.FindAsync(loginInfo.Login);

        if (user == null)
        {
            return SignInStatus.Failure;
        }

        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }

        return await SignInOrTwoFactor(user, isPersistent);
    }

    private async Task<SignInStatus> SignInOrTwoFactor(
        ApplicationUser user, 
        bool isPersistent)
    {
        if (await UserManager.GetTwoFactorEnabledAsync(user.Id) &&
            !await AuthenticationManager.TwoFactorBrowserRememberedAsync(user.Id))
        {
            var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));

            AuthenticationManager.SignIn(identity);
            return SignInStatus.RequiresTwoFactorAuthentication;
        }

        await SignInAsync(user, isPersistent, false);
        return SignInStatus.Success;
    }

    public async Task<SignInStatus> PasswordSignIn(
        string userName, 
        string password, 
        bool isPersistent, 
        bool shouldLockout)
    {
        var user = await UserManager.FindByNameAsync(userName);

        if (user == null)
        {
            return SignInStatus.Failure;
        }

        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }

        if (await UserManager.CheckPasswordAsync(user, password))
        {
            return await SignInOrTwoFactor(user, isPersistent);
        }

        if (shouldLockout)
        {
            // If lockout is requested, increment access failed 
            // count which might lock out the user
            await UserManager.AccessFailedAsync(user.Id);

            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return SignInStatus.LockedOut;
            }
        }

        return SignInStatus.Failure;
    }
}
کد این کلاس نسبتا طولانی است، و بررسی عمیق آنها از حوصله این مقاله خارج است. گرچه اگر به دقت یکبار این کلاس را مطالعه کنید می‌توانید براحتی از نحوه کارکرد آن آگاه شوید. همانطور که می‌بینید اکثر متدهای این کلاس مربوط به ورود کاربران و مسئولیت‌های تعیین سطوح دسترسی است.

این متدها ویژگی‌های جدیدی که در Identity 2.0 عرضه شده اند را در بر می‌گیرند. متد آشنایی بنام ()SignInAsync را می‌بینیم، و متدهای دیگری که مربوط به احراز هویت دو مرحله ای و external log-ins می‌شوند. اگر به متدها دقت کنید خواهید دید که برای ورود کاربران به اپلیکیشن کارهای بیشتری نسبت به نسخه پیشین انجام می‌شود.

بعنوان مثال متد Login در کنترلر AccountController را باز کنید تا نحوه مدیریت احراز هویت در Identity 2.0 را ببینید.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInHelper.PasswordSignIn(
        model.Email, 
        model.Password, 
        model.RememberMe, 
        shouldLockout: false);

    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresTwoFactorAuthentication:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");

            return View(model);
    }
}

مقایسه Sign-in با نسخه Identity 1.0
در نسخه 1.0 این فریم ورک، ورود کاربران به اپلیکیشن مانند لیست زیر انجام می‌شد. اگر متد Login در کنترلر AccountController را باز کنید چنین قطعه کدی را می‌بینید.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);

        if (user != null)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}
در قطعه کد بالا متدی در کلاس UserManager را فراخوانی می‌کنیم که مشابه قطعه کدی است که در کلاس SignInHelper دیدیم. همچنین متد SignInAsync را فراخوانی می‌کنیم که مستقیما روی کنترلر AccountController تعریف شده است.
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(
        DefaultAuthenticationTypes.ExternalCookie);

    var identity = await UserManager.CreateIdentityAsync(
        user, DefaultAuthenticationTypes.ApplicationCookie);

    AuthenticationManager.SignIn(
        new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
مسلما با عرضه قابلیت‌های جدید در Identity 2.0 و تغییرات معماری این فریم ورک، پیچیدگی هایی معرفی می‌شود که حتی در امور ساده ای مانند ورود کاربران نیز قابل مشاهده است.


ApplicationDbContext
اگر از نسخه پیشین Identity در اپلیکیشن‌های ASP.NET MVC استفاده کرده باشید با کلاس ApplicationDbContext آشنا هستید. این کلاس پیاده سازی پیش فرض EF فریم ورک است، که اپلیکیشن شما توسط آن داده‌های مربوط به Identity را ذخیره و بازیابی می‌کند.

در پروژه مثال ها، تیم Identity این کلاس را بطور متفاوتی نسبت به نسخه 1.0 پیکربندی کرده اند. اگر فایل IdentityModels.cs را باز کنید تعاریف کلاس ApplicationDbContext را مانند لیست زیر خواهید یافت.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false) {
    }

    static ApplicationDbContext() {
        // Set the database intializer which is run once during application start
        // This seeds the database with admin user credentials and admin role
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create() {
        return new ApplicationDbContext();
    }
}
قطعه کد بالا دو متد استاتیک تعریف می‌کند. یکی ()Create و دیگری ()ApplicationDbContext که سازنده دیتابیس (database initializer) را تنظیم می‌کند. این متد هنگام اجرای اپلیکیشن فراخوانی می‌شود و هر پیکربندی ای که در کلاس ApplicationDbInitializer تعریف شده باشد را اجرا می‌کند. اگر به فایل IdentityConfig.cs مراجعه کنیم می‌توانیم تعاریف این کلاس را مانند لیست زیر بیابیم.
public class ApplicationDbInitializer 
    : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) 
    {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }

    public static void InitializeIdentityForEF(ApplicationDbContext db) 
    {
        var userManager = HttpContext
            .Current.GetOwinContext()
            .GetUserManager<ApplicationUserManager>();

        var roleManager = HttpContext.Current
            .GetOwinContext()
            .Get<ApplicationRoleManager>();

        const string name = "admin@admin.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";

        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);

        if (role == null) 
        {
            role = new IdentityRole(roleName);
            var roleresult = roleManager.Create(role);
        }

        var user = userManager.FindByName(name);

        if (user == null) 
        {
            user = new ApplicationUser { UserName = name, Email = name };

            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }

        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);

        if (!rolesForUser.Contains(role.Name)) 
        {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}
پیکربندی جاری در صورتی که مدل موجودیت‌ها تغییر کنند دیتابیس را پاک کرده و مجددا ایجاد می‌کند. در غیر اینصورت از دیتابیس موجود استفاده خواهد شد. اگر بخواهیم با هر بار اجرای اپلیکیشن دیتابیس از نو ساخته شود، می‌توانیم کلاس مربوطه را به <DropCreateDatabaseAlways<ApplicationDbContext تغییر دهیم. بعنوان مثال هنگام توسعه اپلیکیشن و بمنظور تست می‌توانیم از این رویکرد استفاده کنیم تا هر بار با دیتابیسی (تقریبا) خالی شروع کنیم.

نکته حائز اهمیت دیگر متد ()InitializeIdentityForEF است. این متد کاری مشابه متد ()Seed انجام می‌دهد که هنگام استفاده از مهاجرت‌ها (Migrations) از آن استفاده می‌کنیم. در این متد می‌توانید رکوردهای اولیه ای را در دیتابیس ثبت کنید. همانطور که مشاهده می‌کنید در قطعه کد بالا نقشی مدیریتی بنام Admin ایجاد شده و کاربر جدیدی با اطلاعاتی پیش فرض ساخته می‌شود که در آخر به این نقش منتسب می‌گردد. با انجام این مراحل، پس از اجرای اولیه اپلیکیشن کاربری با سطح دسترسی مدیر در اختیار خواهیم داشت که برای تست اپلیکیشن بسیار مفید خواهد بود.

در این مقاله نگاهی اجمالی به Identity 2.0 در پروژه‌های ASP.NET MVC داشتیم. کامپوننت‌های مختلف فریم ورک و نحوه پیکربندی آنها را بررسی کردیم و با تغییرات و قابلیت‌های جدید به اختصار آشنا شدیم. در مقالات بعدی بررسی هایی عمیق‌تر خواهیم داشت و با نحوه استفاده و پیاده سازی قسمت‌های مختلف این فریم ورک آشنا خواهیم شد.


مطالعه بیشتر
نظرات مطالب
استفاده از MediaWiki بهترین روش نگهداری یادداشت‌های شخصی خصوصا برای برنامه‌نویس‌ها
- البته شاید این کار به منظور استفاده تک کاربره مناسب باشه اما بسیاری از مزایای اون از دست میره چراکه صدها extension برای مدیاویکی تولید شده و پشتیبانی میشه که دیگه نمیشه ازشون استفاده کرد. مثل افزونه کتاب ساز اون
- مدیاویکی دائم در حال به روز رسانی نرم افزار خودش هست که توسعه اون خیلی سریع رخ میده.
 - نرم افزار اکثرقابلیت‌های مورد نیاز فارسی زبان‌ها رو داره مثلا زبان فارسی و تاریخ شمسی.
- فروم‌های متعدد برای پاسخگویی به اشکالات کاربران وجود داره.
- قابلیت همکاری تیمی که بهترین قابلیت‌های ویکی هست از بین میره.
- فکر میکنم بهترین کار همکاری با تیم تولید کننده در رفع نیازهای بومی باشه مثلا :
  1. ادیتور اون خیلی مناسب کاربر معمولی نیست
  2. در خیلی از موارد چپ چین راست چین کردن متن با مشکل مواجه میشه
  3. دکمه‌های میان بر برای خیلی از عملیات رو نداره
  4. موقع تایپ مطلب قابلیت WYSIWYG وجود نداره باید به preview رفت یا مطلب رو ذخیره کرد و سپس نتیجه اونو دید
مسیرراه‌ها
WPF
          مطالب
          Scaffolding در EF Core
          ایجاد Model  از روی Database موجود در EF Core

          در بسیاری اوقات ممکن است تیم تحلیل دیتابیس، از توسعه اپلیکیشن جدا شده باشد تا مراحل نرمال سازی و تست بهره وری اجرای کوئری‌ها، به‌صورت جداگانه‌ای از توسعه‌ی برنامه انجام شود؛ یا ممکن است دیتابیس یک برنامه‌ی از پیش موجود، برای نگهداری و مهندسی مجدد به شما سپرده شود. سناریو هر چه باشد، جهت سرعت بخشیدن به توسعه‌ی نرم افزار میتوان از Entity Framework Core جهت ایجاد فایل‌های Model  از روی دیتابیس موجود استفاده کرد.

          در این مثال ، از دیتابیس SQL Server  و یک برنامه‌ی کنسول و همچنین از ابزار NET Core CLI. استفاده خواهیم کرد.
          با استفاده از ابزار CLI  ابتدا یک فولدر خالی به نام EfCoreDbToModel  ایجاد میکنیم:
          > mkdir EfCoreDbToModel
          سپس وارد این فولدر شده:
          > cd EfCoreDbToModel
          و بعد از آن یک پروژه‌ی جدید کنسول را در این فولدر ایجاد مینماییم:
          > dotnet new console
           پس از مشاهده پیام Restore Succeeded، بسته‌های زیر را به پروژه اضافه میکنیم:
          > dotnet add package Microsoft.EntityFrameworkCore.SqlServer
          > dotnet add package Microsoft.EntityFrameworkCore.Tools
          > dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design
          بسته‌ی اول SQL Server Provider مناسب برای Entity Framework Core هست. بسته‌ی دوم مدیریت دستورات Entity Framework Core، از جمله دستورات Scaffold-DbContxet ،  Add-Migration  و Update-Database را بر عهده خواهد داشت. هر دو بسته‌ی فوق جهت ارتباط EF Core  با SQL Server  ضروری هستند و در نهایت جهت دسترسی به امکانات  ( Design-Time )  زمان طراحیِ  EF Core در SQL Server از جمله Scaffold کردن Model، به بسته‌ی سوم نیازمندیم.

          در ادامه فایل csproj. را باز کرده و در صورتیکه خط زیر در آن موجود نیست، آن را به گره ItemGroup  اضافه کنید:
           < DotNetCliToolReference Include= " Microsoft.EntityFrameworkCore.Tools.DotNet " Version= " 2.0.0 " />
          سپس بسته‌ها را Restore  نمایید:
          > dotnet restore

          اکنون با اجرای دستوری مثل دستور زیر، بررسی کنید که آیا دستورات Ef Core در دسترس هستند یا خیر:
          > dotnet ef -h
          در صورتیکه همه چیز مطابق انتظار کار کرده باشد، باید نتیجه‌ای مشابه تصویر زیر نمایش داده شود:


          برای تولید فایل‌های Model، از دستور dbContext scaffold بصورت زیر استفاده میکنیم:
          >dotnet ef dbcontext scaffold "Server=.;Database=Your_DB;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Model
          دستور فوق دارای دو آرگومان اصلی است:
            1- Connection String
            2- Provider که Entity Framework Core Provider مخصوص دیتابیس مدنظر شماست.

          لیستی از دیتابیس‌های مورد پشتیبانی EF Core را میتوانید در اینجا مشاهده کنید.

          پس از اجرای دستور فوق، فولدر Model، شامل فایل‌های Entity و همچنین یک فایل دیگر که معرف DbContext است، ایجاد خواهند شد:


            گزینه‌ی o- دایرکتوری ایجاد فایل‌های مدل و DbContext را مشخص می‌کند. در صورتیکه از وارد کردن آن صرف نظر کنید، این فایل‌ها بصورت پیش فرض در مسیری قرار خواهند گرفت که فایل csproj. وجود دارد.
          همانطور که ملاحظه میکنید نام کلاس DbContext از ترکیب نام دیتابیس بعلاوه‌ی کلمه “Context” خواهد بود. جهت تغییر نام این کلاس می‌توانید از گزینه‌ی "context "Your_Context_Title- استفاده نمائید. برای مثال:
          > dotnet ef dbcontext scaffold "Server=.\;Database=Your_Db_name;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Model -context "MyDbContext"

          جهت کسب اطلاعات بیشتر رجوع کنید به ^ و ^.
          مطالب
          Functional Programming - قسمت چهارم - برخورد با Exception ها
          چنانچه قسمت‌های قبلی سری آموزش برنامه نویسی تابعی Functional Programming را مطالعه نکرده‌اید، پیشنهاد میکنم قبلا آن‌ها را  (+  و  +  و  +) قبل از شروع بخوانید. در این قسمت قرار است تاثیر استثناءها (exception) را بر روی کدها بررسی کرده و راهکاری را از جنس functional برایش ارائه کنیم. 



          Exception و خوانایی کد

          تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:

          public ActionResult CreateEmployee(string name) { 
              try { 
                  ValidateName(name);
                  // ادامه کد‌ها return View("با موفقیت ثبت شد");
                  }
              catch (ValidationException ex) 
              { 
                  return View("خطا", ex.Message);
              }
          }
          
          private void ValidateName(string name) { 
              if (string.IsNullOrWhiteSpace(name)) 
                  throw new ValidationException("نام نمی‌تواند خالی باشد");
          
              if (name.Length > 100) 
                  throw new ValidationException("نام نمی‌تواند طولانی باشد");
          }

          در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کد‌های مشابه به این کد را زیاد دیده‌اید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت ساده‌تر شما از امضای این متد نمی‌توانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشه‌ای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنه‌ی متد برگردیم. اگر به‌خاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم: 

          به عبارت دیگر وقتی از exception‌ها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام می‌دهید که دستور GOTO انجام می‌داد. این دستور در روش‌های قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگی‌های زیادی را خواهد داشت. چراکه فراخوانی قطعه‌های کد و متد‌ها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exception‌ها برای کنترل جریان برنامه، می‌توانند از GOTO هم بد‌تر باشند؛ چرا که exception میتواند از لایه‌های مختلف کد نیز عبور کند.

          امیدوارم تا اینجا به یک عقیده‌ی مشترک رسیده باشیم. خوب راهکار چیست؟ تصور کنید که تکه کد بالا را به صورت زیر تبدیل کنیم: 

          public ActionResult CreateEmployee(string name) { 
              string error = ValidateName(name);
          
           if (error != string.Empty) 
                  return View("خطا", error);
              // ادامه کد‌ها return View("با موفقیت ثبت شد");
          }
          
          private string ValidateName(string name) { 
          
              if (string.IsNullOrWhiteSpace(name)) 
                  return "نام نمی‌تواند خالی باشد";
          
              if (name.Length > 100) 
                  return "طول نام نمی‌تواند بیشتر از 100 کاراکتر باشد";
          
              return string.Empty;
          }

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



          موارد استفاده Exception

          با همه‌ی بدی‌هایی که از Exception‌ها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟

          1. Exception‌ها واقعا برای موارد استثنائی هستند.
          2. Exception‌ها برای شرایطی هستند که به معنای واقعی یک باگ باشند.
          3. منتظر رخ دادن Exception نباشیم! 

          در توضیح مورد سوم، در اعتبار سنجی داده‌های کاربر (Validation) انتظار داده‌ی نادرستی را می‌توان داشت، پس نمی‌توانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید


          دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که می‌توان انتظار استفاده‌ی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامین‌های مختلف، دیتایی رد و بدل می‌شود که معتبر نیست، میتوانیم آن را جزء استثناء‌ها در نظر بگیریم. به مثال زیر دقت کنید:

          public ActionResult UpdateEmployee(int employeeId, string name) { 
              string error = ValidateName(name);
              
              if (error != string.Empty) 
                  return View("Error", error);
              
              Employee employee = GetEmployee(employeeId); 
              employee.UpdateName(name);
          }
          
          public class Employee { 
          
              public void UpdateName(string name){
          
                  if (name == null) 
                      throw new ArgumentNullException();
                  
                  // ادامه کد‌ها }
          }

          در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین می‌باشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنه‌ی متد UpdateName دقت کنید، می‌بینید که مجددا از خالی نبودن نام اطمینان حاصل کرده‌ایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردن‌ها در دامین‌های مختلف، معمولا guard clause گفته می‌شود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است! 



          مفهوم fail fast

          تا اینجا متوجه شدیم که از exception‌ها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آن‌ها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast می‌رساند. این مفهوم به ما میگوید:

          • کار جاری را به محض یک اتفاق استثنائی باید متوقف کنیم.
          • رعایت این نکته در نهایت ما را به یک نرم افزار پایدار خواهد رساند.


          برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.

          متد زیر را ببینید: 

          public void ProcessItems(List<Item> items) { 
          
              foreach (Item item in items) { 
                  try { 
                      Process(item);
           } 
                  catch (Exception ex) 
                  { 
                      Logger.Log(ex);
           }
           }
           }

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

          به صور خلاصه مهم‌ترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:

          • مسیر رسیدن به خطا‌ها سر راست‌تر می‌شود.
          • نرم افزار به پایداری مناسبی خواهد رسید.
          • از اعتبار دیتای ذخیره شده اطمینان خواهیم داشت.


          کجا exception‌ها را به دام بیندازیم؟

          در یکی از حالت‌های زیر:

          • لاگ کردن
          • متوقف کردن عملیات
          • هیچ گاه در بلاک catch هیچ منطقی را پیاده نکنید.


          حالت دیگر در استفاه از کتابخانه‌های دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:

          • جلوی این نوع استثنائات را در پایین‌ترین حد ممکن در کد خود بگیرید.
          • Exception هایی را catch کنید که میدانید در حالت استثناء، چه کاری را می‌توانید انجام دهید.


          این به این معنی میباشد که به صورت کلی همه نوع Exception ای را به صورت کلی نگیرید و نوع Exception اختصاصی را در بلاک catch قرار دهید. الان که قرار شد در بعضی از حالت‌ها جلوی استثنائات را بگیریم، خوب است ببینیم چطور باید اینکار را انجام بدیم.

          قطعه کد زیر را در نظر بگیرید:

          public void CreateCustomer(string name) { 
              Customer customer = new Customer(name); 
              bool result = SaveCustomer(customer);
              if (!result) { 
                  MessageBox.Show("Error connecting to the database. Please try again later.");
              }
          }
          
          private bool SaveCustomer(Customer customer) { 
              try { 
                  using (MyContext context = new MyContext()) { 
                      context.Customers.Add(customer);
                   context.SaveChanges();
                  } 
                  return true;
              }
              catch (DbUpdateException ex) { 
                  if (ex.Message == "Unable to open the DB connection") 
                      return false; 
                  else 
                      throw;
              }
          }

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

          در اینجا من این نوع را با نام کلاس Result معرفی میکنم. انتظاری که از این نوع اختصاصی داریم:

          • Honest بودن متد را نگه دارد.
          • خروجی متد را به همراه وضعیت اجرا شدن برگرداند.
          • شکل یکسانی را برای خطا‌ها داشته باشد.
          • فقط جلوی خطا‌های غیر منتظره را بگیرد.


          برای مثال کد بالا را به شکل زیر refactor می‌کنیم:

          private Result SaveCustomer(Customer customer) { 
              try { 
          
                  using (var context = new MyContext()) { 
          
                      context.Customers.Add(customer); 
                      context.SaveChanges();
           } 
          
                  return Result.Ok();
              } 
              catch (DbUpdateException ex) { 
                  if (ex.Message == "Unable to open the DB connection") 
                      Result.Fail(ErrorType.DatabaseIsOffline);
          
                  if (ex.Message.Contains("IX_Customer_Name")) 
                      return Result.Fail(ErrorType.CustomerAlreadyExists);
          
                  throw;
              }
          }

          به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.

          اگر به امضای متد‌های زیر نگاه کنیم، می‌توانیم آن‌ها را طبق الگوی CQS دسته‌بندی کنیم: 

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

          مطالب
          آشنایی با CLR: قسمت نوزدهم
          در فصل دوم کتاب تا به الان یاد گرفتیم چگونه ماژول‌ها را کامپایل کنیم و چگونه آن‌ها را در یک اسمبلی قرار دهیم. حال وقت آن فرا رسیده است که با بسته بندی کردن (Package) و انتشار آن (Deploy) به طوری که کاربران بتوانند برنامه را اجرا کنند آشنا شویم.

          نصب برنامه از طریق فروشگاه ویندوز
          در فروشگاه ویندوز Windows Store Apps قوانین سخت و شدیدی برای بسته بندی کردن اسمبلی‌ها وجود دارد. ویژوال استودیو تمام اسمبلی‌های مورد نیاز برنامه را در یک فایل با پسوند appx قرار داده و آن را به سمت فروشگاه آپلود می‌کند. هر کاربری که این فایل appx را نصب کند، همه‌ی اسمبلی‌هایی را که در دایرکتوری مربوطه قرار گرفته است، توسط CLR بار شده و آیکن برنامه هم در صفحه‌ی start ویندوز قرار می‌گیرد و اگر دیگر کاربران همان سیستم هم این فایل appx را نصب کنند، از آنجا که قبلا روی سیستم موجود هست، تنها آیکن برنامه به صفحه‌ی start اضافه می‌گردد و برای حذف هم تنها آیکن برنامه از روی این صفحه حذف می‌شود؛ مگر اینکه تنها کاربری باشد که این برنامه را نصب کرده‌است که در آن صورت کلا همه‌ی اسمبلی‌های آن از روی سیستم حذف می‌شود.
          در صورتیکه کاربرهای مختلف نسخه‌های مختلفی از همان برنامه را روی سیستم نصب کنند، برای اسمبلی‌ها هر کدام یک دایرکتوری ایجاد شده و به ازای نسخه‌ی نصب شده آن کاربر، یکی از این دایرکتوری‌ها مورد استفاده قرار می‌گیرند. کاربران مختلف می‌توانند روی سیستم به طور همزمان از نسخه‌های مختلف برنامه استفاده کنند.

          روش‌های پکیج گذاری
          برای برنامه‌های دسکتاپ که ربطی به فروشگاه ندارند و بین ایرانیان طرفدار زیادی دارد، نیازی به استفاده از هیچ روش خاصی نیست و یک کپی معمولی هم کفایت می‌کند. همه‌ی فایل‌های مثل اسمبلی، باید در یک دایرکتوری قرار گرفته و به روش کپی کردن آن را انتقال داد. یا برای بسته بندی از یک فایل batch کمک گرفت و آن را روی سیستم نصب کرد و نیازی به هیچ تغییری در رجیستری نیست. برای حذف برنامه هم، حذف معمولی دایرکتوری مربوطه کفایت می‌کند.
          البته گزینه‌های دیگری هم برای پکیج کردن این نوع برنامه‌ها وجود دارند:
          یکی از روش‌های پکیج کردن فایل‌ها به صورت cab هست که عموما برای سناریوهای اینترنتی و فشرده سازی و کاهش زمان دانلود به کار می‌رود.
          روش دوم استفاده از پکیج MSI است که توسط سرویس نصب مایکروسافت Microsoft Installer Service یا MSIExec.exe انجام می‌گیرد. فایل‌های MSI به اسمبلی‌ها اجازه می‌دهند که بر اساس زمان تقاضای CLR برای بارگیری اولیه نصب شوند. البته این ویژگی جدیدی نیست و برای فایل‌های exe یا dll مدیریت نشده هم به کار می‌رود.

          استفاده از نصاب سازها
          بهتر هست که برای انتشار برنامه از برنامه‌های نصاب سازی استفاده کنید که با واسطی جذاب‌تر به نصب پرداخته و امکاناتی از قبیل shotrcut‌ها، حذف و بازیابی و نصب و .. را هم به کاربر می‌دهند.
          نصاب سازهای متفاوتی وجود دارند که در زیر به تعدادی از آنها اشاره می‌کنیم:
          Install Shield (+ ) : این برنامه نسخه‌های متفاوتی را با قیمت‌های متفاوتی، عرضه می‌کند و در این زمینه، جزء بهترین‌ها نام برده می‌شود. حتی ویژگی‌های مخصوصی هم برای ویژوال استودیو دارد. شرکت سازنده، برنامه‌ی دیگری را هم اخیر تحت نام Install Anywhere عرضه کرده است که اجازه می‌دهد از روی یک برنامه برای پلتفرم‌های مختلف setup بسازد.

          NSIS : این برنامه هم در زمینه‌ی ساخت setup محبوبیت زیادی دارد. این برنامه به صورت متن باز منتشر شده و رایگان است. امکانات این برنامه ساده است و برای راه اندازی سریع یک setup و اجرای راحت آن توسط کاربر، کاملا کاربردی است.

          Tarma Installmate : این نرم افزار نسبت به InstallShield ساده‌تر و کم حجم‌تر است. حداقل برای برنامه‌های عادی امکانات مناسبی دارد.

          DeployMaster : یک برنامه‌ی دیگر با امکانات حرفه‌ای جهت انشار برنامه‌های دسکتاپ، که از ویندوز 98 تا 8.1 را در حال حاضر پشتیبانی می‌کند.

          QSetup Installation Suite : یک برنامه‌ی نصب حرفه‌ای که فایل نهایی آن می‌تواند به دو فرمت exe یا MSI باشد و قابلیت‌هایی چون پشتیبانی از زبان فارسی، ورود لایسنس، سریال نرم افزار و ... را نیز پشتیبانی می‌کند.

          Inno Setup : این برنامه هم امکانات خوبی را برای ساخت یک نصاب ساز دارد و همچنین از زبان پاسکال جهت اسکریپت نویسی جهت توسعه امکانات بهره می‌برد.

          Visual Patch : وب سایت پی سی دانلود این برنامه را اینگونه توضیح می‌دهد:
          نرم افزار Visual Patch یک ابزار توسعه یافته‌ی نرم افزاری برای ساخت پچ و آپدیت برنامه‌ها می‌باشد. این سازنده پچ باینری، استفاده از فشرده سازی داده DeltaMAX برای سریع‌تر کردن توسعه‌ی نرم افزار، یکپارچگی با نصب نرم افزار و ابزار‌های مدیریت پچ از فروشندگانی نظیر Installshield, Lumension, Patchlink, Shavlik, Indigo Rose و ...، را به طور برجسته نمایان ساخته است.
          با استفاده از این ابزار پچ کردن برنامه‌ها که برای توسعه دهندگان نرم افزار و برنامه نویسان طراحی شده است، توزیع نرم افزار و سیستم گسترش پچ بهبود می‌یابد. Visual Patch الگوریتم‌های فشرده سازی و state-of- the-art binary differencing را نمایان می‌سازد و این کمک می‌کند که شما به کوچکتر شدن و بهتر شدن پچ‌های نرم افزار اطمینان داشته باشید.  
          و ...

          انتشار توسط ویژوال استودیو
          ویژوال استودیو هم امکانات خوبی برای انتشار در بخش Properties پروژه، برگه‌ی publish ارائه می‌کند و فایل MSI نتیجه را به سمت وب سرور، FTP Server یا روی دیسک ارسال میکند. یکی از خصوصیات خوب این روش این است که میتواند پیش نیازهایی مانند فریم ورک دات نت یا sql server Express را به سیستم اضافه کنید؛ در نهایت با مزیت آپدیت و نصب تک کلیکی، کاربر، برنامه را بر روی سیستم نصب کند.

          اسمبلی‌های انتشاریافته اختصاصی
          در روش‌هایی که ذکر کردیم، از آنجا که اسمبلی‌ها در همان شاخه یا دایرکتوری برنامه قرار گرفته‌اند و نمی‌توان آن‌ها را با برنامه‌های دیگر به اشتراک گذاشت (مگر اینکه برنامه دیگری را هم در همان دایرکتوری قرار داد) به این روش Privately Deployed Assemblies می‌گویند. این روش برگ برنده بزرگی برای برنامه نویسان، کاربران و مدیران سیستم‌ها محسوب می‌شود. زیرا که جابجایی آنها راحت بوده و CLR در همانجا اسمبلی‌ها را در حافظه بار کرده و اجرا می‌کند. در این نوع برنامه‌ها عملیات نصب/جابجایی/ حذف به راحتی صورتی میگرد و نیازی به تنظیمات خارجی مانند رجیستری ندارد. یکی از خصوصیات مهمی که دارد این هست که جداول متادیتا به اسمبلی اشاره می‌کنند که برنامه بر پایه آن ساخته شده و با آن تست شده است؛ نه با اسمبلی موجود دیگر در سیستم که شاید نام نوع مورد استفاده آن یا اسمبلی آن به طور تصادفی با آن یکی است.
          در مقالات آتی در مورد اشتراک گذاری اسمبلی‌ها بین چند برنامه مفصل‌تر صحبت خواهیم کرد.

          مطالب
          Test Driven Development
          نوشتن  تست برای نرم افزار امری ضروریست، چه پس از تولید نرم افزار چه در حین تولید، در کل به وسیله تست می‌توان از به وجود آمدن باگ‌ها در هنگام گسترش دادن برنامه تا حد قابل توجهی جلوگیری کرد.
          از معروف ترین روش‌های تست می‌توان عناوین زیر را نام برد:
          • Unit test
          • Integration test
          • Smoke test
          • Regression test
          • Acceptance test

           Test Driven Development  یک پروسه تولید نرم افزار است که برای اولین بار توسط Kent_Beck معرفی شد.
          TDD شامل 4 مرحله کلی است:
          1. نوشتن تست قبل از نوشتن کد.
          2. کامپایل کردن کد و اطمینان از Fail شدن کامپایل.
          3. پیاده سازی کد به طوری که تست ما پاس شود.
          4. Refactoring 
          مراحل 4 گانه تست باید به صورت متناوب اجرا شوند.
          البته بسیاری این 4 مورد را با عبارت  red/green/refactor نیز می‌شناسند.

          همانطور که گفته شد در کل نوشتن تست باعث می‌شود که با اضافه شدن کدهای جدید در برنامه از به وجود آمدن باگ تا حدی جلوگیری شود.
          اما مزایای TDD:
          • TDD باعث کاهش زمان تولید نرم افزار می‌شود.البته این حرف کمی عجیب است.(در ادامه بیشتر توضیح می‌دهم)
          • اعتماد شما نسبت به کد بالا می‌رود.
          • باگ کمتری تولید می‌شود بتابراین اعتماد مصرف کنندگان نیز نسبت به برنامه شما بالا می‌رود.
          • باعث نظم در کد می‌شود.
          • باعث انعطاف پذیری بیشتر در نرم افزار می‌شود.
          • ریسک تولید نرم افزار به علت باگ کمتر به حداقل می‌سد.
          • ...

          البته باید به این نکته نیز اشاره داشت که مایکروسافت تحقیقی انجام داده که بر طبق آن نوشتن کد به روش TDD می‌تواند 15 تا 30 در صد روند تولید نرم افزار را افزایش دهد ولی در عوض بین 40 تا 90 در صد می‌تواند از به وجود آمدن باگ جدید جلوگیری کند.
          در بسیاری از محیط‌های برنامه نویسی، نه تنها به این موضوع اهمیت داده نمی‌شود بلکه به طور کلی به اشتباه گرفته شده و حتی در پروژه هایی که تست نوشته می‌شود مفاهیم آن( که در بالا نام برده شده )جابجا شده و به اشتباه نام برده می‌شود.
          هدف از نوشتن تست،تست کردن قطعات کوچک کد است,به عنوان مثال نباید تست به گونه ای باشد که یک متد با 300 خط کد را تحت پوشش قرار دهد.ابتدا باید کد به قطعات کوچک شکسته و بعد تست شود.
          یک نمونه از متد تست:
                  [Test]
                  public void TestFullName()
                  {
                      
                      Person person = new Person ();
                      person.lname = "Doe";
                      person.mname = "Roe";
                      person.fname = "John";
          
                      string actual = person.GetFullName();
                      string expected = "John Roe Doe";
                      Assert.AreEqual(expected, actual, ”The GetFullName returned a different Value”);
                  }
          هدف از نوشتن این پست مقدمه ای بر شروع سری پست‌های TDD باستفاده از MVC.Net و فریم ورک قدرت مند تست Nunit است. 
          نظرات مطالب
          انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
          سوالی که واسه من پیش اومده اینه که من یک نرم افزار Web Form  دارم . هدفم اینه که یک روز قبل از تاریخ تولد اعضا ایمیلی با عنوان تولدتان مبارک ارسال شه .حالا من باید این کد‌های گفته شده را در برنامه خود بیاورم یا یک وب سرویس بنویسم که اینکارو انجام بده؟اگه بخوام که در برنامه انجام بشه خوب کجا باید نوشت ؟ یا به عبارتی دیگه باید حتما برنامه اجرا بشه و اگه برنامه اصلا Run نشه چی میشه؟
          مطالب
          خلاصه اشتراک‌های روز یک شنبه 1390/07/03