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

سپس با کلیک بروی قسمت مشخص شده از تصویر بالا تصویر مانند زیر ظاهر شود.

در این نوشته قصد داریم کادری به این صورت حالا به هر منظوری طراحی نماییم.

برای کار سه قسمت کد داریم:

  1. کدهای طراحی قسمت مورد نظر در صفحه وب
  2. نوشتن کدهای CSS مربوطه
  3. نوشتن کدهای jQuery

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

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>کادر لغزان با jQuery</title>
    <script src="Scripts/jquery-1.7.1.min.js"></script>
    <link href="CSS/site.css" rel="stylesheet" />
</head>
<body>
    <div id="loginPanel">
        <div style="height: auto;" id="login">
            <div>
                <div>
                    <br />
                    محتویات دلخواه خود را در این قسمت قرار دهید
                </div>
            </div>
            <div><a href="#" id="closeLogin"></a></div>
        </div>
        <div id="container">
            <div id="top">
                <!-- login -->
                <ul>
                    <li>&nbsp;</li>
                    <li><a id="toggleLogin" href="#">پانل باز شو</a></li>
                </ul>
                <!-- / login -->
            </div>
            <!-- / top -->

        </div>
    </div>
    <div id="main">
        محتویات سایت در این قسمت قرار می‌گیرد
    </div>
    </body>
</html>
در صفحه ایجاد شده قسمتی را برای نگهداری پانل مورد نظر قرار دادیم و در div با نام loginContent مواردی را که می‌خواهیم در پانل مربوطه نمایش داده شود، قرار می‌دهیم،  <"div id="loginPanel> نگهدارنده کل قسمت مربوطه (کادر لغزان می‌باشد)، و قسمت <"div id="container> قسمتی است که دکمه یا عنوان مورد نظر برای باز شدن یا بستن کادر استفاده می‌شود.
در مرحله دوم کدهای CSS بخش‌های مورد نظر (جهت رنگ و تصاویر و شکل و شمایل کادر مورد نظر) را مانند زیر ایجاد می‌کنیم.
body {
     margin:0; 
     padding:0; 
     width:100%; 
     background: #e9e9e9 url(images/header_bg.gif) top repeat-x;
     direction: rtl;
}
html {
     padding:0; 
     margin:0;
}

#main {margin-top: 100px;}

#loginPanel {
    margin: 0px; 
    position: absolute; 
    overflow: hidden; 
    height: auto;
    z-index: 3000;
    width: 100%;
    top: 0px;
    color: #fff;
}
#top {
    background: url(images/login_top.jpg) repeat-x 0 0;
    height: 38px;
    position: relative;
}
#top ul.login {
    display: block;
    position: relative;
    float: right;
    clear: right;
    height: 38px;
    width: auto;
    margin: 0;
    right: 150px;
    color: white;
    text-align: center;
    background: url(images/login_r.png) no-repeat right 0;
    padding-right: 45px;
}
#top ul.login li.left {
    background: url(images/login_l.png) no-repeat left 0;
    height: 38px;
    width: 45px;
    padding: 0;
    margin: 0;
    display: block;
    float: left;
}
#top ul.login li {
    text-align: left;
    padding: 0 6px;
    display: block;
    float: left;
    height: 38px;
    background: url(images/login_m.jpg) repeat-x 0 0;
}
#top ul.login li a {
    color: #fff;
    text-decoration: none;
}
#top ul.login li a:hover {
    color: #ff0000;
    text-decoration: none;
}
#login {
    width: 100%;
    color: white;
    background: #1E1E1E;
    overflow: hidden;
    position: relative;
    z-index: 3;
    height: 0px;
}
#login a {
    text-decoration: none;
    color: #fff;
}
#login a:hover {
    color: white;
    text-decoration: none;
}
#login .loginContent {
    width: 900px;
    height: 80px;
    margin: 0 auto;
    padding-top: 25px;
    text-align: right;
}
#login .loginClose {
    display: block;
    position: absolute;
    right: 15px;
    top: 10px;
    width: 70px;
    text-align: left;
}
#login .loginClose a {
    display: block;
    width: 100%;
    height: 20px;
    background: url(images/button_close.jpg) no-repeat right 0;
    padding-right: 10px;
    border: none;
    color: white;
}
#login .loginClose a:hover {
    background: url(images/button_close.jpg) no-repeat right -20px;
}
.cen { text-align: center;}
.w_100p{ width: 100%;}
خوب تا اینجای کار فقط کادر با قالب مورد نظر ایجاد شد، برای اینکه عمل مورد نظر انجام شود با استفاده از تکنیک‌های jQuery به صورت زیر کار را به پایان می‌رسانیم. در انتهای صفحه اسکریپت زیر را قبل از قسمت <body/> می‌نویسیم.
 <script type="text/javascript">
        $(document).ready(function () {
            $("#login").hide(0);
            $("#toggleLogin").click(function () {
                $("#login").slideToggle("slow");
            });
            $("#closeLogin").click(function () {
                $("#login").slideUp("slow");
            });
        });
    </script>
با نوشتن این اسکریپت بعد از لود صفحه مورد نظر ابتدا کادر ما مخفی می‌شود، سپس برای دکمه (یا هر المانی که می‌خواهیم با کلیک روی آن کادر بسته یا باز شود) کد کلیک می‌نویسیم که با کلیک بروی آن عمل اسلاید (باز یا بسته شدن) رخ دهد. در نهایت در رویداد کلیک لینک close تعیین می‌کنیم که کادر به آرامی  بسته شود.
مثال کامل از ^ قابل دانلود است
لینک‌های کمکی جهت آشنایی بیشتر با توابع استفاده شده:




مطالب
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 داشتیم. کامپوننت‌های مختلف فریم ورک و نحوه پیکربندی آنها را بررسی کردیم و با تغییرات و قابلیت‌های جدید به اختصار آشنا شدیم. در مقالات بعدی بررسی هایی عمیق‌تر خواهیم داشت و با نحوه استفاده و پیاده سازی قسمت‌های مختلف این فریم ورک آشنا خواهیم شد.


