The new version 2.3.1 of the Roslynator.Analyzers package brings the number of analyzers, refactorings and fixes to over 500.
سوالات مصاحبه ای در مورد #C و دات نت
انتقال خودکار Data Annotations از مدلها به ViewModelهای ASP.NET MVC به کمک AutoMapper
public LoginProfile() { CreateMap<LoginViewModel, User>().ForAllMembers(_ => _.Ignore()); CreateMap<LoginViewModel, User>().ForMember(_ => _.UserName , __ => __.MapFrom(_ => _.UserName)); CreateMap<LoginViewModel, User>().ForMember(_ => _.PasswordHash , __ => __.MapFrom(_ => _.Password)); }
Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters ========================================================================================== LoginViewModel -> User (Destination member list) App.ViewModel.Enities.Identity.LoginViewModel -> App.DomainClasses.Entities.Identity.User (Destination member list) Unmapped properties: FirstName LastName IsSystemAccount IsBan RegisterDate LastLoginDate RowVersion City CityId // more ...
import {Observable} from 'rxjs';
روشهای مختلف import ویژگیهای کتابخانهی RxJS
الف) import همه چیز به صورت یکجا
import Rx from "rxjs/Rx";
Rx.Observable.of(1, 2, 3).map(i => i.toString());
ب) تنها import ویژگیهای مورد نیاز
import { Observable } from "rxjs/Observable"; import "rxjs/add/observable/of"; import "rxjs/add/operator/map";
یک مثال
Observable.of(1, 2, 3).map(i => i.toString());
ج) فراخوانی مستقیم متدهای RxJS نه از طریق Observable
import { Observable } from "rxjs/Observable"; import { of } from "rxjs/observable/of"; import { map } from "rxjs/operator/map";
const source = of(1, 2, 3); const mapped = map.call(source, i => i.toString());
مقید کردن برنامه به عدم استفاده از حالت «الف» و اجبار به استفاده از حالت «ب»
اگر به ریشهی پوشهی پروژههای مبتنی بر Angular CLI دقت کنید، فایل tslint.json نیز در آنها قابل مشاهده است و اگر افزونهی VSCode آنرا نیز نصب کرده باشید، در حین کار با VSCode، خطاهای مرتبط را درون ادیتور مشاهده خواهید کرد. TSLint، قابلیت توسعه داشته و یک نمونهی از اینها، بستهی TSLint rules for RxJS است. برای نصب آن ابتدا دستور ذیل را صادر کنید:
npm install rxjs-tslint-rules --save-dev
{ "rulesDirectory": [ "node_modules/codelyzer" ], "extends": [ "rxjs-tslint-rules" ], "rules": { "rxjs-add": { "severity": "error" }, "rxjs-no-patched": { "severity": "error" }, "rxjs-no-unused-add": { "severity": "error" }, "rxjs-no-wholesale": { "severity": "error" }, "rxjs-no-subject-unsubscribe": { "severity": "error" },
مدیریت بهتر حالت «ب» یا «تنها import ویژگیهای مورد نیاز»
زمانیکه از روش «ب» استفاده میکنیم، کلاسهای سرویس برنامه پر خواهند شد از کدهای تکراری ذیل:
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/catch';
برای مدیریت بهتر اینکار، فایل جدیدی را به نام src\app\shared\rxjs-operators.ts ایجاد میکنیم؛ با محتوای ذیل:
// define the rxjs operators needed by your app // see node_module/rxjs/Rx.js for more // statics import "rxjs/add/observable/from"; import "rxjs/add/observable/throw"; // operators import "rxjs/add/operator/catch"; import "rxjs/add/operator/combineLatest"; import "rxjs/add/operator/debounceTime"; import "rxjs/add/operator/delay"; import "rxjs/add/operator/distinctUntilChanged"; import "rxjs/add/operator/do"; import "rxjs/add/operator/filter"; import "rxjs/add/operator/finally"; import "rxjs/add/operator/first"; import "rxjs/add/operator/ignoreElements"; import "rxjs/add/operator/let"; import "rxjs/add/operator/map"; import "rxjs/add/operator/mapTo"; import "rxjs/add/operator/mergeMap"; import "rxjs/add/operator/startWith"; import "rxjs/add/operator/switchMap"; import "rxjs/add/operator/takeUntil"; import "rxjs/add/operator/withLatestFrom"; import "rxjs/add/operator/takeUntil"; import "rxjs/add/operator/take";
سپس کافی است به فایل src\app\app.module.ts مراجعه کرده و این فایل را import کنیم:
// import RxJs needed operators only once import "./shared/rxjs-operators"; //... @NgModule({ //...
import { Observable } from "rxjs/Observable";
دریافت Git for Windows 2.37.0
Changes since Git for Windows v2.36.1 (May 9th 2022)
New Features
- Comes with Git v2.37.0.
- Many anti-malware products seem to have problems with our MSYS2 runtime, leading to problems running e.g.
git subtree
. We added a workaround that hopefully helps in most of these scenarios. - Comes with MSYS2 runtime (Git for Windows flavor) based on Cygwin 3.3.5.
- Comes with PCRE2 v10.40.
- Comes with Git LFS v3.2.0.
- Comes with GNU TLS v3.7.6.
- SSH's CBC ciphers, which were re-enabled in 2017 to better support Azure Repos have again been disabled by default because Azure Repos does not require them any longer.
- Comes with OpenSSL v1.1.1p.
- Comes with Git Credential Manager Core v2.0.779.
- Comes with cURL v7.84.0.
Security Advisory Notices
CVE-2019-1077 Visual Studio Extension Auto Update Vulnerability
An elevation of privilege vulnerability exists when the Visual Studio Extension auto-update process improperly performs certain file operations. An attacker who successfully exploited this vulnerability could delete files in arbitrary locations. To exploit this vulnerability, an attacker would require unprivileged access to a vulnerable system. The security update addresses the vulnerability by securing locations the Visual Studio Extension auto-update performs file operations in.
CVE-2019-1075 ASP.NET Core Spoofing Vulnerability
A spoofing vulnerability exists in ASP.NET Core that could lead to an open redirect. An attacker who successfully exploited the vulnerability could redirect a targeted user to a malicious website. To exploit the vulnerability, an attacker could send a link that has a specially crafted URL and convince the user to click the link.
The security update addresses the vulnerability by correcting how ASP.NET Core parses URLs. Details can be found in the .NET Core release notes.
CVE-2019-1113 WorkflowDesigner XOML deserialization allows code execution
A XOML file referencing certain types could cause random code to be executed when the XOML file is opened in Visual Studio. There is now a restriction on what types are allowed to be used in XOML files. If a XOML file containing one of the newly unauthorized types is opened, a message is displayed explaining that the type is unauthorized.
For further information, please refer to https://support.microsoft.com/en-us/help/4512190/remote-code-execution-vulnerability-if-types-are-specified-in-xoml.
public abstract class BaseEntity { #region Properties /// <summary> /// gets or sets Identifier of this Entity /// </summary> public virtual long Id { get; set; } /// <summary> /// gets or sets date that this entity was created /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets Date that this entity was updated /// </summary> public virtual DateTime ModifiedOn { get; set; } /// <summary> /// indicate this entity is Locked for Modify /// </summary> public virtual bool ModifyLocked { get; set; } /// <summary> /// gets or sets date that this entity repoted last time /// </summary> public virtual DateTime? ReportedOn { get; set; } /// <summary> /// gets or sets counter for Content's report /// </summary> public virtual int ReportsCount { get; set; } /// <summary> /// gets or sets TimeStamp for prevent concurrency Problems /// </summary> public virtual byte[] RowVersion { get; set; } #endregion #region NavigationProperties /// <summary> /// gets ro sets User that Modify this entity /// </summary> public virtual User ModifiedBy { get; set; } /// <summary> /// gets ro sets Id of User that modify this entity /// </summary> public virtual long? ModifiedById { get; set; } /// <summary> /// gets ro sets User that Create this entity /// </summary> public virtual User CreatedBy { get; set; } /// <summary> /// gets ro sets User that Create this entity /// </summary> public virtual long CreatedById { get; set; } #endregion }
- ReportedOn : نگهداری آخرین تاریخ اخطار
- ModifyLocked : به منظور ممانعت از ویرایش
- CreatedBy,CreatedById : به منظور ایجاد ارتباط یک به چند بین کاربر و سایر موجودیتها (به عنوان ایجاد کننده)
- ModifiedBy , ModifiedById : به منظور ایجاد ارتباط یک به چند بین کاربر و سایر موجودیتها (به عنوان آخرین تغییر دهنده)
مدل کلکسیون (کالکشن ،Collection)
/// <summary> /// Represents the Collection for group posts by topic /// </summary> public class Collection : GuidBaseEntity { #region Properties /// <summary> /// gets or sets name of collection /// </summary> public virtual string Name { get; set; } /// <summary> /// gets or sets Alternative SlugUrl /// </summary> public virtual string SlugUrl { get; set; } /// <summary> /// gets or sets some description of group /// </summary> public virtual string Description { get; set; } /// <summary> /// gets or sets description that indicate how to pay /// </summary> public virtual string HowToPay { get; set; } /// <summary> /// gets or sets Visibility Type /// </summary> public virtual CollectionVisibility Visibility { get; set; } /// <summary> /// gets or sets color of Collection's Cover /// </summary> public virtual string Color { get; set; } /// <summary> /// gets or sets Name of Image that used as Cover /// </summary> public virtual string Photo { get; set; } /// <summary> /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance /// </summary> public virtual string TagNames { get; set; } /// <summary> /// indicate this collection is active or not /// </summary> public virtual bool IsActive { get; set; } #endregion #region NavigationProperties /// <summary> /// get or set collection of attachments that attached in this group /// </summary> public virtual ICollection<CollectionAttachment> Attachments { get; set; } /// <summary> /// get or set tags of collection /// </summary> public virtual ICollection<Tag> Tags { get; set; } /// <summary> /// get or set List Of Posts that Associated with this Collection /// </summary> public virtual ICollection<CollectionPost> Posts { get; set; } /// <summary> /// get or set Users that they are Member of this collection if visibility is Custom /// </summary> public virtual ICollection<User> Memebers { get; set; } #endregion } public enum CollectionVisibility { Friends, OnlyMe, Public, NotFree, Custom }
- Visibility : برای اعمال محدودیت دسترسی به یک کلکسیون در نظر گرفته شده است. از نوع، نوع دادهی شمارشی CollectionVisibility پیاده سازی شدهی دربالا، میباشد. حالت Custom برای زمانی است که نیاز است صرفا یک سری از کاربران بدون هزینه، دسترسی برای مطالعهی این کلکسیون داشته باشند. حالت NotFree هم برای زمانی است که کاربرانی که هزینهی مورد نظر را پرداخت کرده باشند، به عنوان عضو به این کلکسیون میتوانند دسترسی داشته باشند.
- Members : به منظور اعمال ارتباط چند به چند بین مدل کاربر و مدل کلکسیون، در نظر گرفته شده است و زمانی این لیست اعضا خالی نیست که حالت Visibility با NotFree یا Custom مقدار دهی شده باشد.
- Tags : برای یافتن راحتتر کلکسیونهای مورد نظر کاربران، یک ارتباط چند به چند بین کلکسیونها و مخزن برچسب مطرح شدهی در مقاله اول، در نظر گرفته شده است.
- TagNames : برای افزایش کارآیی سیستم در نظر گرفته شده است و نام برچسبهای در ارتباط با کلکسیون را در خود به صورت جدا شده با (,) نگهداری میکند.
- Posts : لیست پستهایی است که توسط مدیر کلکسیون در آن ارسال میشود. لذا یک ارتباط یک به چند بین کلکسیونها و CollectionPost در نظر گرفته شده است.
- Attachments : لیست فایلهایی است که در کلکسیون بارگذاری شدهاند (در ادامه پیاده سازی خواهد شد).
- Owner , OwnerId : هر کلکسیون نیز یک صاحب خواهد داشت. بدین منظور یک ارتباط یک به چند بین کاربر و کلکسیون برقرار شده است.
- HowToPay : توضیحاتی در مورد نحوهی پرداخت هزینه (در صورتی که Visibility این کلکسیون NotFree باشد).
مدل پستهای کلکسیون ها
public class CollectionPost : GuidBaseEntity { #region Properties /// <summary> /// indicate this post should be pin /// </summary> public virtual bool IsPin { get; set; } /// <summary> /// gets or sets the blog pot body /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets the content title /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets value indicating Custom Slug /// </summary> public virtual string SlugAltUrl { get; set; } /// <summary> /// gets or sets a value indicating whether the content comments are allowed /// </summary> public virtual bool AllowComments { get; set; } /// <summary> /// indicate comments should be approved before display /// </summary> public virtual bool ModerateComments { get; set; } /// <summary> /// gets or sets viewed count /// </summary> public virtual long ViewCount { get; set; } /// <summary> /// Gets or sets the total number of approved comments /// <remarks>The same as if we run Item.Comments.Count() /// We use this property for performance optimization (no SQL command executed) /// </remarks> /// </summary> public virtual int ApprovedCommentsCount { get; set; } /// <summary> /// Gets or sets the total number of unapproved comments /// <remarks>The same as if we run Item.Comments.Count() /// We use this property for performance optimization (no SQL command executed) /// </remarks> /// </summary> public virtual int UnApprovedCommentsCount { get; set; } /// <summary> /// gets or sets rating complex instance /// </summary> public virtual Rating Rating { get; set; } /// <summary> /// gets or sets information of User-Agent /// </summary> public virtual string Agent { get; set; } /// <summary> /// indicate users can share this post /// </summary> public virtual bool IsEnableForShare { get; set; } #endregion #region NavigationProperties /// <summary> /// get or set comments of this post /// </summary> public virtual ICollection<CollectionComment> Comments { get; set; } /// <summary> /// gets or sets collection that associated with this post /// </summary> public virtual Collection Collection { get; set; } /// <summary> /// gets or sets id of collection that associated with this post /// </summary> public virtual Guid CollectionId { get; set; } #endregion }
- IsEnableForShare : اگر با مقدار True مقدار دهی شده باشد ، امکان به اشتراک گذاری آن درشبکههای اجتماعی وجود خواهد داشت.
- Comments : اگر مقدار AllowComments مربوط به پست ارسالی true باشد، در آن صورت امکان نظر دهی به پست هم امکان پذیر خواهد بود. برای برقراری ارتباط یک به چند بین مدل پست کلکسیون و نظرات کلکسیون، این لیست در مدل بالا گنجانده شده است.
- ModerateComments : اگر برای پست خاصی، با مقدار true مقدار دهی شده باشد، نظرات آن پست قبل از نمایش باید توسط مدیر آن کلکسیون تأیید شوند.
- Author , AuthorId : در واقع ارسال کنندهی تمام پستها، همان صاحب کلکسیون میباشد. ولی برای راحتی واکشی لیست پستهای ارسالی کاربر مورد نظر یک ارتباط یک به چند بین کاربر و پستهای ارسالی را در کلکسیون اعمال کردهایم.
- Collection , CollectionId : کلید خارجی ما در دیتابیس ایجاد شده خواهد بود که نشان دهندهی ارتباط یک به چند بین کلکسیون و پستها میباشد.
- IsPin : اگر لازم است پستی به عنوان اولین پست در کلکسیون نمایش داده شود، این خصوصیت برای آن true خواهد بود.
- ApprovedCommentsCount , UnApprovedCommentsCount: برای افزایش کارآیی سیستم در نظر گرفته شده است و هنگام درج نظر جدید یا حذف نظر، ویرایش خواهند شد.
مدل نظرات ارسالی در کلکسیون ها
public class CollectionComment { #region Ctor public CollectionComment() { Id = SequentialGuidGenerator.NewSequentialGuid(); CreatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// get or set identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets date of creation /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets body of blog post's comment /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets body of blog post's comment /// </summary> public virtual Rating Rating { get; set; } /// <summary> /// gets or sets informations of agent /// </summary> public virtual string UserAgent { get; set; } /// <summary> /// indicate this comment is Approved /// </summary> public virtual bool IsApproved { get; set; } /// <summary> /// gets or sets Ip Address of Creator /// </summary> public virtual string CreatorIp { get; set; } /// <summary> /// gets or sets datetime that is modified /// </summary> public virtual DateTime? ModifiedOn { get; set; } /// <summary> /// gets or sets counter for report this comment /// </summary> public virtual int ReportsCount { get; set; } /// <summary> /// indicate this entity is Locked for Modify /// </summary> public virtual bool ModifyLocked { get; set; } /// <summary> /// gets or sets date that this entity repoted last time /// </summary> public virtual DateTime? ReportedOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets post that this comment added it /// </summary> public virtual CollectionPost Post { get; set; } /// <summary> /// gets or sets id of post that this comment added it /// </summary> public virtual Guid PostId { get; set; } /// <summary> /// get or set user that create this record /// </summary> public virtual User Creator { get; set; } /// <summary> /// get or set Id of user that create this record /// </summary> public virtual long CreatorId { get; set; } /// <summary> /// gets or sets CollectionComment's identifier for Replying and impelemention self referencing /// </summary> public virtual Guid? ReplyId { get; set; } /// <summary> /// gets or sets Collection's comment for Replying and impelemention self referencing /// </summary> public virtual CollectionComment Reply { get; set; } /// <summary> /// get or set collection of Collection's comment for Replying and impelemention self referencing /// </summary> public virtual ICollection<CollectionComment> Children { get; set; } #endregion }
مدل بالا نشان دهندهی نظرات ارسالی برای پستهای کلکسیونها میباشد. صرفا کاربران عضو سیستم این اجازه را در صورتی خواهند داشت که برای پست مورد نظر خصوصیت AllowComments با مقدار true مقدار دهی شده باشد
حالت درختی آن مشخص است. برای اعمال ارتباط یک به چند بین پستها و نظرات، از CollectionPost و CollectionPostId استفاده خواهد شد.
- IsApproved : برای زمانی استفاده خواهد شد که خصوصیت ModerateComments پست مورد نظر با مقدار true مقدار دهی شده باشد.
- ReportsCount : به مانند بخشهای قبل، تعداد اخطارهای داده شدهی برای یک نظر را نشان خواهد داد.
- Creator,CreatorId : ارسال کنندهی نظر میباشد و برای ایجاد ارتباط یک به چند بین کاربر و نظرات کلکسیونها در نظر گرفته شدهاند.
- ReportedOn : نگه داری آخرین تاریخ اخطار
- ModifyLocked : به منظور ممانعت از ویرایش
مدل فایلهای ضمیمه کلکسیون ها
public class CollectionAttachment : BaseAttachment { #region NavigationProperties /// <summary> /// gets or sets Collection that this file attached /// </summary> public virtual Collection Collection { get; set; } /// <summary> /// gets or sets Id of Collection that this file attached /// </summary> public virtual Guid? CollectionId { get; set; } #endregion }
مدل آگهی ها
/// <summary> /// Represents Announcement For Announcement Section /// </summary> public class Announcement : BaseContent { #region Properties /// <summary> /// gets or sets Date that this Announcement will Expire /// </summary> public virtual DateTime? ExpireOn { get; set; } /// <summary> /// indicate this accouncement is approved by admin if announcementSetting.Moderate==true /// </summary> public virtual bool IsApproved { get; set; } #endregion #region NavigationProperties /// <summary> /// get or set Collection of Comments for this Announcement /// </summary> public virtual ICollection<AnnouncementComment> Comments { get; set; } #endregion }
- ExpireOn : زمان انقضای آگهی
- IsApproved : به منظور اعمال مدیریتی در نظر گرفته شده است
- Comments : اگر امکان ارسال نظرات برای آگهی از بخش تنظیمات فعال باشد، این لیست نظرات ما را نگه داری خواهد کرد. لذا یک رابطهی یک به چند بین نظرات و آگهیها خواهد بود.
مدل نظرات آگهی ها
/// <summary> /// Repersents Comment For Announcement /// </summary> public class AnnouncementComment : BaseComment { #region NavigationProperties /// <summary> /// gets or sets body of announcement's comment /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets body of announcement's comment /// </summary> public virtual AnnouncementComment Reply { get; set; } /// <summary> /// gets or sets body of announcement's comment /// </summary> public virtual ICollection<AnnouncementComment> Children { get; set; } /// <summary> /// gets or sets announcement that this comment sent to it /// </summary> public virtual Announcement Announcement { get; set; } /// <summary> /// gets or sets announcement'Id that this comment sent to it /// </summary> public virtual long AnnouncementId { get; set; } #endregion }
مدل سیستم لاگ عملیات کاربران
/// <summary> /// Represent The Operation's log /// </summary> public class AuditLog { #region Ctor /// <summary> /// /// </summary> public AuditLog() { Id = SequentialGuidGenerator.NewSequentialGuid(); OperatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier of AuditLog /// </summary> public virtual Guid Id { get; set; } /// <summary> /// sets or gets description of Log /// </summary> public virtual string Description { get; set; } /// <summary> /// sets or gets when log is operated /// </summary> public virtual DateTime OperatedOn { get; set; } /// <summary> /// sets or gets log's section /// </summary> public virtual AuditSection Section { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets log's creator /// </summary> public virtual User OperatedBy { get; set; } /// <summary> /// sets or gets identifier of log's creator /// </summary> public virtual long OperatedById { get; set; } #endregion } public enum AuditSection { Blog, News, Forum, ... }
مدل دامینهای ممنوع
/// <summary> /// Represents Domain that is banned /// </summary> public class BannedDomain { #region Propertie /// <summary> /// gets or sets identifier of Domain /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets DomainName /// </summary> public virtual string Name { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime BannedOn { get; set; } #endregion }
مدل کلمات ممنوع
/// <summary> /// Represents the banned words /// </summary> public class BannedWord { #region Ctor /// <summary> /// Create one instance of <see cref="BannedWord"/> /// </summary> public BannedWord() { Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties /// <summary> /// gets or sets identifier of BannedWord /// </summary> public Guid Id { get; set; } /// <summary> /// gets or sets Bad word /// </summary> public string BadWord { get; set; } /// <summary> /// gets or sets Good replaceword /// </summary> public string GoodWord { get; set; } /// <summary> /// indicating that this Word is spam /// </summary> public bool IsStopWord { get; set; } #endregion }
- BadWord : کلمه مورد نظر که قرار است Ban شود.
- IsStopWord : اگر لازم نیست جایگزینی برای کلمه استفاده شود و فقط لازم است حذف گردد، مقدار این خصوصیت true خواهد بود.
- GoodWord : کلمه جایگزین
مدل تنظیمات سیستم
/// <summary> /// Represent The CMS setting /// </summary> public class Setting { #region Properties /// <summary> /// sets or gets name of setting /// </summary> public virtual string Name { get; set; } /// <summary> /// sets or gets value of setting /// </summary> public virtual string Value { get; set; } /// <summary> /// sets or gets Type of setting /// </summary> public virtual string Type { get; set; } #endregion }
نتیجهی این قسمت
اگر به کدهای مثال رسمی ASP.NET Identity نگاهی بیندازید، میبینید که کلاس مربوط به جدول کاربران ApplicationUser نام دارد، ولی در سیستم IRIS نام آن User است. بهتر است که ما هم نام کلاس خود را از User به ApplicationUser تغییر دهیم چرا که مزایای زیر را به دنبال دارد:
1- به راحتی میتوان کدهای مورد نیاز را از مثال Identity کپی کرد.
2- در سیستم Iris، بین کلاس User متعلق به پروژه خودمان و User مربوط به HttpContext تداخل رخ میداد که با تغییر نام کلاس User دیگر این مشکل را نخواهیم داشت.
برای این کار وارد پروژه Iris.DomainClasses شده و نام کلاس User را به ApplicationUser تغییر دهید. دقت کنید که این تغییر نام را از طریق Solution Explorer انجام دهید و نه از طریق کدهای آن. پس از این تغییر ویژوال استودیو میپرسد که آیا نام این کلاس را هم در کل پروژه تغییر دهد که شما آن را تایید کنید.
برای آن که نام جدول Users در دیتابیس تغییری نکند، وارد پوشهی Entity Configuration شده و کلاس UserConfig را گشوده و در سازندهی آن کد زیر را اضافه کنید:
ToTable("Users");
برای نصب ASP.NET Identity دستور زیر را در کنسول Nuget وارد کنید:
Get-Project Iris.DomainClasses, Iris.Datalayer, Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.EntityFramework
همچنین بهتر است که به کلاس CustomRole، یک property به نام Description اضافه کنید تا توضیحات فارسی نقش مورد نظر را هم بتوان ذخیره کرد:
public class CustomRole : IdentityRole<int, CustomUserRole> { public CustomRole() { } public CustomRole(string name) { Name = name; } public string Description { get; set; } }
نکته: پیشنهاد میکنم که اگر میخواهید مثلا نام CustomRole را به IrisRole تغییر دهید، این کار را از طریق find and replace انجام ندهید. با همین نامهای پیش فرض کار را تکمیل کنید و سپس از طریق خود ویژوال استودیو نام کلاس را تغییر دهید تا ویژوال استودیو به نحو بهتری این نامها را در سرتاسر پروژه تغییر دهد.
سپس کلاس ApplicationUser پروژه IRIS را باز کرده و تعریف آن را به شکل زیر تغییر دهید:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
اکنون میتوانید propertyهای Id، UserName، PasswordHash و Email را حذف کنید؛ چرا که در کلاس پایه IdentityUser تعریف شده اند.
وارد Iris.DataLayer شده و کلاس IrisDbContext را به شکل زیر ویرایش کنید:
public class IrisDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IUnitOfWork
public DbSet<ApplicationUser> Users { get; set; }
public IrisDbContext() : base("IrisDbContext") { }
همچنین درون متد OnModelCreating کدهای زیر را پس از فراخوانی متد (base.OnModelCreating(modelBuilder جهت تعیین نام جداول دیتابیس بنویسید:
modelBuilder.Entity<CustomRole>().ToTable("AspRoles"); modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims"); modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles"); modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins");
Add-Migration UpdateDatabaseToAspIdentity
public partial class UpdateDatabaseToAspIdentity : DbMigration { public override void Up() { CreateTable( "dbo.UserClaims", c => new { Id = c.Int(nullable: false, identity: true), UserId = c.Int(nullable: false), ClaimType = c.String(), ClaimValue = c.String(), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.UserLogins", c => new { LoginProvider = c.String(nullable: false, maxLength: 128), ProviderKey = c.String(nullable: false, maxLength: 128), UserId = c.Int(nullable: false), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId }) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.UserRoles", c => new { UserId = c.Int(nullable: false), RoleId = c.Int(nullable: false), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => new { t.UserId, t.RoleId }) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .ForeignKey("dbo.AspRoles", t => t.RoleId, cascadeDelete: true) .Index(t => t.RoleId) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.AspRoles", c => new { Id = c.Int(nullable: false, identity: true), Description = c.String(), Name = c.String(nullable: false, maxLength: 256), }) .PrimaryKey(t => t.Id) .Index(t => t.Name, unique: true, name: "RoleNameIndex"); AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "SecurityStamp", c => c.String()); AddColumn("dbo.Users", "PhoneNumber", c => c.String()); AddColumn("dbo.Users", "PhoneNumberConfirmed", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "TwoFactorEnabled", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "LockoutEndDateUtc", c => c.DateTime()); AddColumn("dbo.Users", "LockoutEnabled", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "AccessFailedCount", c => c.Int(nullable: false)); } public override void Down() { DropForeignKey("dbo.UserRoles", "RoleId", "dbo.AspRoles"); DropForeignKey("dbo.UserRoles", "ApplicationUser_Id", "dbo.Users"); DropForeignKey("dbo.UserLogins", "ApplicationUser_Id", "dbo.Users"); DropForeignKey("dbo.UserClaims", "ApplicationUser_Id", "dbo.Users"); DropIndex("dbo.AspRoles", "RoleNameIndex"); DropIndex("dbo.UserRoles", new[] { "ApplicationUser_Id" }); DropIndex("dbo.UserRoles", new[] { "RoleId" }); DropIndex("dbo.UserLogins", new[] { "ApplicationUser_Id" }); DropIndex("dbo.UserClaims", new[] { "ApplicationUser_Id" }); DropColumn("dbo.Users", "AccessFailedCount"); DropColumn("dbo.Users", "LockoutEnabled"); DropColumn("dbo.Users", "LockoutEndDateUtc"); DropColumn("dbo.Users", "TwoFactorEnabled"); DropColumn("dbo.Users", "PhoneNumberConfirmed"); DropColumn("dbo.Users", "PhoneNumber"); DropColumn("dbo.Users", "SecurityStamp"); DropColumn("dbo.Users", "EmailConfirmed"); DropTable("dbo.AspRoles"); DropTable("dbo.UserRoles"); DropTable("dbo.UserLogins"); DropTable("dbo.UserClaims"); } }
AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false, defaultValue:true));
Update-Database
Get-Project Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.Owin
باز از پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer کلاسهای ApplicationRoleManager، ApplicationSignInManager، ApplicationUserManager، CustomRoleStore، CustomUserStore، EmailService و SmsService را به پوشه EFServcies پروژهی Iris.ServiceLayer کپی کنید.
x.For<IIdentity>().Use(() => (HttpContext.Current != null && HttpContext.Current.User != null) ? HttpContext.Current.User.Identity : null); x.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<IrisDbContext>(); x.For<IrisDbContext>().HybridHttpOrThreadLocalScoped() .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>()); x.For<DbContext>().HybridHttpOrThreadLocalScoped() .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>()); x.For<IUserStore<ApplicationUser, int>>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>(); x.For<IRoleStore<CustomRole, int>>() .HybridHttpOrThreadLocalScoped() .Use<RoleStore<CustomRole, int, CustomUserRole>>(); x.For<IAuthenticationManager>() .Use(() => HttpContext.Current.GetOwinContext().Authentication); x.For<IApplicationSignInManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationSignInManager>(); x.For<IApplicationRoleManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationRoleManager>(); // map same interface to different concrete classes x.For<IIdentityMessageService>().Use<SmsService>(); x.For<IIdentityMessageService>().Use<IdentityEmailService>(); x.For<IApplicationUserManager>().HybridHttpOrThreadLocalScoped() .Use<ApplicationUserManager>() .Ctor<IIdentityMessageService>("smsService").Is<SmsService>() .Ctor<IIdentityMessageService>("emailService").Is<IdentityEmailService>() .Setter<IIdentityMessageService>(userManager => userManager.SmsService).Is<SmsService>() .Setter<IIdentityMessageService>(userManager => userManager.EmailService).Is<IdentityEmailService>(); x.For<ApplicationUserManager>().HybridHttpOrThreadLocalScoped() .Use(context => (ApplicationUserManager)context.GetInstance<IApplicationUserManager>()); x.For<ICustomRoleStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomRoleStore>(); x.For<ICustomUserStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>();
اگر ()HttpContext.Current.GetOwinContext شناسایی نمیشود دلیلش این است که متد GetOwinContext یک متد الحاقی است که برای استفاده از آن باید پکیج نیوگت زیر را نصب کنید:
Install-Package Microsoft.Owin.Host.SystemWeb
تغییرات Iris.Web
using System; using Iris.Servicelayer.Interfaces; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; using Owin; using StructureMap; namespace Iris.Web { public class Startup { public void Configuration(IAppBuilder app) { configureAuth(app); } private static void configureAuth(IAppBuilder app) { ObjectFactory.Container.Configure(config => { config.For<IDataProtectionProvider>() .HybridHttpOrThreadLocalScoped() .Use(() => app.GetDataProtectionProvider()); }); //ObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase(); // 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 = ObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity() } }); 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); app.CreatePerOwinContext( () => ObjectFactory.Container.GetInstance<IApplicationUserManager>()); // 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( // clientId: "", // clientSecret: ""); } } }
تا به این جای کار اگر پروژه را اجرا کنید نباید هیچ مشکلی مشاهده کنید. در بخش بعدی کدهای مربوط به کنترلرهای ورود، ثبت نام، فراموشی کلمه عبور و ... را با سیستم Identity پیاده سازی میکنیم.
تعریف موجودیتهای مورد نیاز جهت طراحی یک سیستم اعتبارسنجی
در اینجا کنترل کامل سیستم در اختیار ما است و در این حالت میتوان طراحی تمام قسمتها را از ابتدا و مطابق میل خود انجام داد. برای مثال سیستم اعتبارسنجی سادهی ما، شامل جدول کاربران و نقشهای آنها خواهد بود و این دو با هم رابطهی 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 نیز میتواند مفید باشد و به همین نحو کار میکنند.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
کتابخانه loadCSS
A function for loading CSS asynchronously
Why loadCSS?
Referencing CSS stylesheets with link[rel=stylesheet]
or @import
causes browsers to delay page rendering while a stylesheet loads. When loading stylesheets that are not critical to the initial rendering of a page, this blocking behavior is undesirable. The new <link rel="preload">
standard enables us to load stylesheets asynchronously, without blocking rendering, and loadCSS provides a JavaScript polyfill for that feature to allow it to work across browsers. Additionally, loadCSS offers a separate (and optional) JavaScript function for loading stylesheets dynamically.
npm install fg-loadcss --save