"devDependencies": { "angular-cli": "1.0.0-beta.28.3",
EF Code First #6
- assembly binding هم باید کار کنه چون فضای نام System.ComponentModel.DataAnnotations.Schema داخل خود اسمبلی جدید EF هست؛ هرچند به ظاهر جزئی از یک اسمبلی دیگر به نظر میرسد، که در عمل اینطور نیست. به این ترتیب امکان استفاده از EF5 در برنامههای دات نت 4 هم هست.
فریم ورک 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
پیکربندی 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(); } }
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(); } }
مورد بعدی ای که جلب توجه میکند فراخوانیهای دیگری برای پیکربندی احراز هویت دو مرحلهای است. همچنین پیکربندیهای جدیدی برای کوکیها تعریف شده است که در نسخه قبلی وجود نداشتند.
تا اینجا پیکربندیهای اساسی برای اپلیکیشن شما انجام شده است و میتوانید از اپلیکیشن خود استفاده کنید. بکارگیری فراهم کنندگان خارجی در حال حاضر غیرفعال است و بررسی آنها نیز از حوصله این مقاله خارج است. این کلاس پیکربندیهای اساسی 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; } }
اگر از نسخه 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; } }
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; } }
اما از همه چیز مهمتر امضا (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>
public virtual TKey Id { get; set; }
public virtual ICollection<TRole> Roles { get; }
در نسخه 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; } }
public class IdentityUserRole<TKey> { public IdentityUserRole(); public virtual TKey RoleId { get; set; } public virtual TKey UserId { get; set; } }
بررسی پیاده سازی جدید 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); } }
مورد حائز اهمیت بعدی در متد ()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; } }
سرویسهای ایمیل و پیامک برای احراز هویت دو مرحله ای و تایید حسابهای کاربری
دو کلاس دیگری که در فایل 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); } }
// 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();
کلاس کمکی 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); }
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); }
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(); } }
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); } } }
نکته حائز اهمیت دیگر متد ()InitializeIdentityForEF است. این متد کاری مشابه متد ()Seed انجام میدهد که هنگام استفاده از مهاجرتها (Migrations) از آن استفاده میکنیم. در این متد میتوانید رکوردهای اولیه ای را در دیتابیس ثبت کنید. همانطور که مشاهده میکنید در قطعه کد بالا نقشی مدیریتی بنام Admin ایجاد شده و کاربر جدیدی با اطلاعاتی پیش فرض ساخته میشود که در آخر به این نقش منتسب میگردد. با انجام این مراحل، پس از اجرای اولیه اپلیکیشن کاربری با سطح دسترسی مدیر در اختیار خواهیم داشت که برای تست اپلیکیشن بسیار مفید خواهد بود.
در این مقاله نگاهی اجمالی به Identity 2.0 در پروژههای ASP.NET MVC داشتیم. کامپوننتهای مختلف فریم ورک و نحوه پیکربندی آنها را بررسی کردیم و با تغییرات و قابلیتهای جدید به اختصار آشنا شدیم. در مقالات بعدی بررسی هایی عمیقتر خواهیم داشت و با نحوه استفاده و پیاده سازی قسمتهای مختلف این فریم ورک آشنا خواهیم شد.
مطالعه بیشتر
کوکیهایی که باید HTTPS only شوند
کوکیهای پیشفرض برنامههای ASP.NET به صورت HTTP Only به سمت کلاینت ارسال میشوند. این کوکیها توسط اسکریپتها قابل خوانده شدن نیستند و به همین جهت یکی از راههای مقاومت بیشتر در برابر حملات XSS به شمار میروند. پس از ارتقاء به HTTPS، این کوکیها را هم میتوان HTTPs Only کرد تا فقط به کلاینتهایی که از طریق آدرس HTTPS سایت به آن وارد شدهاند، ارائه شود:
1) کوکی آنتیفورجری توکن
AntiForgeryConfig.RequireSsl = true;
<configuration> <system.web> <authentication mode="Forms"> <forms requireSSL="true" cookieless="UseCookies"/> </authentication> </system.web> </configuration>
3) تمام httpCookies
<configuration> <system.web> <httpCookies httpOnlyCookies="true" requireSSL="true" /> </system.web> </configuration>
4) کوکیهای Role manager
<configuration> <system.web> <roleManager cookieRequireSSL="true" /> </system.web> </configuration>
5) کوکیهای OWIN Authentication و ASP.NET Identity 2.x
var options = new CookieAuthenticationOptions() { CookieHttpOnly = true, CookieSecure = CookieSecureOption.Always, ExpireTimeSpan = TimeSpan.FromMinutes(10) };
فعالسازی اجبار به استفادهی از HTTPS
با استفاده از فیلتر RequireHttps، دسترسی به تمام اکشن متدهای برنامه تنها به صورت HTTPS میسر خواهد شد:
filters.Add(new RequireHttpsAttribute(permanent: true));
using System.Web.Mvc; namespace MyWebsite { internal static class FilterConfig { internal static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new RequireHttpsAttribute(permanent: true)); } } }
همچنین در متد Application_BeginRequest نیز میتوان بررسی کرد که آیا درخواست ارسالی یک درخواست HTTPS است یا خیر؟ اگر خیر، میتوان کاربر را به صورت خودکار به نگارش HTTPS آن هدایت دائم کرد:
protected void Application_BeginRequest(Object sender, EventArgs e) { if (!HttpContext.Current.Request.IsSecureConnection) { var builder = new UriBuilder { Scheme = "https", Host = Request.Url.Host, // use the RawUrl since it works with URL Rewriting Path = Request.RawUrl }; Response.Status = "301 Moved Permanently"; Response.AddHeader("Location", builder.ToString()); } }
فعالسازی HSTS جهت اطلاع به مرورگر که این سایت تنها از طریق HTTPS قابل دسترسی است و تمام درخواستهای HTTP را به صورت خودکار از طریق HTTPS انجام بده:
<httpProtocol> <customHeaders> <add name="Strict-Transport-Security" value="max-age=16070400; includeSubDomains" />
<rewrite> <rules> <rule name="Redirect to HTTPS" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="off" ignoreCase="true" /> <add input="{HTTP_HOST}" negate="true" pattern="localhost" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule>
اصلاح تمام آدرسهای مطلقی که توسط برنامه تولید میشوند
اگر در برنامهی خود از Url.Action برای تولید آدرسها استفاده میکنید، با ذکر پارامتر protocol آن، آدرس تولیدی آن بجای یک مسیر نسبی، یک مسیر مطلق خواهد بود. اگر پیشتر این پروتکل را به صورت دستی به http تنظیم کردهاید، روش صحیح آن به صورت زیر است که با آدرس جدید HTTPS سایت هم سازگار است:
var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: this.Request.Url.Scheme);
اصلاح تمام آدرسهایی که پیشتر توسط برنامه تولید شدهاند
یک نمونه آن در مطلب «به روز رسانی تمام فیلدهای رشتهای تمام جداول بانک اطلاعاتی توسط Entity framework 6.x» بحث شدهاست.
اصلاح فایل robots.txt و درج آدرس HTTPS جدید
اگر در فایل robots.txt سایت، آدرس مطلق Sitemap را به صورت HTTP درج کرده بودید، آنرا به HTTPS تغییر دهید:
User-agent: * Sitemap: https://www.dntips.ir/Sitemap
Fiddler ابزاری است که به صورت یک پروکسی عمل میکند و تمام اطلاعات ارسالی و دریافتی از طریق مرورگر وب شما را میتواند مونیتور نموده و اطلاعات لازم را به شما ارائه دهد.
برای مثال IE8 ارائه شده و مطابق بررسیها سرعت بارگذاری صفحات در آن اگر کمتر از فایرفاکس نباشد، حداقل برابر یا کمی بیشتر است یا حداقل سرعت آن دوبرابر IE7 گزارش شده. حال شاید این سؤال پیش بیاید که این اندازه گیریها که گاهی در حد میلی ثانیه است چگونه صورت میگیرد؟ از چه ابزاری برای اینکار استفاده میکنند؟
یکی از ابزارهای دقیق انجام اینکار استفاده از Fiddler است (شکل زیر).
این برنامه مستقل از مرورگر عمل میکند و برای مثال تنظیمات پروکسی فایرفاکس برای استفاده از آن در این صفحه توضیح داده شده است.
اخیرا افزونهای سورس باز برای این برنامه تحت عنوان Casaba Security Watcher ارائه شده است که از آدرس زیر قابل دریافت است:
برای نصب آن، دو فایل dll آنرا که اسمبلیهای دات نتی هستند، به درون فولدر scripts برنامه فیدلر کپی کرده و سپس برنامه را اجرا کنید. یک tab جدید به فیدلر تحت عنوان security auditor اضافه میشود که کار آن بررسی محتوای رد و بدل شده و گوشزد کردن موارد امنیتی مرتبط است:
آیا جداول بهینه سازی شدهی برای حافظه، همان DBCC PINTABLE منسوخ شده هستند؟
در نگارشهای قدیمیتر اس کیوال سرور، دستوری وجود داشت به نام DBCC PINTABLE که سبب ثابت نگه داشتن صفحات جداول مبتنی بر دیسک یک دیتابیس، در حافظه میشد. به این ترتیب تمام خواندنهای مرتبط با آن جدول، از حافظه صورت میگرفت. مشکل این روش که سبب منسوخ شدن آن گردید، اثرات جانبی آن بود؛ مانند خوانده شدن صفحات جدیدتر (با توجه به اینکه ساختار پردازشی و موتور بانک اطلاعاتی تغییری نکرده بود) و نیاز به حافظهی بیشتر تا حدی که کل کش بافر سیستم را پر میکرد و امکان انجام سایر امور آن مختل میشدند. همچنین اولین ارجاعی به یک جدول، سبب قرار گرفتن کل آن در حافظه میگشت. به علاوه ساختار این سیستم نیز همانند روش مبتنی بر دیسک، بر اساس همان روشهای قفل گذاری، ذخیره سازی اطلاعات و تهیه ایندکسهای متداول بود.
اما جداول بهینه سازی شدهی برای حافظه، از یک موتور کاملا جدید استفاده میکنند؛ با ساختار جدیدی برای ذخیره سازی اطلاعات و تهیه ایندکسها. دسترسی به اطلاعات آنها شامل قفل گذاریهای متداول نیست و در آن حداقل زمان دسترسی به اطلاعات درنظر گرفته شدهاست. همچنین در آنها data pages یا index pages و کش بافر نیز وجود ندارد.
نحوهی ذخیره سازی و مدیریت اطلاعات جداول بهینه سازی شده برای حافظه
جداول بهینه سازی شده برای حافظه، فرمت ردیفهای کاملا جدیدی را نیز به همراه دارند و جهت قرارگرفتن در حافظه ودسترسی سریع به آنها بهینه سازی شدهاند. برخلاف جداول مبتنی بر دیسک سخت که اطلاعات آنها در یک سری صفحات خاص به نامهای data or index pages ذخیره میشوند، اینگونه جداول، دارای ظروف مبتنی بر صفحه نیستند و از مفهوم چند نگارشی برای ذخیره سازی اطلاعات استفاده میکنند؛ به این معنا که ردیفها به ازای هر تغییری، دارای یک نگارش جدید خواهند بود و بلافاصله در همان نگارش اصلی به روز رسانی نمیشوند.
در اینجا هر ردیف دارای یک timestamp شروع و یک timestamp پایان است. timestamp شروع بیانگر تراکنشی است که ردیف را ثبت کرده و timestamp پایان برای مشخص سازی تراکنشی بکار میرود که ردیف را حذف کرده است. اگر timestamp پایان، دارای مقدار بینهایت باشد، به این معنا است که ردیف متناظر با آن هنوز حذف نشدهاست. به روز رسانی یک ردیف در اینجا، ترکیبی است از حذف یک ردیف موجود و ثبت ردیفی جدید. برای یک عملیات فقط خواندنی، تنها نگارشهایی که timestamp معتبری داشته باشند، قابل مشاهده خواهند بود و از مابقی صرفنظر میگردد.
در OLTP درون حافظهای که از روش چندنگارشی همزمانی استفاده میکند، برای یک ردیف مشخص، ممکن است چندین نگارش وجود داشته باشند؛ بسته به تعداد باری که یک رکورد به روز رسانی شدهاست. در اینجا یک سیستم garbage collection همیشه فعال، نگارشهایی را که توسط هیچ تراکنشی مورد استفاده قرار نمیگیرند، به صورت خودکار حذف میکند؛ تا مشکل کمبود حافظه رخ ندهد.
آیا میتوان به کارآیی جداول بهینه سازی شده برای حافظه با همان روش متداول مبتنی بر دیسک اما با بکارگیری حافظهی بیشتر و استفاده از یک SSD RAID رسید؟
خیر! حتی اگر کل بانک اطلاعاتی مبتنی بر دیسک را در حافظه قرار دهید به کارآیی روش جداول بهینه سازی شدهی برای حافظه نخواهید رسید. زیرا در آن هنوز مفاهیمی مانند data pages و index pages به همراه یک buffer pool پیچیده وجود دارند. در روشهای مبتنی بر دیسک، ردیفها از طریق page id و row offset آنها قابل دسترسی میشوند. اما در جداول بهینه سازی شدهی برای حافظه، ردیفهای جداول با یک B-tree خاص به نام Bw-Tree در دسترس هستند.
میزان حافظهی مورد نیاز برای جداول بهینه سازی شدهی برای حافظه
باید درنظر داشت که تمام جداول بهینه سازی شدهی برای حافظه، به صورت کامل در حافظه ذخیره خواهند شد. بنابراین بدیهی است که نیاز به مقدار کافی حافظه در اینجا ضروری است. توصیه صورت گرفته، داشتن حافظهای به میزان دو برابر اندازهی اطلاعات است. البته در اینجا چون با یک سیستم هیبرید سر و کار داریم، حافظهی کافی جهت کار buffer pool مختص به جداول مبتنی بر دیسک را نیز باید درنظر داشت.
همچنین اگر به اندازهی کافی حافظه در سیستم تعبیه نشود، شاهد شکست مداوم تراکنشها خواهید بود. به علاوه امکان بازیابی و restore جداول را نیز از دست خواهید داد.
البته لازم به ذکر است که اگر کل بانک اطلاعاتی شما چند ترابایت است، نیازی نیست به همین اندازه یا بیشتر حافظه تهیه کنید. فقط باید به اندازهی جداولی که قرار است جهت قرار گرفتن در حافظه بهینه سازی شوند، حافظه تهیه کنید که حداکثر آن 256 گیگابایت است.
چه برنامههایی بهتر است از امکانات OLTP درون حافظهای SQL Server 2014 استفاده کنند؟
- برنامههایی که در آنها تعداد زیادی تراکنش کوتاه مدت وجود دارد به همراه درجهی بالایی از تراکنشهای همزمان توسط تعداد زیادی کاربر.
- اطلاعاتی که توسط برنامه زیاد مورد استفاده قرار میگیرند را نیز میتوان در جداول بهینه سازی شده جهت حافظه قرار داد.
- زمانیکه نیاز به اعمال دارای write بسیار سریع و با تعداد زیاد است. چون در جداول بهینه سازی شدهی برای حافظه، صفحات دادهها و ایندکسها وجود ندارند، نسبت به حالت مبتنی بر دیسک، بسیار سریعتر هستند. در روشهای متداول، برای نوشتن اطلاعات در یک صفحه، مباحث همزمانی و قفلگذاری آنرا باید در نظر داشت. در صورتیکه در روش بهینه سازی شدهی برای حافظه، به صورت پیش فرض از حالتی همانند snapshot isolation و همزمانی مبتنی بر نگارشهای مختلف رکورد استفاده میشود.
- تنظیم و بهینه سازی جداولی با تعداد Read بالا. برای مثال، جداول پایه سیستم که اطلاعات تعاریف محصولات در آن قرار دارند. این نوع جداول عموما با تعداد Readهای بالا و تعداد Write کم شناخته میشوند. چون طراحی جداول مبتنی بر حافظه از hash tables و اشارهگرهایی برای دسترسی به رکوردهای موجود استفاده میکند، اعمال Read آن نیز بسیار سریعتر از حالت معمول هستند.
- مناسب جهت کارهای data warehouse و ETL Staging Table. در جداول مبتنی بر حافظه امکان عدم ذخیره سازی اطلاعات بر روی دیسک سخت نیز پیش بینی شدهاست. در این حالت فقط اطلاعات ساختار جدول، ذخیرهی نهایی میگردد و اگر سرور نیز ری استارت گردد، مجددا میتواند اطلاعات خود را از منابع اصلی data warehouse تامین کند.
محدودیتهای جداول بهینه سازی شدهی برای حافظه در SQL Server 2014
- تغیر اسکیما و ساختار جداول بهینه سازی شدهی برای حافظه مجاز نیست. به بیان دیگر دستور ALTER TABLE برای اینگونه جداول کاربردی ندارد. این مورد جهت ایندکسها نیز صادق است. همان زمانیکه جدول ایجاد میشود، باید ایندکس آن نیز تعریف گردد و پس از آن این امکان وجود ندارد.
تنها راه تغییر اسکیمای اینگونه جداول، Drop و سپس ایجاد مجدد آنها است.
البته باید درنظر داشت که SQL Server 2014، اولین نگارش این فناوری را ارائه دادهاست و در نگارشهای بعدی آن، بسیاری از این محدودیتها قرار است که برطرف شوند.
- جداول بهینه سازی شدهی برای حافظه حتما باید دارای یک ایندکس باشند. البته اگر یک primary key را برای آنها تعریف نمائید، کفایت میکند.
- از unique indexها پشتیبانی نمیکند، مگر اینکه از نوع primary key باشد.
- حداکثر 8 ایندکس را میتوان بر روی اینگونه جداول تعریف کرد.
- امکان تعریف ستون identity در آن وجود ندارد. اما میتوان از قابلیت sequence برای رسیدن به آن استفاده کرد.
- DML triggers را پشتیبانی نمیکند.
- کلیدهای خارجی و قیود را پشتیبانی نمیکند.
- حداکثر اندازهی یک ردیف آن 8060 بایت است. بنابراین از نوعهای دادهای max دار و XML پشتیبانی نمیکند.
این مورد در حین ایجاد جدول بررسی شده و اگر اندازهی ردیف محاسبهی شدهی آن توسط SQL Server 2014 بیش از 8060 بایت باشد، جدول را ایجاد نخواهد کرد.
اگر سرور را ری استارت کنیم، چه اتفاقی برای اطلاعات جداول بهینه سازی شدهی برای حافظه رخ میدهد؟
حالت DURABILTY انتخاب شدهی در حین ایجاد جدول بهینه سازی شدهی برای حافظه، تعیین کنندهای این مساله است. اگر SCHEMA_ONLY انتخاب شده باشد، کل اطلاعات شما با ری استارت سرور از دست خواهد رفت؛ البته اطلاعات ساختار جدول حفظ خواهد گردید. اگر حالت SCHEMA_AND_DATA انتخاب شود، اطلاعات شما پس از ریاستارت سرور نیز در دسترس خواهد بود. این اطلاعات به صورت خودکار از لاگ تراکنشها بازیابی شده و مجددا در حافظه قرار میگیرند.
حالت SCHEMA_ONLY برای مصارف برنامههای data warehouse بیشتر کاربرد دارد. جایی که اطلاعات قرار است از منابع دادهی مختلفی تامین شوند.
برای مطالعه بیشتر
SQL Server 2014: NoSQL Speeds with Relational Capabilities
SQL Server 2014 In-Memory OLTP Architecture and Data Storage
Overview of Applications, Indexes and Limitations for SQL Server 2014 In-Memory OLTP Tables
Microsoft SQL Server 2014: In-Memory OLTP Overview
SQL Server in Memory OLTP for Database Developers
Exploring In-memory OLTP Engine (Hekaton) in SQL Server 2014 CTP1
اول اینکه، کیفیت دادههای ذخیرهشده در دستگاههای تلفن همراه کاربر بیشتر شخصی میشود تا مواردی دیگر! به غیر از ایمیل، پیامهای فوری، SMS / MMS ، لاگ تماسها، عکسها و پست صوتی وجود دارند که عموما توسعه دهندگان را دچار مشکل میکند.
برخی از گزینههای فوق بر روی یک کامپیوتر رومیزی هم وجود دارند، ولی اهمیت این دادهها بر روی اندروید و اجزای آن اهمیت فوق العادهای دارد. اطلاعات روی دستگاه موبایل شما به احتمال زیاد از ارزش بیشتری برخوردار خواهد بود، چرا که آنها را در یک صفحه 4 - 5 اینچی به همراه خود حمل میکنید و با خود هر کجا میبرید! این حالت، یک پلتفرم همگرا را بوجود میآورد؛ به این دلیل که سیستم رومیزی شما و تلفن همراه یک مجموعه غنی و کامل از اطلاعات حساس هستند که هردوی آنها شامل اطلاعات شخصی میباشند و برای شما اهمیت زیادی خواهند داشت. تصور کنید زمانیکه برای جلوگیری از نفوذ یا به سرقت رفتن شماره تلفنهای خود، یک پشتیبان بر روی سیستم رو میزی خود تهیه میکنید و فایل پشتیبان شمارههای تماس را بر روی سیستم شخصی نگه داری میکنید! آیا این همان پلتفرم همگرا نیست؟ آیا این دو سیستم مکمل هم نیستند؟حتی اگر همگامسازی را با یک مکان دوردست (Google Drive) انجام دهید، با این حال شما فقط در مقابل از دست دادن دادهها محافظت کردهاید و نه از دست دادن حریم خصوصی!
همچنین در نظر بگیرید که فرمت دادههای ذخیرهشده در دستگاههای تلفن همراه، تعیین و مشخص شوند! این کار اطلاعات حساس شما را به مرز سرقت نزدیکتر میکند. هر تلفن همراه SMS / MMS ، تماسها، و پست صوتی خواهد داشت. مکانهای ذخیره شده از روی GPS و مواردی دیگر که قطعا اطلاع دارید، تمامی اینها جزء مواردی هستند که خطرات امنیتی را در سیستم عامل اندروید شامل میشود. حالا در نظر بگیرید که این اطلاعات تا چه حد مهم است؟ برای کاربرانی که هیچ گونه پشتیبانی از اطلاعاتی از خود ندارند، از دست دادن دادهها قابل تصور نیست!
خطرناکترین نوع حملات بر روی پلتفرم اندروید انجام میشوند، در سکوت کامل و چندین هزار مایل دروتر از شما و فرد مهاجم نیازی به دسترسی فیزیکی و لمس تلفن همراه شما نخواهد داشت! این نوع حملات در هر زمانی ممکن است رخ دهد و اغلب میتواند به دلیل امنیت ضعیف در جای دیگری بر روی دستگاه رخ دهد.
در مطلب بعدی پیرامون امنیت معماری اندروید صبحت خواهیم کرد...
EF Core چیست؟
EF Core یک ORM یا object-relational mapper چندسکویی است که امکان کار با بانکهای اطلاعاتی مختلف را از طریق اشیاء دات نتی میسر میکند. توسط آن قسمت عمدهی کدهای مستقیم کار با بانکهای اطلاعاتی حذف شده و تبدیل به کدهای دات نتی میشوند. مزیت این لایهی Abstraction اضافی (لایهای بر روی کدهای مستقیم لایه ADO.NET زیرین)، امکان تعویض بانک اطلاعاتی مورد استفاده، تنها با تغییر کدهای آغازین برنامهاست؛ بدون نیاز به تغییری در سایر قسمتهای برنامه. همچنین کار با اشیاء دات نتی و LINQ، مزایایی مانند تحت نظر قرار گرفتن کدها توسط کامپایلر و برخورداری از ابزارهای Refactoring پیشرفته را میسر میکنند. به علاوه SQL خودکار تولیدی توسط آن نیز همواره پارامتری بوده و مشکلات حملات تزریق SQL در این حالت تقریبا به صفر میرسند (اگر مستقیما SQL نویسی نکنید و صرفا از LINQ استفاده کنید). مزیت دیگر همواره پارامتری بودن کوئریها، رفتار بسیاری از بانکهای اطلاعاتی با آنها همانند رویههای ذخیره شده است که به عدم تولید Query planهای مجزایی به ازای هر کوئری رسیده منجر میشود که در نهایت سبب بالا رفتن سرعت اجرای کوئریها و مصرف حافظهی کمتری در سمت سرور بانک اطلاعاتی میگردد.
تفاوت EF Core با نگارشهای دیگر Entity framework در چیست؟
سورس باز بودن
EF از نگارشهای آخر آن بود که سورس باز شد؛ اما EF Core از زمان نگارشهای پیش نمایش آن به صورت سورس باز در GitHub قابل دسترسی است.
چند سکویی بودن
EF Core برخلاف EF 6.x (آخرین نگارش مبتنی بر Full Framework آن)، نه تنها چندسکویی است و قابلیت اجرای بر روی Mac و لینوکس را نیز دارا است، به علاوه امکان استفادهی از آن در انواع و اقسام برنامههای دات نتی مانند UWP یا Universal Windows Platform و Windows phone که پیشتر با EF 6.x میسر نبود، وجود دارد. لیست این نوع سکوها و برنامههای مختلف به شرح زیر است:
• All .NET application (Console, ASP.NET 4, WinForms, WPF) • Mac and Linux applications (Mono) • UWP (Universal Windows Platform) • ASP.NET Core applications • Can use EF Core in Windows phone and Windows store app
افزایش تعداد بانکهای اطلاعاتی پشتیبانی شده
در EF Full یا EF 6.x، هدف اصلی، تنها کار با بانکهای اطلاعاتی رابطهای بود و همچنین مایکروسافت صرفا نگارشهای مختلف SQL Server را به صورت رسمی پشتیبانی میکرد و برای سایر بانکهای اطلاعاتی دیگر باید از پروایدرهای ثالث استفاده کرد.
در EF Core علاوه بر افزایش تعداد پروایدرهای رسمی بانکهای اطلاعاتی رابطهای، پشتیبانی از بانکهای اطلاعاتی NoSQL هم اضافه شدهاست؛ به همراه پروایدر ویژهای به نام In Memory جهت انجام سادهتر Unit testing. کاری که با نگارشهای پیشین EF به سادگی و از روز اول پشتیبانی نمیشد.
حذف و یا عدم پیاده سازی تعدادی از قابلیتهای EF 6.x
اگر موارد فوق جزو مهمترین مزایای کار با EF Core باشند، باید درنظر داشت که به علت حذف و یا تقلیل یافتن یک سری از ویژگیها در NET Core.، مانند Reflection (جهت پشتیبانی از دات نت در سکوهای مختلف کاری و خصوصا پشتیبانی از حالتی که کامپایلر مخصوص برنامههای UWP نیاز دارد تمام نوعها را همانند زبانهای C و ++C، در زمان کامپایل بداند)، یک سری از قابلیتهای EF 6.x مانند Lazy loading هنوز در EF Core پشتیبانی نمیشوند. لیست کامل و به روز شدهی این موارد را در اینجا میتوانید مطالعه کنید.
بنابراین امکان انتقال برنامههای EF 6.x به EF Core 1.0 عموما وجود نداشته و نیاز به بازنویسی کامل دارند. هرچند بسیاری از مفاهیم آن با EF Code First یکی است.
برپایی تنظیمات اولیهی EF Core 1.0 در یک برنامهی ASP.NET Core 1.0
برای نصب تنظیمات اولیهی EF Core 1.0 در یک برنامهی ASP.NET Core 1.0، جهت کار با مشتقات SQL Server (و SQL LocalDB) نیاز است سه بستهی ذیل را نصب کرد (از طریق منوی Tools -> NuGet Package Manager -> Package Manager Console):
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Pre PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
پس از اجرای سه دستور فوق، تغییرات مداخل فایل project.json برنامه به صورت ذیل خواهند بود:
{ "dependencies": { // same as before "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0" } }
{ "dependencies": { // same as before "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", "Microsoft.EntityFrameworkCore.Tools": { "version": "1.0.0-preview2-final", "type": "build" }, "Microsoft.EntityFrameworkCore.SqlServer.Design": { "version": "1.0.0", "type": "build" } }, "tools": { // same as before "Microsoft.EntityFrameworkCore.Tools": { "version": "1.0.0-preview2-final", "imports": [ "portable-net45+win8" ] } } }
بنابراین از همین ابتدای کار، بدون مراجعهی به Package Manager Console، چهار تغییر فوق را به فایل project.json اعمال کرده و آنرا ذخیره کنید؛ تا کار به روز رسانی و نصب بستهها، به صورت خودکار و همچنین صحیحی انجام شود.
فعال سازی صفحات مخصوص توسعه دهندههای EF Core 1.0
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهندهها» با تعدادی از اینگونه صفحات آشنا شدیم. برای EF Core نیز بستهی مخصوصی به نام Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore وجود دارد که امکان فعال سازی صفحهی نمایش خطاهای بانک اطلاعاتی را میسر میکند. بنابراین ابتدا به فایل project.json مراجعه کرده و این بسته را اضافه کنید:
{ "dependencies": { // same as before "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0" } }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDatabaseErrorPage(); }
تعریف اولین Context برنامه و مشخص سازی رشتهی اتصالی آن
در این تصویر، زیر ساخت نگاشتهای EF Core را مشاهده میکنید. در سمت چپ، ظرفی را داریم به نام DB Context که در برگیرندهی Db Setها است. در سمت راست که بیانگر ساختار کلی یک بانک اطلاعاتی است، معادل اینها را مشاهده میکنیم. هر Db Set به یک جدول بانک اطلاعاتی نگاشت خواهد شد و متشکل است از کلاسی به همراه یک سری خواص که اینها نیز به فیلدها و ستونهای آن جدول در سمت بانک اطلاعاتی نگاشت میشوند.
بنابراین برای شروع کار، پوشهای را به نام Entities به پروژه اضافه کرده و سپس کلاس ذیل را به آن اضافه میکنیم:
namespace Core1RtmEmptyTest.Entities { public class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
using Microsoft.EntityFrameworkCore; namespace Core1RtmEmptyTest.Entities { public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Person> Persons { get; set; } } }
سازندهی این کلاس نیز به نحو خاصی تعریف شدهاست. اگر به سورسهای EF Core مراجعه کنیم، کلاس پایهی DbContext دارای دو سازندهی با و بدون پارامتر است:
protected DbContext() : this((DbContextOptions) new DbContextOptions<DbContext>()) { } public DbContext([NotNull] DbContextOptions options) { // … }
using Microsoft.EntityFrameworkCore; namespace Core1RtmEmptyTest.Entities { public class ApplicationDbContext : DbContext { public DbSet<Person> Persons { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"... connection string ..."); } } }
اما چون در یک برنامهی ASP.NET Core، کار ثبت سرویس مربوط به EF Core، در کلاس آغازین برنامه انجام میشود و در آنجا به سادگی میتوان به خاصیت Configuration برنامه دسترسی یافت و توسط آن رشتهی اتصالی را دریافت کرد، مرسوم است از سازندهی با پارامتر DbContext به نحوی که در ابتدا عنوان شد، استفاده شود.
بنابراین در ادامه، پس از مطالعهی مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایلهای config» به فایل appsettings.json مراجعه کرده و تنظیمات رشتهی اتصالی برنامه را به صورت ذیل در آن مشخص میکنیم:
{ "ConnectionStrings": { "ApplicationDbContextConnection": "Data Source=(local);Initial Catalog=TestDbCore2016;Integrated Security = true" } }
در اینجا به وهلهی پیش فرض SQL Server اشاره شدهاست؛ از حالت اعتبارسنجی ویندوزی SQL Server استفاده میشود و بانک اطلاعاتی جدیدی به نام TestDbCore2016 در آن مشخص گردیدهاست.
پس از تعریف رشتهی اتصالی، متد OnConfiguring را از کلاس ApplicationDbContext حذف کرده و از همان نگارش دارای سازندهی با پارامتر آن استفاده میکنیم. برای اینکار به کلاس آغازین برنامه مراجعه کرده و توسط متد AddDbContext این Context را به سرویسهای ASP.NET Core معرفی میکنیم:
public class Startup { public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false) .AddJsonFile($"appsettings.{env}.json", optional: true); Configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; }); services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(Configuration["ConnectionStrings:ApplicationDbContextConnection"]); });
بنابراین قسمت options.UseSqlServer را یا در اینجا مقدار دهی میکنید و یا از طریق بازنویسی متد OnConfiguring کلاس Context برنامه.
یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه وجود دارد
با توجه به اینکه Context برنامه را به صورت یک سرویس به ASP.NET Core معرفی کردیم، امکان تزریق وابستگیها نیز در آن وجود دارد. یعنی بجای روش فوق، میتوان IConfigurationRoot را به سازندهی کلاس Context برنامه نیز تزریق کرد:
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace Core1RtmEmptyTest.Entities { public class ApplicationDbContext : DbContext { private readonly IConfigurationRoot _configuration; public ApplicationDbContext(IConfigurationRoot configuration) { _configuration = configuration; } //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) //{ //} public DbSet<Person> Persons { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(_configuration["ConnectionStrings:ApplicationDbContextConnection"]); } } }
در این حالت متد ConfigureServices کلاس آغازین برنامه، چنین شکلی را پیدا میکند و ساده میشود:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>();
یک نکته: امکان تزریق ApplicationDbContext به تمام کلاسهای برنامه وجود دارد
همینقدر که ApplicationDbContext را به عنوان سرویسی در ConfigureServices تعریف کردیم، امکان تزریق آن در اجزای مختلف یک برنامهی ASP.NET Core نیز وجود دارد:
using System.Linq; using Core1RtmEmptyTest.Entities; using Microsoft.AspNetCore.Mvc; namespace Core1RtmEmptyTest.Controllers { public class TestDBController : Controller { private readonly ApplicationDbContext _ctx; public TestDBController(ApplicationDbContext ctx) { _ctx = ctx; } public IActionResult Index() { var name = _ctx.Persons.First().FirstName; return Json(new { firstName = name }); } } }
در این حالت پس از اجرای برنامه، خطای ذیل را مشاهده خواهیم کرد:
علت اینجا است که هنوز این بانک اطلاعاتی ایجاد نشدهاست و همچنین ساختار جداول را به آن منتقل نکردهایم که این موارد را در قسمتهای بعدی مرور خواهیم کرد.