مطالعه بیشتر
مطالب
MEF و الگوی Singleton

در مورد معرفی مقدماتی MEF می‌توانید به این مطلب مراجعه کنید و در مورد الگوی Singleton به اینجا.


کاربردهای الگوی Singleton عموما به شرح زیر هستند:
1) فراهم آوردن دسترسی ساده و عمومی به DAL (لایه دسترسی به داده‌ها)
2) دسترسی عمومی به امکانات ثبت وقایع سیستم در برنامه logging -
3) دسترسی عمومی به تنظیمات برنامه
و موارد مشابهی از این دست به صورتیکه تنها یک روش دسترسی به این اطلاعات وجود داشته باشد و تنها یک وهله از این شیء در حافظه قرار گیرد.

با استفاده از امکانات MEF دیگر نیازی به نوشتن کدهای ویژه تولید کلاس‌های Singleton نمی‌باشد زیرا این چارچوب کاری دو نوع روش وهله سازی از اشیاء (PartCreationPolicy) را پشتیبانی می‌کند: Shared و NonShared . حالت Shared دقیقا همان نام دیگر الگوی Singleton است. البته لازم به ذکر است که حالت Shared ، حالت پیش فرض تولید وهله‌ها بوده و نیازی به ذکر صریح آن همانند ویژگی زیر نیست:
[PartCreationPolicy(CreationPolicy.Shared)]

مثال:
فرض کنید قرار است از کلاس زیر تنها یک وهله بین صفحات یک برنامه‌ی Silverlight توزیع شود. با استفاده از ویژگی‌ Export به MEF اعلام کرده‌ایم که قرار است سرویسی را ارائه دهیم :

using System;
using System.ComponentModel.Composition;

