دریافت کتاب Switching to Angular 2
- چه کسانی این فریم ورک را توسعه میدهند؟ گوگل. این مورد خیلی مهم است. در این بین شاید Aurelia مدرنتر به نظر برسد اما تیم آن سابقهی خوبی در نگهداری محصولات قبلی آن ندارد. برای مثال Durandal آنها به طور کامل رها شده و الان Aurelia را شروع کردهاند.
- تیم Angular اینقدر شجاعت داشته که اقرار کند نگارش 1 آن مشکلات زیادی دارد و دست به یک بازنویسی کامل زدهاند. باید دقت کرد که چقدر اینکار برای یک تیم محبوب در وب مشکل است و قطعا نگارش 2 آن که با این حجم بالای اعتراضات عدم سازگاری با نگارش 1 آن تولید شدهاست، بسیاری از مشکلات نگارش قبلی را ندارد. همچنین همین مساله سازگاری آنرا با نگارشهای بعد از 2 نیز تضمین خواهد کرد. چون اینبار دست به یک طراحی مجدد زدهاند و ... این طراحی مجدد خیلی برای آنها گران تمام شدهاست (خصوصا از لحاظ حجم انتقادهای رسیده). بنابراین در آینده در زمینه سازگاری با نگارشهای قبلی به شدت محتاط خواهند بود. این موردی است که استفاده کنندگان از ReactJS به زودی با آن مواجه خواهند شد.
- گروه کاربری مصرف کنندگان آن. چه تعداد مقاله، ویدیوی آموزشی، انجمن رفع اشکال و امثال اینها را در مورد یک محصول میتوانید پیدا کنید؟ قطعا AngularJS در این زمینه حرف اول را میزند.
- Angular 2 بر مبنای استاندارد web component طراحی شدهاست که در دراز مدت نیز سبب برد AngularJS 2 خواهد شد و نیاز کمتر به بازنویسیهای کلی.
- Angular 2 برای کار با محصولات موبایل بهینه سازی شدهاست و مانند بوت استرپ 3 یک فریم ورک mobile first است.
- استفادهی از Type Script به عنوان زبان اول این پلتفرم (البته استفادهی از آن اجباری نیست). این مساله در دراز مدت سبب تولید کدهایی با کیفیت بالاتر میشود. برای مثال الان ReactJS مخلوطی است از ES 5 و ES 6. اما AngularJS 2 تصمیم بهتری را اتخاذ کردهاست. همین مساله سبب شدهاست که توسعهی Type Script توسط مایکروسافت سرعت بیشتری پیدا کند.
- داشتن امکانات توکار بیشتری نسبت به رقبا. برای مثال ReactJS یک کتابخانه است؛ اما AngularJS 2 یک فریم ورک کامل که تمام کتابخانههای جانبی رقبا را یکجا از روز اول دارد.
بنابراین آیا پس از ارائهی نهایی Angular 2، ارزش یادگیری را دارد؟ بله؛ حتما. ابتدا (و هم اکنون) اعتراضهای شدیدی در مورد عدم سازگاری آن با نگارشهای قبلی وجود خواهد داشت و پس از مدتی همه آنرا فراموش کرده و خودشان را وفق میدهند.
تعریف موجودیتهای مورد نیاز جهت طراحی یک سیستم اعتبارسنجی
در اینجا کنترل کامل سیستم در اختیار ما است و در این حالت میتوان طراحی تمام قسمتها را از ابتدا و مطابق میل خود انجام داد. برای مثال سیستم اعتبارسنجی سادهی ما، شامل جدول کاربران و نقشهای آنها خواهد بود و این دو با هم رابطهی many-to-many دارند. به همین جهت جدول UserRole نیز در اینجا پیش بینی شدهاست.
جدول کاربران
public class User { public User() { UserRoles = new HashSet<UserRole>(); } public int Id { get; set; } public string Username { get; set; } public string Password { get; set; } public string DisplayName { get; set; } public bool IsActive { get; set; } public DateTimeOffset? LastLoggedIn { get; set; } /// <summary> /// every time the user changes his Password, /// or an admin changes his Roles or stat/IsActive, /// create a new `SerialNumber` GUID and store it in the DB. /// </summary> public string SerialNumber { get; set; } public virtual ICollection<UserRole> UserRoles { get; set; } }
جدول نقشهای کاربران
public class Role { public Role() { UserRoles = new HashSet<UserRole>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<UserRole> UserRoles { get; set; } }
public static class CustomRoles { public const string Admin = nameof(Admin); public const string User = nameof(User); }
جدول ارتباط نقشها با کاربران و برعکس
public class UserRole { public int UserId { get; set; } public int RoleId { get; set; } public virtual User User { get; set; } public virtual Role Role { get; set; } }
تعریف Context برنامه و فعالسازی Migrations در EF Core 2.0
DbContext برنامه را به صورت ذیل در یک اسمبلی دیگر اضافه خواهیم کرد:
public interface IUnitOfWork : IDisposable { DbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveChanges(bool acceptAllChangesOnSuccess); int SaveChanges(); Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()); Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()); } public class ApplicationDbContext : DbContext, IUnitOfWork { public ApplicationDbContext(DbContextOptions options) : base(options) { } public virtual DbSet<User> Users { set; get; } public virtual DbSet<Role> Roles { set; get; } public virtual DbSet<UserRole> UserRoles { get; set; } protected override void OnModelCreating(ModelBuilder builder) { // it should be placed here, otherwise it will rewrite the following settings! base.OnModelCreating(builder); // Custom application mappings builder.Entity<User>(entity => { entity.Property(e => e.Username).HasMaxLength(450).IsRequired(); entity.HasIndex(e => e.Username).IsUnique(); entity.Property(e => e.Password).IsRequired(); entity.Property(e => e.SerialNumber).HasMaxLength(450); }); builder.Entity<Role>(entity => { entity.Property(e => e.Name).HasMaxLength(450).IsRequired(); entity.HasIndex(e => e.Name).IsUnique(); }); builder.Entity<UserRole>(entity => { entity.HasKey(e => new { e.UserId, e.RoleId }); entity.HasIndex(e => e.UserId); entity.HasIndex(e => e.RoleId); entity.Property(e => e.UserId); entity.Property(e => e.RoleId); entity.HasOne(d => d.Role).WithMany(p => p.UserRoles).HasForeignKey(d => d.RoleId); entity.HasOne(d => d.User).WithMany(p => p.UserRoles).HasForeignKey(d => d.UserId); }); } }
سازندهی کلاس به همراه پارامتر DbContextOptions است تا بتوان آنرا در آغاز برنامه تغییر داد.
فعالسازی مهاجرتها در EF Core 2.0
EF Core 2.0 برخلاف نگارشهای قبلی آن به دنبال کلاسی مشتق شدهی از IDesignTimeDbContextFactory میگردد تا بتواند نحوهی وهله سازی ApplicationDbContext را دریافت کند. در اینجا چون DbContext تعریف شده دارای یک سازندهی با پارامتر است، EF Core 2.0 نمیداند که چگونه باید آنرا در حین ساخت مهاجرتها و اعمال آنها، وهله سازی کند. کار کلاس ApplicationDbContextFactory ذیل دقیقا مشخص سازی همین مساله است:
/// <summary> /// Only used by EF Tooling /// </summary> public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> { public ApplicationDbContext CreateDbContext(string[] args) { var basePath = Directory.GetCurrentDirectory(); Console.WriteLine($"Using `{basePath}` as the BasePath"); var configuration = new ConfigurationBuilder() .SetBasePath(basePath) .AddJsonFile("appsettings.json") .Build(); var builder = new DbContextOptionsBuilder<ApplicationDbContext>(); var connectionString = configuration.GetConnectionString("DefaultConnection"); builder.UseSqlServer(connectionString); return new ApplicationDbContext(builder.Options); } }
{ "ConnectionStrings": { "DefaultConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCore2CookieAuthenticationDB;Integrated Security=True;MultipleActiveResultSets=True;" }, "LoginCookieExpirationDays": 30 }
کار یافتن این کلاس در حین تدارک و اعمال مهاجرتها توسط EF Core 2.0 خودکار بوده و باید محل قرارگیری آن دقیقا در اسمبلی باشد که DbContext برنامه در آن تعریف شدهاست.
تدارک لایه سرویسهای برنامه
پس از مشخص شدن ساختار موجودیتها و همچنین Context برنامه، اکنون میتوان لایه سرویس برنامه را به صورت ذیل تکمیل کرد:
سرویس کاربران
public interface IUsersService { Task<string> GetSerialNumberAsync(int userId); Task<User> FindUserAsync(string username, string password); Task<User> FindUserAsync(int userId); Task UpdateUserLastActivityDateAsync(int userId); }
پیاده سازی کامل این سرویس را در اینجا میتوانید مشاهده کنید.
سرویس نقشهای کاربران
public interface IRolesService { Task<List<Role>> FindUserRolesAsync(int userId); Task<bool> IsUserInRole(int userId, string roleName); Task<List<User>> FindUsersInRoleAsync(string roleName); }
پیاده سازی کامل این سرویس را در اینجا میتوانید مشاهده کنید.
سرویس آغاز بانک اطلاعاتی
public interface IDbInitializerService { void Initialize(); void SeedData(); }
پیاده سازی کامل این سرویس را در اینجا میتوانید مشاهده کنید.
سرویس اعتبارسنجی کوکیهای کاربران
یکی از قابلیتهای میانافزار اعتبارسنجی ASP.NET Core 2.0، رخدادی است که در آن اطلاعات کوکی دریافتی از کاربر، رمزگشایی شده و در اختیار برنامه جهت تعیین اعتبار قرار میگیرد:
public interface ICookieValidatorService { Task ValidateAsync(CookieValidatePrincipalContext context); } public class CookieValidatorService : ICookieValidatorService { private readonly IUsersService _usersService; public CookieValidatorService(IUsersService usersService) { _usersService = usersService; _usersService.CheckArgumentIsNull(nameof(usersService)); } public async Task ValidateAsync(CookieValidatePrincipalContext context) { var userPrincipal = context.Principal; var claimsIdentity = context.Principal.Identity as ClaimsIdentity; if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any()) { // this is not our issued cookie await handleUnauthorizedRequest(context); return; } var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber); if (serialNumberClaim == null) { // this is not our issued cookie await handleUnauthorizedRequest(context); return; } var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value; if (!int.TryParse(userIdString, out int userId)) { // this is not our issued cookie await handleUnauthorizedRequest(context); return; } var user = await _usersService.FindUserAsync(userId).ConfigureAwait(false); if (user == null || user.SerialNumber != serialNumberClaim.Value || !user.IsActive) { // user has changed his/her password/roles/stat/IsActive await handleUnauthorizedRequest(context); } await _usersService.UpdateUserLastActivityDateAsync(userId).ConfigureAwait(false); } private Task handleUnauthorizedRequest(CookieValidatePrincipalContext context) { context.RejectPrincipal(); return context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } }
- آیا کوکی دریافت شده دارای اطلاعات HttpContext.User است؟
- آیا این کوکی به همراه اطلاعات فیلد SerialNumber است؟
- آیا این کوکی به همراه Id کاربر است؟
- آیا کاربری که بر اساس این Id یافت میشود غیرفعال شدهاست؟
- آیا کاربری که بر اساس این Id یافت میشود دارای SerialNumber یکسانی با نمونهی موجود در بانک اطلاعاتی است؟
اگر خیر، این اعتبارسنجی رد شده و بلافاصله کوکی کاربر نیز معدوم خواهد شد.
تنظیمات ابتدایی میانافزار اعتبارسنجی کاربران در ASP.NET Core 2.0
تنظیمات کامل ابتدایی میانافزار اعتبارسنجی کاربران در ASP.NET Core 2.0 را در فایل Startup.cs میتوانید مشاهده کنید.
ابتدا سرویسهای برنامه معرفی شدهاند:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUnitOfWork, ApplicationDbContext>(); services.AddScoped<IUsersService, UsersService>(); services.AddScoped<IRolesService, RolesService>(); services.AddScoped<ISecurityService, SecurityService>(); services.AddScoped<ICookieValidatorService, CookieValidatorService>(); services.AddScoped<IDbInitializerService, DbInitializerService>();
سپس تنظیمات مرتبط با ترزیق وابستگیهای ApplicationDbContext برنامه انجام شدهاست. در اینجا رشتهی اتصالی، از فایل appsettings.json خوانده شده و سپس در اختیار متد UseSqlServer قرار میگیرد:
services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"), serverDbContextOptionsBuilder => { var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds; serverDbContextOptionsBuilder.CommandTimeout(minutes); serverDbContextOptionsBuilder.EnableRetryOnFailure(); }); });
در ادامه تعدادی Policy مبتنی بر نقشهای ثابت سیستم را تعریف کردهایم. این کار اختیاری است اما روش توصیه شدهی در ASP.NET Core، کار با Policyها است تا کار مستقیم با نقشها. Policyها انعطاف پذیری بیشتری را نسبت به نقشها ارائه میدهند و در اینجا به سادگی میتوان چندین نقش و یا حتی Claim را با هم ترکیب کرد و به صورت یک Policy ارائه داد:
// Only needed for custom roles. services.AddAuthorization(options => { options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin)); options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User)); });
قسمت اصلی تنظیمات میان افزار اعتبارسنجی مبتنی بر کوکیها در اینجا قید شدهاست:
// Needed for cookie auth. services .AddAuthentication(options => { options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { options.SlidingExpiration = false; options.LoginPath = "/api/account/login"; options.LogoutPath = "/api/account/logout"; //options.AccessDeniedPath = new PathString("/Home/Forbidden/"); options.Cookie.Name = ".my.app1.cookie"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SameSite = SameSiteMode.Lax; options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>(); return cookieValidatorService.ValidateAsync(context); } }; });
کار نهایی تنظیمات میان افزار اعتبارسنجی در متد Configure با فراخوانی UseAuthentication صورت میگیرد. اینجا است که میان افزار، به برنامه معرفی خواهد شد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication();
همچنین پس از آن، کار اجرای سرویس آغاز بانک اطلاعاتی نیز انجام شدهاست تا نقشها و کاربر Admin را به سیستم اضافه کند:
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var dbInitializer = scope.ServiceProvider.GetService<IDbInitializerService>(); dbInitializer.Initialize(); dbInitializer.SeedData(); }
پیاده سازی ورود و خروج به سیستم
پس از این مقدمات به مرحلهی آخر پیاده سازی این سیستم اعتبارسنجی میرسیم.
پیاده سازی Login
در اینجا از سرویس کاربران استفاده شده و بر اساس نام کاربری و کلمهی عبور ارسالی به سمت سرور، این کاربر یافت خواهد شد.
در صورت وجود این کاربر، مرحلهی نهایی کار، فراخوانی متد الحاقی HttpContext.SignInAsync است:
[AllowAnonymous] [HttpPost("[action]")] public async Task<IActionResult> Login([FromBody] User loginUser) { if (loginUser == null) { return BadRequest("user is not set."); } var user = await _usersService.FindUserAsync(loginUser.Username, loginUser.Password).ConfigureAwait(false); if (user == null || !user.IsActive) { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Unauthorized(); } var loginCookieExpirationDays = _configuration.GetValue<int>("LoginCookieExpirationDays", defaultValue: 30); var cookieClaims = await createCookieClaimsAsync(user).ConfigureAwait(false); await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, cookieClaims, new AuthenticationProperties { IsPersistent = true, // "Remember Me" IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.AddDays(loginCookieExpirationDays) }); await _usersService.UpdateUserLastActivityDateAsync(user.Id).ConfigureAwait(false); return Ok(); }
private async Task<ClaimsPrincipal> createCookieClaimsAsync(User user) { var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); identity.AddClaim(new Claim(ClaimTypes.Name, user.Username)); identity.AddClaim(new Claim("DisplayName", user.DisplayName)); // to invalidate the cookie identity.AddClaim(new Claim(ClaimTypes.SerialNumber, user.SerialNumber)); // custom data identity.AddClaim(new Claim(ClaimTypes.UserData, user.Id.ToString())); // add roles var roles = await _rolesService.FindUserRolesAsync(user.Id).ConfigureAwait(false); foreach (var role in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role.Name)); } return new ClaimsPrincipal(identity); }
[Route("api/[controller]")] [Authorize(Policy = CustomRoles.Admin)] public class MyProtectedAdminApiController : Controller
پیاده سازی Logout
متد الحاقی HttpContext.SignOutAsync کار Logout کاربر را تکمیل میکند.
[AllowAnonymous] [HttpGet("[action]"), HttpPost("[action]")] public async Task<bool> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return true; }
آزمایش نهایی برنامه
در فایل index.html ، نمونهای از متدهای لاگین، خروج و فراخوانی اکشن متدهای محافظت شده را مشاهده میکنید. این روش برای برنامههای تک صفحهای وب یا SPA نیز میتواند مفید باشد و به همین نحو کار میکنند.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
Angular 10 منتشر شد
Version 10.0.0 is here! This is a major release that spans the entire platform, including the framework, Angular Material, and the CLI. This release is smaller than typical; it has only been 4 months since we released version 9.0 of Angular.
نگران Angular 3 نباشید
in Angular 1.x we had this:
- Angular 1.0 - major version
- Angular 1.1 - major version (well, more a preview of Angular 1.2)
- Angular 1.2 - major version
- Angular 1.3 - major version (dropped IE8 support)
- Angular 1.4 - major version
- Angular 1.5 - major version
In “Angular 2”, you’re looking at this:
- Angular 2 - major version
- Angular 3 - major version
- Angular 4 - major version
- Angular 5 - major version
- Angular 6 - major version
- Angular 7 - major version
We finished week 2 of the 9-week boot camp. This week was AngularJS week. We covered building the front-end of a Single Page App with the AngularJS framework. In particular, we covered topics such as client-side routing, making Ajax calls using the $http service and the $route factory, building custom AngularJS services, working with Google Maps, using Angular UI Bootstrap, and uploading files to services such as FilePicker.io.
انتقال توسط مقدار
اگر پارامتری به صورت value type تعریف شود، این مقدار درون متد، ایجاد شده و حافظهای به آن اختصاص داده میشود و سپس در انتهای متد تخریب خواهد شد. بنابراین تغییری در مقدار آن، سبب انعکاس آن به فراخوان متد، نخواهد شد.
static void PassByValueSample() { int a = 1; PassByValue(a); Console.WriteLine($"after the invocation of {nameof(PassByValue)}, {nameof(a)} = {a}"); } static void PassByValue(int x) { x = 2; }
انتقال توسط ارجاع
برای بازگشت مقدار تغییر داده شدهی توسط یک متد، میتوان یک نوع خروجی را برای آن تعریف کرد. راه دیگر آن تعریف یک پارامتر توسط واژهی کلیدی ref است. پارامتری که به این روش تعریف شود، هم ارسال کنندهی مقدار و هم دریافت کنندهی تغییرات خواهد بود.
static void PassByReferenceSample() { int a = 1; PassByReference(ref a); Console.WriteLine($"after the invocation of {nameof(PassByReference)}, {nameof(a)} = {a}"); } static void PassByReference(ref int x) { x = 2; }
متغیرهای out
با استفاده از واژهی کلیدی ref، میتوان یک مقدار را هم به تابع ارسال کرد و هم از آن دریافت نمود. اما اگر تنها قرار است مقداری از تابع بازگشت داده شود، میتوان از متغیرهای out استفاده کرد.
static void OutSample() { Out(out int a); Console.WriteLine($"after the invocation of {nameof(Out)}, {nameof(a)} = {a}"); } static void Out(out int x) { x = 2; }
البته اگر تنها قرار است یک مقدار را از یک متد دریافت کنیم، بهتر است نوع خروجی متد را مشخص کرد و از آن استفاده نمود؛ بجای استفادهی از متغیرهای out. یک نمونه کاربرد مفید متغیرهای out در خود فریم ورک و توسط متد TryParse وجود دارد:
if (int.TryParse("42", out var result)) { Console.WriteLine($"the result is {result}"); }
Ref Locals در C# 7
در C# 7، امکان تعریف متغیرهای محلی از نوع ref نیز وجود دارد:
int x = 3; ref int x1 = ref x; x1 = 2; Console.WriteLine($"local variable {nameof(x)} after the change: {x}");
باید دقت داشت که نمیتوان یک مقدار را به یک ref variable نسبت داد:
ref int i = sequence.Count();
ref int number1 = null; // ERROR ref int number2 = 42; // ERROR
Ref Returns در C# 7
در C# 7، واژهی کلیدی ref را به همراه نوع خروجی نیز میتوان بکار برد:
static ref int ReturnByReference() { int[] arr = { 1 }; ref int x = ref arr[0]; return ref x; }
static ref int ReturnByReference2() { int[] arr = { 1 }; return ref arr[0]; }
هرچند امکان بازگشت یک متغیر محلی int به صورت ref وجود ندارد، اما اگر این متغیر به صورت ref به تابع ارسال شده باشد، این امکان میسر است:
static ref int ReturnByReference3(ref int x) { x = 2; return ref x; }
چند نکته: امکان تعریف فیلد ref و یا خاصیت get;set دار از نوع ref وجود ندارد. اما تعریف خواصی که یک ref را بازگشت میدهند، میسر هستند:
class Thing1 { ref string _Text1; /* Error */ ref string Text2 { get; set; } /* Error */ string _text = "Text"; ref string Text3 { get { return ref _text; } } // Properties that return a reference are allowed }
مزیت کار با ref returns
ref returnها شاید آنچنان در کدهای روزمرهی #C بکارگرفته نشوند، اما نیاز به کدهای unsafe و کار مستقیم با pointers را به حداقل میرسانند و به آن میتوان لقب safe pointer را اطلاق کرد؛ از این جهت که این کد هنوز هم یک managed code است و نه یک unsafe code.
private MyBigStruct[] array = new MyBigStruct[10]; private int current; public ref MyBigStruct GetCurrentItem() { return ref array[current]; }