namespace SlMefTest
{
[Export]
public class WebServiceData
{
public int Result { set; get; }

public WebServiceData()
{
var rnd = new Random();
Result = rnd.Next();
}
}

}
اکنون برای اثبات اینکه تنها یک وهله از این کلاس در اختیار صفحات مختلف قرار خواهد گرفت، یک User control جدید را به همراه یک دکمه که مقدار Result را نمایش می‌دهد به برنامه اضافه خواهیم کرد. دکمه‌ی دیگری را نیز به همین منظور به صفحه‌ی اصلی برنامه اضافه می‌کنیم.
کدهای صفحه اصلی برنامه (که از یک دکمه و یک Stack panel جهت نمایش محتوای یوزر کنترل تشکیل شده) به شرح بعد هستند:
<UserControl x:Class="SlMefTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<Button Content="MainPageButton" Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0" Name="button1"
VerticalAlignment="Top" Width="98" Click="button1_Click" />
<StackPanel Name="panel1" Margin="5"/>
</StackPanel>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class MainPage
{
[Import]
public WebServiceData Data { set; get; }

public MainPage()
{
InitializeComponent();
this.Loaded += mainPageLoaded;
}

void mainPageLoaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
panel1.Children.Add(new SilverlightControl1());
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
با استفاده از ویژگی Import به MEF اعلام می‌کنیم که به اطلاعاتی از نوع شیء WebServiceData نیاز داریم و توسط متد CompositionInitializer.SatisfyImports کار وهله سازی و پیوند زدن export و import های همانند صورت می‌گیرد. سپس استفاده‌ی مستقیم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

کدهای User control ساده اضافه شده به شرح زیر هستند:

<UserControl x:Class="SlMefTest.SilverlightControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Button Content="UserControlButton"
Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Name="button1"
VerticalAlignment="Top"
Width="125"
Click="button1_Click" />
</Grid>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class SilverlightControl1
{
[Import]
public WebServiceData Data { set; get; }

public SilverlightControl1()
{
InitializeComponent();
this.Loaded += silverlightControl1Loaded;
}

void silverlightControl1Loaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
اکنون قبل از شروع برنامه یک break point را در سازنده‌ی کلاس WebServiceData قرار دهید. سپس برنامه را آغاز نمائید. تنها یکبار این سازنده فراخوانی خواهد شد (هر چند در دو کلاس کار Import اطلاعات WebServiceData صورت گرفته است). همچنین با کلیک بر روی دو دکمه‌ای که اکنون در صفحه‌ی اصلی برنامه ظاهر می‌شوند، فقط یک عدد مشابه نمایش داده می‌شود (با توجه به اینکه اطلاعات هر دکمه در یک وهله‌ی جداگانه قرار دارد؛ یکی متعلق است به صفحه‌ی اصلی و دیگری متعلق است به user control اضافه شده).

مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت پنجم - محافظت از مسیرها
در قسمت سوم، کار ورود به سیستم و امکان مشاهده‌ی صفحه‌ی محافظت شده‌ی پس از لاگین را پیاده سازی کردیم. در این قسمت می‌خواهیم امکان دسترسی به مسیر http://localhost:4200/protectedPage را کنترل کنیم. تا اینجا اگر کاربر بدون لاگین کردن نیز این مسیر را درخواست کند، می‌تواند حداقل ساختار ابتدایی آن‌را مشاهده کند که باید مدیریت شود و این مدیریت دسترسی می‌تواند بر اساس وضعیت لاگین کاربر و همچنین نقش‌های او در سیستم باشد:


طراحی بخش‌هایی از این قسمت، از پروژه‌ی «کنترل دسترسی‌ها در Angular با استفاده از Ng2Permission» ایده گرفته شده‌اند.


استخراج اطلاعات کاربر وارد شده‌ی به سیستم از توکن دسترسی او

یکی از روش‌های دسترسی به اطلاعات کاربر در سمت کلاینت، مانند نقش‌های او، تدارک متدی در سمت سرور و بازگشت Claims او به سمت کلاینت است:
public IActionResult Get()
{ 
    var user = this.User.Identity as ClaimsIdentity; 
    var config = new 
    { 
        userName = user.Name, 
        roles = user.Claims.Where(x => x.Type == ClaimTypes.Role).Select(x => x.Value).ToList() 
    }; 
    return Ok(config); 
}
 اما با توجه به اینکه در زمان لاگین، نقش‌های او (و سایر Claims دلخواه) نیز به توکن دسترسی نهایی اضافه می‌شوند، می‌توان این کوئری گرفتن مدام از سرور را تبدیل به روش بسیار سریعتر استخراج آن‌ها از توکنی که هم اکنون در کش مرورگر ذخیره شده‌است، کرد.
همچنین باید دقت داشت چون این توکن دارای امضای دیجیتال است، کوچکترین تغییری در آن در سمت کاربر، سبب برگشت خوردن خودکار درخواست ارسالی به سمت سرور می‌شود (یکی از مراحل اعتبارسنجی کاربر در سمت سرور، اعتبارسنجی توکن دریافتی (قسمت cfg.TokenValidationParameters) و همچنین بررسی خودکار امضای دیجیتال آن است). بنابراین نگرانی از این بابت وجود ندارد.
اگر اطلاعات کاربر در سمت سرور تغییر کنند، با اولین درخواست ارسالی به سمت سرور، رخ‌داد OnTokenValidated وارد عمل شده و درخواست ارسالی را برگشت می‌زند (در مورد پیاده سازی سمت سرور این مورد، در مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» بیشتر بحث شده‌است). در این حالت کاربر مجبور به لاگین مجدد خواهد شد که این مورد سبب به روز رسانی خودکار اطلاعات توکن‌های ذخیره شده‌ی او در مرورگر نیز می‌شود.

اگر از قسمت دوم این سری به‌خاطر داشته باشید، توکن decode شده‌ی برنامه، چنین شکلی را دارد:
{
  "jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a",
  "iss": "http://localhost/",
  "iat": 1513070340,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid",
  "DisplayName": "وحید",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber": "709b64868a1d4d108ee58369f5c3c1f3",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
    "Admin",
    "User"
  ],
  "nbf": 1513070340,
  "exp": 1513070460,
  "aud": "Any"
}
به همین جهت متدی را برای تبدیل این اطلاعات به شیء کاربر، ایجاد خواهیم کرد و در سراسر برنامه از این اطلاعات آماده، برای تعیین دسترسی او به قسمت‌های مختلف برنامه‌ی سمت کلاینت، استفاده می‌کنیم.
برای این منظور اینترفیس src\app\core\models\auth-user.ts را به صورت ذیل ایجاد می‌کنیم:
export interface AuthUser {
  userId: string;
  userName: string;
  displayName: string;
  roles: string[];
}
پس از آن متد getAuthUser را جهت استخراج خواص ویژه‌ی توکن دسترسی decode شده‌ی فوق، به صورت ذیل به سرویس Auth اضافه می‌کنیم:
  getAuthUser(): AuthUser {
    if (!this.isLoggedIn()) {
      return null;
    }

    const decodedToken = this.getDecodedAccessToken();
    let roles = decodedToken["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"];
    if (roles) {
      roles = roles.map(role => role.toLowerCase());
    }
    return Object.freeze({
      userId: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"],
      userName: decodedToken["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],
      displayName: decodedToken["DisplayName"],
      roles: roles
    });
  }
کار با این متد بسیار سریع است و نیازی به رفت و برگشتی به سمت سرور ندارد؛ چون تمام اطلاعات استخراجی توسط آن هم اکنون در کش سریع مرورگر کلاینت موجود هستند. استفاده‌ی از متد Object.freeze هم سبب read-only شدن این خروجی می‌شود.
همچنین در اینجا تمام نقش‌های دریافتی، تبدیل به LowerCase شده‌اند. با اینکار مقایسه‌ی بعدی آن‌ها با نقش‌های درخواستی، حساس به بزرگی و کوچکی حروف نخواهد بود.


تعریف نقش‌های دسترسی به مسیرهای مختلف سمت کلاینت

مرسوم است اطلاعات اضافی مرتبط با هر مسیر را به خاصیت data آن route انتساب می‌دهند. به همین جهت به فایل dashboard-routing.module.ts مراجعه کرده و نقش‌های مجاز به دسترسی به مسیر protectedPage را به خاصیت data آن به صورت ذیل اضافه می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";
import { AuthGuardPermission } from "../core/models/auth-guard-permission";

const routes: Routes = [
  {
    path: "protectedPage",
    component: ProtectedPageComponent,
    data: {
      permission: {
        permittedRoles: ["Admin"],
        deniedRoles: null
      } as AuthGuardPermission
    }
  }
];
که در اینجا ساختار اینترفیس AuthGuardPermission، در فایل جدید app\core\models\auth-guard-permission.ts به صورت ذیل تعریف شده‌است:
export interface AuthGuardPermission {
  permittedRoles?: string[];
  deniedRoles?: string[];
}
به این ترتیب هر قسمتی از برنامه که نیاز به اطلاعات سطوح دسترسی مسیری را داشت، ابتدا به دنبال route.data["permission"] خواهد گشت (کلیدی به نام permission در خاصیت data یک مسیر) و سپس اطلاعات آن‌را بر اساس ساختار اینترفیس AuthGuardPermission، تحلیل می‌کند.
در اینجا تنها باید یکی از خواص permittedRoles  (نقش‌های مجاز به دسترسی/صدور دسترسی فقط برای این نقش‌های مشخص، منهای مابقی) و یا deniedRoles (نقش‌های غیرمجاز به دسترسی/دسترسی همه‌ی نقش‌های ممکن، منهای این نقش‌های تعیین شده)، مقدار دهی شوند.


افزودن کامپوننت «دسترسی ندارید» به ماژول Authentication

در ادامه می‌خواهیم اگر کاربری به مسیری دسترسی نداشت، به صورت خودکار به صفحه‌ی «دسترسی ندارید» هدایت شود. به همین جهت این کامپوننت را به صورت ذیل به ماژول authentication اضافه می‌کنیم:
>ng g c Authentication/AccessDenied
با این خروجی که سبب درج خودکار آن در قسمت declaration فایل authentication.module نیز می‌شود:
 AccessDenied
  create src/app/Authentication/access-denied/access-denied.component.html (32 bytes)
  create src/app/Authentication/access-denied/access-denied.component.ts (296 bytes)
  create src/app/Authentication/access-denied/access-denied.component.css (0 bytes)
  update src/app/Authentication/authentication.module.ts (550 bytes)
سپس به فایل authentication-routing.module.ts مراجعه کرده و مسیریابی آن‌را نیز اضافه می‌کنیم:
import { LoginComponent } from "./login/login.component";
import { AccessDeniedComponent } from "./access-denied/access-denied.component";

const routes: Routes = [
  { path: "login", component: LoginComponent },
  { path: "accessDenied", component: AccessDeniedComponent }
];
قالب access-denied.component.html را نیز به صورت ذیل تکمیل می‌کنیم:
<h1 class="text-danger">
  <span class="glyphicon glyphicon-ban-circle"></span> Access Denied
</h1>
<p>Sorry! You don't have access to this page.</p>
<button class="btn btn-default" (click)="goBack()">
  <span class="glyphicon glyphicon-arrow-left"></span> Back
</button>

<button *ngIf="!isAuthenticated" class="btn btn-success" [routerLink]="['/login']"
  queryParamsHandling="merge">
  Login
</button>
که دکمه‌ی Back آن به کمک سرویس Location، صورت ذیل پیاده سازی شده‌است:
import { Component, OnInit } from "@angular/core";
import { Location } from "@angular/common";

import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-access-denied",
  templateUrl: "./access-denied.component.html",
  styleUrls: ["./access-denied.component.css"]
})
export class AccessDeniedComponent implements OnInit {

  isAuthenticated = false;

  constructor(
    private location: Location,
    private authService: AuthService
  ) {
  }

  ngOnInit() {
    this.isAuthenticated = this.authService.isLoggedIn();
  }

  goBack() {
    this.location.back(); // <-- go back to previous location on cancel
  }
}


در اینجا اگر کاربر به سیستم وارد نشده باشد، دکمه‌ی لاگین نیز به او نمایش داده می‌شود. همچنین وجود "queryParamsHandling="merge در لینک مراجعه‌ی به صفحه‌ی لاگین، سبب خواهد شد تا query string موجود در صفحه نیز حفظ شود و به صفحه‌ی لاگین انتقال پیدا کند. در صفحه‌ی لاگین نیز جهت پردازش این نوع کوئری استرینگ‌ها، تمهیدات لازم درنظر گرفته شده‌اند.
دکمه‌ی back آن نیز توسط سرویس توکار Location واقع در مسیر angular/common@ پیاده سازی شده‌است.


ایجاد یک محافظ مسیر سمت کلاینت برای بررسی وضعیت کاربر جاری و همچنین نقش‌های او

پس از تعریف متد getAuthUser و استخراج اطلاعات کاربر از توکن دسترسی دریافتی که شامل نقش‌های او نیز می‌شود، اکنون می‌توان متد بررسی این نقش‌ها را نیز به سرویس Auth اضافه کرد:
  isAuthUserInRoles(requiredRoles: string[]): boolean {
    const user = this.getAuthUser();
    if (!user || !user.roles) {
      return false;
    }
    return requiredRoles.some(requiredRole => user.roles.indexOf(requiredRole.toLowerCase()) >= 0);
  }

  isAuthUserInRole(requiredRole: string): boolean {
    return this.isAuthUserInRoles([requiredRole]);
  }
متد some در جاوا اسکریپت شبیه به متد Any در C# LINQ عمل می‌کند. همچنین در مقایسه‌ی صورت گرفته، با توجه به اینکه user.roles را پیشتر به LowerCase تبدیل کرد‌یم، حساسیتی بین نقش Admin و admin و کلا کوچکی و بزرگی نام نقش‌ها وجود ندارد.
اکنون در هر قسمتی از برنامه که نیاز به بررسی امکان دسترسی یک کاربر به نقش یا نقش‌هایی خاص وجود داشته باشد، می‌توان AuthService را به سازنده‌ی ‌آن تزریق و سپس از متد فوق جهت بررسی نهایی، استفاده کرد.

در ادامه یک Route Guard جدید را در مسیر app\core\services\auth.guard.ts ایجاد می‌کنیم. کار آن بررسی خودکار امکان دسترسی به یک مسیر درخواستی است:
import { Injectable } from "@angular/core";
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";

import { AuthService } from "./auth.service";
import { AuthGuardPermission } from "../models/auth-guard-permission";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    if (!this.authService.isLoggedIn()) {
      this.showAccessDenied(state);
      return false;
    }

    const permissionData = route.data["permission"] as AuthGuardPermission;
    if (!permissionData) {
      return true;
    }

    if (Array.isArray(permissionData.deniedRoles) && Array.isArray(permissionData.permittedRoles)) {
      throw new Error("Don't set both 'deniedRoles' and 'permittedRoles' in route data.");
    }

    if (Array.isArray(permissionData.permittedRoles)) {
      const isInRole = this.authService.isAuthUserInRoles(permissionData.permittedRoles);
      if (isInRole) {
        return true;
      }

      this.showAccessDenied(state);
      return false;
    }

    if (Array.isArray(permissionData.deniedRoles)) {
      const isInRole = this.authService.isAuthUserInRoles(permissionData.deniedRoles);
      if (!isInRole) {
        return true;
      }

      this.showAccessDenied(state);
      return false;
    }
  }

  private showAccessDenied(state: RouterStateSnapshot) {
    this.router.navigate(["/accessDenied"], { queryParams: { returnUrl: state.url } });
  }
}
در اینجا در ابتدا وضعیت لاگین کاربر بررسی می‌گردد. این وضعیت نیز از طریق سرویس Auth که به سازنده‌ی کلاس تزریق شده‌است، تامین می‌شود. اگر کاربر هنوز لاگین نکرده باشد، به صفحه‌ی عدم دسترسی هدایت خواهد شد.
سپس خاصیت permission اطلاعات مسیر استخراج می‌شود. اگر چنین مقداری وجود نداشت، همینجا کار با موفقیت خاتمه پیدا می‌کند.
در آخر وضعیت دسترسی به نقش‌های استخراجی deniedRoles و permittedRoles که از اطلاعات مسیر دریافت شدند، توسط متد isAuthUserInRoles سرویس Auth بررسی می‌شوند.

در متد showAccessDenied کار ارسال آدرس درخواستی (state.url) به صورت یک کوئری استرینگ (queryParams) با کلید returnUrl به صفحه‌ی accessDenied صورت می‌گیرد. در این صفحه نیز دکمه‌ی لاگین به همراه "queryParamsHandling="merge است. یعنی کامپوننت لاگین برنامه، کوئری استرینگ returnUrl را دریافت می‌کند:
 this.returnUrl = this.route.snapshot.queryParams["returnUrl"];
 و پس از لاگین موفق، در صورت وجود این کوئری استرینگ، هدایت خودکار کاربر، به مسیر returnUrl پیشین صورت خواهد گرفت:
if (this.returnUrl) {
   this.router.navigate([this.returnUrl]);
} else {
   this.router.navigate(["/protectedPage"]);
}

محل معرفی این AuthGuard جدید که در حقیقت یک سرویس است، در ماژول Core، در قسمت providers آن، به صورت ذیل می‌باشد:
import { AuthGuard } from "./services/auth.guard";

@NgModule({
  providers: [
    AuthGuard
  ]
})
export class CoreModule {}
در آخر برای اعمال این Guard جدید، به فایل dashboard-routing.module.ts مراجعه کرده و خاصیت canActivate را مقدار دهی می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";
import { AuthGuardPermission } from "../core/models/auth-guard-permission";
import { AuthGuard } from "../core/services/auth.guard";

const routes: Routes = [
  {
    path: "protectedPage",
    component: ProtectedPageComponent,
    data: {
      permission: {
        permittedRoles: ["Admin"],
        deniedRoles: null
      } as AuthGuardPermission
    },
    canActivate: [AuthGuard]
  }
];
به این ترتیب با درخواست این مسیر، پیش از فعالسازی و نمایش آن، توسط AuthGuard معرفی شده‌ی به آن، کار بررسی وضعیت کاربر و نقش‌های او که از خاصیت permission خاصیت data دریافت می‌شوند، صورت گرفته و اگر عملیات تعیین اعتبار اطلاعات با موفقیت به پایان رسید، آنگاه کاربر مجوز دسترسی به این قسمت از برنامه را خواهد یافت.

اگر قصد آزمایش آن‌را داشتید، فقط کافی است بجای نقش Admin، مثلا Admin1 را در permittedRoles مقدار دهی کنید، تا صفحه‌ی access denied را در صورت درخواست مسیر protectedPage، بتوان مشاهده کرد.




کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
اشتراک‌ها
معرفی ابزارهای مفید و رایگان fishcode
نرم افزارهای ارائه شده این تیم به صورت رایگان بوده و عموما بدون نیاز به نصب و با حجم بسیار کم میباشد که ابزارهای بسیار کارآمدی در اختیار برنامه نویسان قرار داده است.   
توضیحات تکمیلی در مورد نرم افزار‌ها در سایت این تیم قرار دارد ولی جهت آشنایی دوستان به صورت مختصر در مورد چند نرم افزار مفید این تیم توضحاتی خواهم داد. 

  • Convert.Net
یکی از مفیدترین و بهترین ابزارهای این تیم نرم افزار Convert.Net است که امکانات مفیدی در اختیار شما قرار میدهد از جمله :
  1. Regular Expression Tester : که جهت تست Regex‌های نوشته شده استفاده میشود.
  2. Encoding & Decoding : جهت تبدیل انواع رشته‌های Encoded ویا Decoded به یکدیگر استفاده میشود و از html - url - EScape-js - Base64 - string و ... پشتیبانی میکند.
  3. Encryption & Decryption : جهت Encrypt و Decrypt انواع رشته استفاده میشود که از انکریپتورهای معروفی همچون  AES - Rijndael - DES - SHA - TripleDES پشتیبانی میکند.
  4. Language Translation : یک دیکشنری Multi Language آنلاین در اختیار شما برای ترجمه متون قرار میدهد.
  5. C# & VB.Net Convertor : برای تبدیل کدهای C# به Vb و برعکس استفاده میشود و  طبق تست هایی که روش به شخصه انجام دادم در اکثر موارد بدون خطا تا حدود 90 درصد تبدیلات رو به صورت موفق انجام میده ولی مانند سایر Convertor‌ها با Lambda Expression کمی مشکل دارد.
  6.  Xml & Json Browser : برای مشاهده و تبدیل Xml به Json و برعکس بسیار مفید است .. 
  7. Linq Tester : برای تست کوئری‌های Linq استفاده میشود . (برای استفاده از این امکان باید Roslyne روی سیستم شما نصب باشد)
حجم برنامه 2 مگابایت : دانلود

  • Database.Net
نرم افزار کم حجم جهت اتصال و مدیریت انواع دیتابیس میباشد در عین سادگی و حجم کم ابزار مفیدی جهت اجرای Query‌ها ساخت جداول , مشاهده و ویرایش رکوردها و .... در اختیارتان قرار میدهد.
دیتابیس‌های پشتیابنی شده در این نرم افزار : 
SQL Server 2000/2005/2008/2012/2014/Express/LocalDB         
SQL Server Compact 3.1/3.5/4.0 (*.sdf;*.*)         
SQL Azure 10/11        
MS Access 97/2000/2002/2003 (*.mdb;*.mde;*.*), 2007/2010/2013 (*.accdb;*.accde;*.*)         
MS Excel 97/2000/2002/2003(*.xls;*.*), 2007/2010/2013 (*.xlsx;*.xlsm;*.xlsb;*.*)         
Firebird SuperServer/Embedded 1.5/2.0/2.1/2.5 (*.gdb;*.fdb;*.*)       
SQLite 3.x (*.db;*.db3;*.sqlite;*.*)       
MySQL 5.x, MariaDB 5.x/10.x         
PostgreSQL 8.x/9.x     
Oracle 10g/11g/12c       
IBM DB2 9.x/10.x         
IBM Informix 11.x/12.x         
Sybase ASE 15.x         
dBASE IV (*.dbf)   
Visual FoxPro (*.dbc)    
Data Sources (OleDB)(*.udl;*.*)     
ODBC DSN (Data Source Name)(*.dsn;*.*)       
OData (Open Data Protocol) v1/v2/v3/v4
حجم برنامه 9 مگابایت : دانلود

  • Resource.Net
این نرم افزار جهت ساخت , ویرایش و مدیریت انواع فایل Resource برنامه‌های دات نت بسیار مفید میباشد که از نسخه‌های مختلف دات نت پشتیبانی میکند.

سایر نرم افزار‌های این تیم هم مانند نرم افزارهای معرفی شده بین کاربران محبوبیت زیادی کسب کرده اند که میتوانید برای کسب اطلاعات بیشتر و دانلود این نرم افزار‌ها به وب سایت این تیم مراجعه فرمائید. 
معرفی ابزارهای مفید و رایگان fishcode
مطالب
نحوه ایجاد یک اسلایدشو به صورت داینامیک

در این آموزش قصد دارم نحوه ایجاد یک Slideshow به صورت داینامیک را با ASP.NET طراحی کنم(منظور از ایجاد Slideshow به صورت داینامیک این است که عکس‌ها را به صورت داینامیک از DB بخواند).

اولین گام این است که Plugin مورد نظر را دریافت کنید که من از پلاگین Orbit استفاده کرده ام

ابتدا یک DataBase با نام DynamicSlideShow ایجاد و یک جدول با ساختار زیر با نام Picturesدرون آن ایجاد می‌کنیم

گام بعدی ایجاد یک پروژه Asp.Net با زبان C# و اضافه کردن فایلهای پلاگین به پروژه و ایجاد یک Handler برای بازیابی داده‌ها از DB است. ساختار   Solutionما باید به صورت زیر باشد

برای اینکه بتوانیم با DB ارتباط برقرار کنیم از EF استفاده می‌کنیم به همین منظور ابتدا یک Model  به نام DynamicSlideShowModel ایجاد می‌کنیم

در گام بعد بر روی GenerateFromDatabase  انتخاب کرده و بر روی دکمه Next کلیک می‌کنیم و در مراحل بعد ابتدا DB مربوط به مثال (DynamicSlideShow) و جدول آن را انتخاب می‌کنیم . حال یک Model به درون پوشه App_Code  اضافه شده است  

در ادامه برای واکشی رکوردهای موجود در جدول  Pictures  کدهای زیر را درون Handler می‌نویسیم

  var ctx = new DynamicSlideShowEntities();
  var list = ctx.Pictures.ToList();
  string str = JsonConvert.SerializeObject(list);
  context.Response.Write(str);

  

در این کدها تنها نکته‌ای که احتیاج به توضیح دارد این است که ابتدا یک لیست از رکوردها را از جدول Pictures واکشی می‌کنیم و برای پاس دادن به کلاینت ما آن‌ها را به فرمت Json تبدیل کرده ایم که برای تبدبل از کنابخانه آماده Newtonsoft.Json.dll استفاده کرده ایم 

حال باید با استفاده از jQuery کدهای درون Handler را اجرا کنیم؛ برای همین منظور یک صفحه با نام default.aspx ایجاد کرده و کدهای زیر را درون آن می‌نویسیم

<head runat="server">
    <title>Dynamic SlideShow</title>
    <link href="CSS/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Script/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.PicturePath + '" alt="' + b.PictureText + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"

            });
            });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
</body>

در اینجا ابتدا با استفاده از متد ajax کتابخانه jQuery کدهای درون Handler را اجرا کرده و به ازای هر المان موجود در جدول یک تگ img به صفحه اضافه می‌کنیم.

مطالب
breeze js به همراه ایجاد سایت آگهی قسمت سوم
در مطلب قبلی، پیش نیازهای مربوطه را نصب کردیم. در این قسمت به ساخت صفحات ورود و خروج، ثبت نام کاربران و تغییر رمز عبور خواهیم پرداخت.
در اینجا ما از Account Controller پیش فرض Asp.net Mvc استفاده میکنیم که متدهای مورد استفاده ما در آن قرار دارد و به BreezeController مزین شده است.
 [BreezeController]
    public class AccountController : ApiController
    {
      ...
    }

 اینترفیس IAuthService:
module Interfaces {
    export interface IAuthService {
        user: Models.IUserToken
        getUserInfo(accessToken);
        login(data);
        logOut();
        register(data);
        changePassword(data);
        accessToken(accessToken, data);
    }
}

سرویس AuthService 
پیاده سازی اینترفیس IAuthService را برعهده دارد. در سازنده آن، وابستگی‌های آن مقداردهی شده‌است و همچنین تنظیمات manager را انجام داده‌ایم.
متد accessToken: وظیفه ارسال توکن  را به سرور و همچنین نگهداری آن‌را در local storage، برعهده دارد.
متد getUserInfo: اطلاعات کاربر لاگین شده را  از سرور دریافت مینماید.
متد login: فرمت مورد قبول سرور به نحو زیر میباشد. در صورت موفقیت آمیز بودن، توکن را به متد accessToken  پاس میدهیم و آبجکت user را با مقادیر دریافتی پر می‌نماییم.
"grant_type=password & username=myusername & password=mypassword";
برای فراخوانی متدهای post، همانطوری که در مطلب ارسال کوئری‌های پست به آن اشاره شده‌است، عمل می‌نماییم. در ابتدا فایل breeze.ajaxpost.js را اضافه می‌کنیم سپس در فایل breeze.angular  قطعه کد زیر را در متد useNgHttp اضافه می‌کنیم.
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
breeze.ajaxpost(ajaxAdapter);
با این تنظیمات میتوانیم توسط withParameters، متدهای post را فراخوانی کنیم. 
.withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: newData
                }
متد logOut: خروج از برنامه را برای ما انجام می‌دهد و درصورت موفقیت آمیز بودن، به صفحه اصلی هدایت می‌شود.
متد register: ثبت نام کاربران را بر عهده دارد.
متد changePassword: تغییر رمز عبور کاربران را برعهده دارد.
module AdApps {
    var securityUrls = {
        site: '/',
        login: '/token',
        logout: 'logout',
        register: 'register',
        userInfo: 'getUserInfo',
        changePassword: 'changePassword',
    }
    export class AuthService implements Interfaces.IAuthService {
        private manager: breeze.EntityManager;

        constructor(
            private _breeze: typeof breeze,
            private $http: ng.IHttpProvider,
            private toaster: ngtoaster.IToasterService,
            private $location: ng.ILocationService) {

            var dataService = new _breeze.DataService({
                serviceName: "/breeze/Account",
                hasServerMetadata: false
            });
            var metadataStore = new _breeze.MetadataStore({
                namingConvention: _breeze.NamingConvention.camelCase
            });
            this.manager = new _breeze.EntityManager({
                dataService: dataService,
                metadataStore: metadataStore,
                saveOptions: new _breeze.SaveOptions({
                    allowConcurrentSaves: true, tag: [{}]
                })
            });
        }

        user: Models.IUserToken;
        accessToken(accessToken, data): string {
            if (accessToken === 'clear') {
                localStorage.removeItem('accessToken');
                delete this.$http.defaults.headers.common.Authorization;
            }
            else {
                window.localStorage.setItem("accessToken", accessToken);
                this.$http.defaults.headers.common.Authorization = 'Bearer ' + accessToken;
            }
            return accessToken;
        }
        getUserInfo(): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.userInfo);
            return this.manager.executeQuery(query).then(data => {
                return data.results[0];
            });
        }
        login(data: any): ng.IPromise<any> {
            var newData = "grant_type=password&username=" + data.userName + "&password=" + data.password;
            var query = this._breeze.EntityQuery.from(securityUrls.login)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: newData
                });
            return this.manager.executeQuery(query).then(data => {
                var self = this;
                var result = data.results[0] as any;
                self.accessToken(result.access_token, data.results[0]);
                self.user = <Models.IUserToken>{};
                self.user = <Models.IUserToken>result;
                return result;
            });
        }
        logOut(): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.logout)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                });
            return this.manager.executeQuery(query).then(data => {
                this.user = null;
                this.accessToken('clear', null);
                this.$location.path("/");
            });
        }
        register(data: Object): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.register)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: data
                });
            return this.manager.executeQuery(query).then(data => { });
        }
        changePassword(data: Object): ng.IPromise<any> {
            var query = this._breeze.EntityQuery.from(securityUrls.changePassword)
                .withParameters({
                    $method: 'POST',
                    $encoding: 'JSON',
                    $data: data
                });
            return this.manager.executeQuery(query).then(data => { });
        }
    }
}

سرویس HttpInterceptor 
: رهگیری و پیگیری کردن نتیجه درخواست‌های http را بر عهده دارد.
درrequest : توکن امنیتی را به هدر درخواست‌ها اضافه میکنیم.
در response : در صورت موفقیت درخواست http، پیغام مناسبی را نمایش میدهیم.
در responseError : در صورت عدم موفقیت درخواست http، پیغام مناسبی را نمایش میدهیم.
module AdApps {
    export class HttpInterceptor {
        private static _toaster: ngtoaster.IToasterService;
        private static _$q: ng.IQService;
        constructor(
            private $q: ng.IQService,
            private toaster: ngtoaster.IToasterService,
            private $location: ng.ILocationService) {
            HttpInterceptor._toaster = toaster;
            HttpInterceptor._$q = $q;
        }
        request(config): string {
            config.headers = config.headers || {};
            var authData = window.localStorage.getItem("accessToken");
            if (authData) {
                config.headers.Authorization = "Bearer " + authData;
            }
            return config;
        };
        response(response): ng.IPromise<any> {
            if (response.data && response.data.message && response.status === 200) {
                HttpInterceptor._toaster.success(response.data.message)
            }
            return HttpInterceptor._$q.resolve(response);
        };
        responseError(response): ng.IPromise<any> {
            var self = this;
            var data = response.data;
            var title = "خطا";
            var messages = [];
            if (data) {
                if (data.error) {
                    title = data.error;
                }
                if (data.message) {
                    messages.push(data.message);
                }
                if (data.Message) {
                    messages.push(data.Message);
                }
                if (data.ModelState) {
                    angular.forEach(data.ModelState, function (errors, key) {
                        if (key.substr(0, 1) != "$")
                        { messages.push(errors); }

                    });
                }
                if (data.exceptionMessage) {
                    messages.push(data.exceptionMessage);
                }
                if (data.ExceptionMessage) {
                    messages.push(data.ExceptionMessage);
                }
                if (data.error_description) {
                    messages.push(data.error_description);
                }
                if (messages.length > 0) {
                    HttpInterceptor._toaster.error(title, messages.join("<br/>"));
                }
                if (response.status === "401") {
                    self.$location.path("/ورود");
                }
            }
            return HttpInterceptor._$q.reject(response);
        }
    }
}

معرفی کردن مسیرهای ورود، ثبت نام و تغییر رمز عبور به انگولار
module AdApps {
    class SecurityCtrl {
        constructor(private $scope: Interfaces.IAuthScope, private authService: AuthService) {
            $scope.authService = authService;
            if (window.localStorage.getItem("accessToken") != null) {
                authService.getUserInfo().then(function (data) {
                    $scope.authService.user = data;
                });
            }
            $scope.logOut = function () {
                return authService.logOut().then(function () { });
            }
        }
    }
    define(["angularAmd", "angular", "factory/AuthService", "factory/httpInterceptor"], (angularAmd, ng) => {
        angularAmd = angularAmd.__proto__;
        var app = ng.module("AngularTypeScript", ['ngRoute', 'breeze.angular', 'toaster']);
        var viewPath = "app/views/";
        var controllerPath = "app/controller/";
        app.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
            $httpProvider.interceptors.push("HttpInterceptor");
            $routeProvider
                .when("/", angularAmd.route({
                    templateUrl: viewPath + "home.html",
                    controllerUrl: controllerPath + "home.js"
                }))
                .when("/login", angularAmd.route({
                    templateUrl: viewPath + "login.html",
                    controllerUrl: controllerPath + "login.js"
                }))
                .when("/register", angularAmd.route({
                    templateUrl: viewPath + "register.html",
                    controllerUrl: controllerPath + "register.js"
                }))
                .when("/changePassword", angularAmd.route({
                    templateUrl: viewPath + "change-password.html",
                    controllerUrl: controllerPath + "changePassword.js"
                }))
                .otherwise({ redirectTo: '/' });
        }
        ]);
        app.service('AuthService', ['breeze', '$http', 'toaster', '$location', AuthService]);
        app.service("HttpInterceptor", ["$q", "toaster", "$location", HttpInterceptor]);
        app.controller('SecurityCtrl', ['$scope', 'AuthService', SecurityCtrl]);
        return angularAmd.bootstrap(app);
    });
}

ایجاد کنترلر .login.ts و ارسال سرویس‌های لازم به کلاس LoginCtrl
در صورت صحیح بودن نام کاربری و رمز عبور به صفحه اصلی هدایت خواهد شد.
module AdApps {
    define(['app'], function (app) {
        app.controller('LoginCtrl', ["$scope", "AuthService", "$location", LoginCtrl]);
    });
   export  class LoginCtrl {
        constructor($scope: Interfaces.ILoginScope, authService: AuthService, $location: ng.ILocationService) {
           $scope.submit = function () {
               authService.login(angular.copy($scope.form))
                    .then(function (data) {
                        this.$location.path("/");
                    })
            };
        }
    }
}

ایجاد login.html
<div  ng-controller="LoginCtrl">
    <div>
        <i></i>
        <span>ورود</span>
        <div>
            <div>
            </div>
        </div>
    </div>
    <div>
        <div>
            <div>
                <form name="Form" id="form1">
                    <fieldset>
                        <div>
                            <div>
                                <input
                                       name="username"
                                       ng-model="form.userName"
                                       placeholder="نام کاربری"
                                       required>
                                <span>
                                    <i></i>
                                </span>
                            </div>
                        </div>
                        <div>
                            <div>
                                <input name="password"
                                       type="password"
                                       ng-model="form.password"
                                       placeholder="{{'Password'}}"
                                       validator="required">
                                <span>
                                    <i></i>
                                </span>
                            </div>
                        </div>

                    </fieldset>
                    <div>
                        <button type="submit" ng-click="submit()">ورود</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
فایل‌های مربوط به ثبت نام و تغییر رمز عبور، مشابه لاگین می‌باشد و از ذکر آن خودداری می‌نماییم و فایل‌های مربوطه در پروژه قرار دارند.
با تغییرات بالا، فایل main.ts دارای محتویات زیر میباشد:
requirejs.config({
    paths: {
        "app": "app",
        "angularAmd": "/Scripts/angularAmd",
        "angular": "/Scripts/angular",
        "breezeAjaxpost": "/Scripts/breeze/breeze.ajaxpost",
        "breeze": "/Scripts/breeze/breeze.debug",
        "breezeAngular": "/Scripts/breeze/breeze.angular",
        "bootstrap": "/Scripts/bootstrap",
        "angularRoute": "/Scripts/angular-route",
        "jquery": "/Scripts/jquery-2.2.2",
        "entityManagerService": "factory/entityManagerService",
        "toaster": "/Scripts/toaster",
    },
    waitSeconds: 0,
    shim: {
        "angular": { exports: "angular" },
        "angularRoute": { deps: ["angular"] },
        "bootstrap": { deps: ["jquery"] },
        "breeze": { deps: ["jquery"] },
        "breezeAngular": {
            deps: ["angular", "breeze"]
        },
        "toaster": { deps: ["angular"] },
        "app": {
            deps: ["bootstrap", "angularRoute", "toaster", "breezeAngular", "breezeAjaxpost"]
        }
    }
});
require(["app"]);
فایل پروژه :AngularTypeScript.zip
در قسمت‌های بعدی به ثبت و نمایش آگهی در سایت خواهیم پرداخت.  
اشتراک‌ها
Visual Studio 2019 version 16.0.2 منتشر شد
Visual Studio 2019 version 16.0.2 منتشر شد
نظرات مطالب
بررسی سایز و پسوند فایل آپلود شده قبل از ارسال به سرور
سلام 
ببخشید قسمت دوم که نمایش سایز فایل است را من هر کاری کردم نتونستم انجام بدم.
یه HTML ساده ساختم و کدهای شما رو روش قرار دادم ولی هیچ اتفاقی نیافتاد.
البته اینم بگم که رو سایت jsfiddle  جواب میده و مشکلی نیست ، می‌خواهم بدونم که به جز Library jQuery آیا چیز دیگری باید اضافه کنم ؟
ممنون میشم اگه فایل تستی هم قرار بدید
نظرات مطالب
پَرباد - راهنمای اتصال و پیاده‌سازی درگاه‌های پرداخت اینترنتی (شبکه شتاب)
لطفا در صورتیکه شما با باشگاه مشتریان کار کرده اید و براتون مقدور هست، موارد زیر را در صفحه این پروژه پاسخ بدید.
۱) آیا فایل راهنما برای متدهای باشگاه مشتریان موجود هست؟ (بنده در مستندات، هیچ توضیحی در مورد این متد پیدا نکردم)
۲) آیا در قسمت additionaldata ، فرمت داده باید دقیقا حالتی که شما اینجا مثال زدید باشد یا اینکه حالات دیگری هم امکان پذیر هست؟
۳) متد دیگری هم به نام bpDynamicPayRequest موجود هست. فرق این دو متد چیست؟
تشکر.