اشتراکها
اشتراکها
FireFox 18 و مصرف بالای CPU
از زمانیکه موتور پردازشی جاوا اسکریپت فایرفاکس در نگارش 18 تغییر کرده (Faster JavaScript performance via IonMonkey compiler)، این مرورگر دچار مصرف بالای CPU به صورت مداوم شده ....
نظرات مطالب
اجرای سرویسهای NodeJS در ASP.NET Core
دو مطلب زیر فلسفه وجودی Node Services و چگونگی کارکرد آنها را نشان میدهد.
و
نظرات مطالب
بررسی ساختار ویجتهای وب Kendo UI
سلام
ولی این فایل توی github نیستش. آیا این فایل برای نسخه پولی هستش ؟
شما در کدهاتون از کد زیر استفاده میکنید
<script src="js/kendo.web.min.js" type="text/javascript"></script>
این خطا یعنی اسکریپتهای اصلی به صفحه پیوست نشدهاند. برای تعریف آنها هم از ResolveClientUrl استفاده کنید:
<script type="text/javascript" src='<%: ResolveClientUrl("~/path....") %>'></script>
یعنی کلیه اسکریتها حتی اسکریت هایی مانند :
اجازهی اجرا ندارند؟ و باید در یک فایل جدا به صفحه تزریق شوند؟
<script type="text/javascript"> $(document).ready(function () { PopupForm.ShowForm({ renderFormUrl : "/postreply/renderreplyform", ..... }); }); </script>
نظرات مطالب
مروری بر قابلیت جدید ASP.NET FriendlyUrls
- برای تعریف صحیح اسکریپتها یا از متد ResolveUrl استفاده کنید:
و یا روش بهتر استفاده از اسکریپت منیجر است.
<script src='<%= ResolveUrl("~/Scripts/jquery-1.4.1.js")%>' type="text/javascript" />
نوع دیگری از کوئریهای پرکاربرد، کوئریهای مرتبط با ثبت، حذف و ویرایش اطلاعات هستند که در این قسمت آنها را بررسی میکنیم. البته این مثالها از یکسری مثال کوئریهای مرتبط با PostgreSQL، به EF-Core تبدیل و ترجمه شدهاند. به همین جهت تطابق یک به یکی در اینجا وجود نداشته و روش شیءگرایی که ORMها برای کار با دادهها بکار میگیرند، الزاما کوئریهای یکسانی را تولید نمیکنند؛ اما نتیجهی نهایی آنها یکی است.
مثال 1: افزودن ردیفی به یک جدول بانک اطلاعاتی
امکان و ویژگی جدیدی به نام SPA قرار است به مجموعه اضافه شود. اطلاعات آن که شامل موارد ذیل است، نیاز است به جدول facilities اضافه شود:
اگر قرار باشد چنین کاری را توسط دستورات SQL انجام دهیم، عموما به یکی از دو روش زیر عمل میشود:
که معادل آن در EF-Core به صورت زیر است:
ابتدا وهلهای از موجودیت Facility به DbSet مرتبط با آن اضافه میشود و در آخر SaveChanges فراخوانی خواهد شد تا کوئری متناظر با آن ساخته شده و به بانک اطلاعاتی اعمال شود.
مثال 2: افزودن چندین ردیف از اطلاعات به یک جدول بانک اطلاعاتی
همان مثال قبلی را درنظر بگیرید. اینبار میخواهیم دو ردیف را به آن اضافه کنیم:
معادل کدهای SQL چنین عملی، میتواند کوئری زیر باشد:
و روش انجام آن در EF-Core تفاوتی با مثال قبلی ندارد:
در اینجا میتوان به هر تعدادی که نیاز است وهلههای جدیدی از Facility را به context افزودن و سپس SaveChanges را در آخر کار فراخوانی کرد. اینکه EF-Core دستورات insert معادل را به یکباره و یا به صورت مجزایی اجرا میکند، به مفهومی به نام batching مرتبط است. اطلاعات بیشتر
مثال 3: افزودن اطلاعات محاسبه شده به یک جدول بانک اطلاعاتی
اطلاعات زیر را درنظر بگیرید:
در مثال اصلی عنوان شده که میخواهیم ID آنرا یکی بیشتر از ردیف قبلی ثبت کنیم. در EF-Core و تنظیمات موجودیتهایی که داریم:
چون ستون ID به صورت خود افزایش یابنده معرفی شدهاست که از صفر شروع میشود و به صورت خودکار توسط بانک اطلاعاتی یکی یکی افزایش مییابد، نیازی به حل این مساله وجود ندارد. چون ID افزایش یابنده را خود بانک اطلاعاتی محاسبه میکند. همچنین به همین علت در مثالهای قبلی نیز ID را به صورت مستقیمی مقدار دهی نکردیم. اگر نیاز به انجام چنین کاری وجود داشته باشد (ذکر صریح ID خاصی)، با توجه به طراحی بانک اطلاعاتی حاصل از این تنظیمات:
باید مانند مثال ثبت اطلاعات اولیهی در بانک اطلاعاتی در قسمت اول این سری، از روش SET IDENTITY_INSERT Facilities ON استفاده کرد تا بتوان مجوز ثبت دستی این ID کنترل شدهی توسط بانک اطلاعاتی را پیدا کرد.
مثال 4: به روز رسانی اطلاعاتی از پیش موجود
میخواهیم مقدار InitialOutlay دومین زمین تنیس را از 8000 موجود به 10000 تغییر دهیم. با توجه به اینکه ID این زمین شماره 1 است، در حالت متداول SQL نویسی، به کدهای زیر خواهیم رسید:
که معادل EF-Core آن به صورت زیر است:
این دستورات کوئری مشابهی را تولید نمیکنند. ابتدا موجودیت متناظر با ID شمارهی 1 از بانک اطلاعاتی واکشی شده و سپس مقدار خاصیتی از آن تغییر کردهاست. در آخر SaveChanges بر روی آن فراخوانی میشود.
EF-Core برای اینکه بتواند تغییرات اعمالی به یک شیء را محاسبه کند، نیاز دارد تا آن شیء را به نحوی در سیستم change tracking خودش موجود داشته باشد. هر نوع کوئری که در EF-Core نوشته میشود و به همراه متد AsNoTracking نیست، خروجی تک تک اشیاء حاصل از آن پیش از ارائهی نهایی، وارد سیستم change tracking آن میشوند. یعنی اگر مقادیر خواص این اشیاء را تغییر داده و بر روی آنها SaveChanges را فراخوانی کنیم، کوئریهای متناظر با به روز رسانی تنها این خواص تغییر یافته به صورت خودکار محاسبه شده و به بانک اطلاعاتی اعمال میشوند.
فراخوانی متد AsNoTracking بر روی کوئریهای EF-Core، تولید پروکسیهای change tracking را غیرفعال میکند. یک چنین کوئریهایی صرفا کاربردهای گزارشگیری فقط خواندنی را دارند و نسبت به کوئریهای معمولی، سریعتر و با مصرف حافظهی کمتری هستند. بنابراین نتایج حاصل از کوئریهای متداول EF-Core، به صورت پیشفرض (یعنی بدون داشتن متد AsNoTracking) هم خواندنی و هم نوشتنی با قابلیت اعمال به بانک اطلاعاتی هستند.
مثال 5: به روز رسانی چندین ردیف و چندین جدول در یک زمان
میخواهیم مقادیر MemberCost و GuestCost دو زمین تنیس را به 6 و 30 تغییر دهیم. روش انجام اینکار با SQL نویسی معمولی به صورت زیر است:
اما همانطور که عنوان شد در EF-Core ابتدا باید اشیاء متناظر با این زمینهای تنیس را در سیستم change tacking موجود داشت و سپس نسبت به ویرایش آنها اقدام نمود. یکی از روشهای وارد کردن اشیاء به سیستم change tacking، نوشتن کوئریهای بدون متد AsNoTracking است و سپس به روز رسانی نتایج حاصل از آنها که اکنون توسط پروکسیهای change tracking محصور شدهاند و در آخر فراخوانی SaveChanges بر روی context جاری:
مثال 6: به روز رسانی اطلاعات یک ردیف بر اساس اطلاعات ردیفی دیگر
میخواهیم هزینهی دومین زمین تنیس را به نحوی ویرایش کنیم که 10 درصد بیشتر از هزینهی اولین زمین تنیس باشد.
روش پیشنهادی انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
در EF-Core میتوان اشیاء متناظر با این دو زمین تنیس را ابتدا واکشی کرد، سپس تغییر داد و در نهایت ذخیره کرد:
مثال 7: حذف تمام اطلاعات یک جدول
میخواهیم تمام اطلاعات جدول bookings را حذف کنیم.
روش انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
اما ... این تک کوئری، معادلی را در EF-Core استاندارد ندارد. چون EF-Core نیاز دارد مدام تمام اطلاعات ویرایشی/حذف و به روز رسانی را در context و سیستم change tracking خودش داشته باشد، ابتدا باید توسط یک کوئری لیست اشیاء مدنظر را تهیه کرد و سپس آنرا به متد RemoveRange معرفی کرد تا حذف تک تک آنها که شامل صدها کوئری خواهد شد، صورت گیرد:
این روش سریع نیست؛ اما کار میکند!
البته هستند کتابخانههای ثالثی (^ و ^) که انجام به روز رسانی دستهای و یا حذف دستهای از رکوردها را تنها با یک کوئری SQL میسر میکنند؛ اما ... هنوز جزئی از EF استاندارد نشدهاند و مهمترین مشکل احتمالی این روشها، همگام نبودن context و سیستم change tacking، با نتیجهی حاصل از به روز رسانی یکبارهی صدها ردیف است.
مثال 8: حذف یک کاربر از جدول کاربران
میخواهیم کاربر شمارهی 37 را حذف کنیم.
روش انجام اینکار با SQL نویسی به صورت زیر است:
و در EF-Core برای انجام اینکار میتوان ابتدا شیء متناظر با کاربر 37 را از طریق یک کوئری به سیستم change tracking وارد کرد و سپس آنرا حذف نمود:
یک نکته: امکان سادهتر حذف یک ردیف با داشتن ID آن
کوئری گرفتن از بانک اطلاعاتی، یک روش وارد کردن شیءای به context و سیستم change tacking آن است. در این حالت عموما فرض بر این است که ID شیء را نمیدانیم. اما اگر این ID مانند مثال جاری از پیش مشخص بود، نیازی نیست تا ابتدا از بانک اطلاعاتی کوئری گرفت و کل شیء را در حافظه وارد کرد. در این حالت خاص میتوان با استفاده از روش زیر، این ID را وارد سیستم tracking کرد و سپس حالت آنرا به Deleted تغییر داد و در آخر آنرا ذخیره کرد:
در کدهای فوق میتوان سطر entry.State = EntityState.Deleted را با context.Remove(entry) نیز جایگزین کرد و هر دو به یک معنا هستند.
روش فوق چنین کوئریهایی را ایجاد میکند:
مثال 9: حذف بر اساس یک sub-query
میخواهیم تمام کاربرانی را که هیچگاه رزروی را انجام ندادهاند، حذف کنیم.
این مورد نیز با SQL نویسی مستقیم نیز توسط یک کوئری دستهای قابل انجام است:
اما همانطور که عنوان شد، EF-Core این نوع اعمال ویرایش دستهای را در طی یک تک کوئری پشتیبانی نمیکند. به همین جهت ابتدا آنها را توسط یک کوئری به context وارد کرده و سپس حذف میکنیم:
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
مثال 1: افزودن ردیفی به یک جدول بانک اطلاعاتی
امکان و ویژگی جدیدی به نام SPA قرار است به مجموعه اضافه شود. اطلاعات آن که شامل موارد ذیل است، نیاز است به جدول facilities اضافه شود:
facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.
insert into facilities (facid, name, membercost, guestcost, initialoutlay, monthlymaintenance) values (9, 'Spa', 20, 30, 100000, 800); -- OR insert into facilities values (9, 'Spa', 20, 30, 100000, 800);
context.Facilities.Add(new Facility { Name = "Spa", MemberCost = 20, GuestCost = 30, InitialOutlay = 100000, MonthlyMaintenance = 800 }); context.SaveChanges();
مثال 2: افزودن چندین ردیف از اطلاعات به یک جدول بانک اطلاعاتی
همان مثال قبلی را درنظر بگیرید. اینبار میخواهیم دو ردیف را به آن اضافه کنیم:
facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800. facid: 10, Name: 'Squash Court 2', membercost: 3.5, guestcost: 17.5, initialoutlay: 5000, monthlymaintenance: 80.
insert into facilities (facid, name, membercost, guestcost, initialoutlay, monthlymaintenance) values (9, 'Spa', 20, 30, 100000, 800), (10, 'Squash Court 2', 3.5, 17.5, 5000, 80);
context.Facilities.Add(new Facility { Name = "Spa", MemberCost = 20, GuestCost = 30, InitialOutlay = 100000, MonthlyMaintenance = 800 }); context.Facilities.Add(new Facility { Name = "Squash Court 2", MemberCost = 3.5M, GuestCost = 17.5M, InitialOutlay = 5000, MonthlyMaintenance = 80 }); context.SaveChanges();
مثال 3: افزودن اطلاعات محاسبه شده به یک جدول بانک اطلاعاتی
اطلاعات زیر را درنظر بگیرید:
Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.
namespace EFCorePgExercises.Entities { public class FacilityConfiguration : IEntityTypeConfiguration<Facility> { public void Configure(EntityTypeBuilder<Facility> builder) { builder.HasKey(facility => facility.FacId); builder.Property(facility => facility.FacId).IsRequired().UseIdentityColumn(seed: 0, increment: 1);
CREATE TABLE [dbo].[Facilities]( [FacId] [int] IDENTITY(0,1) NOT NULL, --- ... CONSTRAINT [PK_Facilities] PRIMARY KEY CLUSTERED ( [FacId] ASC );
مثال 4: به روز رسانی اطلاعاتی از پیش موجود
میخواهیم مقدار InitialOutlay دومین زمین تنیس را از 8000 موجود به 10000 تغییر دهیم. با توجه به اینکه ID این زمین شماره 1 است، در حالت متداول SQL نویسی، به کدهای زیر خواهیم رسید:
update facilities set initialoutlay = 10000 where facid = 1;
var facility1 = context.Facilities.Find(1); facility1.InitialOutlay = 10000; context.SaveChanges();
EF-Core برای اینکه بتواند تغییرات اعمالی به یک شیء را محاسبه کند، نیاز دارد تا آن شیء را به نحوی در سیستم change tracking خودش موجود داشته باشد. هر نوع کوئری که در EF-Core نوشته میشود و به همراه متد AsNoTracking نیست، خروجی تک تک اشیاء حاصل از آن پیش از ارائهی نهایی، وارد سیستم change tracking آن میشوند. یعنی اگر مقادیر خواص این اشیاء را تغییر داده و بر روی آنها SaveChanges را فراخوانی کنیم، کوئریهای متناظر با به روز رسانی تنها این خواص تغییر یافته به صورت خودکار محاسبه شده و به بانک اطلاعاتی اعمال میشوند.
فراخوانی متد AsNoTracking بر روی کوئریهای EF-Core، تولید پروکسیهای change tracking را غیرفعال میکند. یک چنین کوئریهایی صرفا کاربردهای گزارشگیری فقط خواندنی را دارند و نسبت به کوئریهای معمولی، سریعتر و با مصرف حافظهی کمتری هستند. بنابراین نتایج حاصل از کوئریهای متداول EF-Core، به صورت پیشفرض (یعنی بدون داشتن متد AsNoTracking) هم خواندنی و هم نوشتنی با قابلیت اعمال به بانک اطلاعاتی هستند.
مثال 5: به روز رسانی چندین ردیف و چندین جدول در یک زمان
میخواهیم مقادیر MemberCost و GuestCost دو زمین تنیس را به 6 و 30 تغییر دهیم. روش انجام اینکار با SQL نویسی معمولی به صورت زیر است:
update cd.facilities set membercost = 6, guestcost = 30 where facid in (0,1);
int[] facIds = { 0, 1 }; var tennisCourts = context.Facilities.Where(x => facIds.Contains(x.FacId)).ToList(); foreach (var tennisCourt in tennisCourts) { tennisCourt.MemberCost = 6; tennisCourt.GuestCost = 30; } context.SaveChanges();
مثال 6: به روز رسانی اطلاعات یک ردیف بر اساس اطلاعات ردیفی دیگر
میخواهیم هزینهی دومین زمین تنیس را به نحوی ویرایش کنیم که 10 درصد بیشتر از هزینهی اولین زمین تنیس باشد.
روش پیشنهادی انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
update cd.facilities facs set membercost = (select membercost * 1.1 from cd.facilities where facid = 0), guestcost = (select guestcost * 1.1 from cd.facilities where facid = 0) where facs.facid = 1;
var fac0 = context.Facilities.Where(x => x.FacId == 0).First(); var fac1 = context.Facilities.Where(x => x.FacId == 1).First(); fac1.MemberCost = fac0.MemberCost * 1.1M; fac1.GuestCost = fac0.GuestCost * 1.1M; context.SaveChanges();
میخواهیم تمام اطلاعات جدول bookings را حذف کنیم.
روش انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
delete from bookings
context.Bookings.RemoveRange(context.Bookings.ToList()); context.SaveChanges();
البته هستند کتابخانههای ثالثی (^ و ^) که انجام به روز رسانی دستهای و یا حذف دستهای از رکوردها را تنها با یک کوئری SQL میسر میکنند؛ اما ... هنوز جزئی از EF استاندارد نشدهاند و مهمترین مشکل احتمالی این روشها، همگام نبودن context و سیستم change tacking، با نتیجهی حاصل از به روز رسانی یکبارهی صدها ردیف است.
مثال 8: حذف یک کاربر از جدول کاربران
میخواهیم کاربر شمارهی 37 را حذف کنیم.
روش انجام اینکار با SQL نویسی به صورت زیر است:
delete from members where memid = 37;
var mem37 = context.Members.Where(x => x.MemId == 37).First(); context.Members.Remove(mem37); context.SaveChanges();
یک نکته: امکان سادهتر حذف یک ردیف با داشتن ID آن
کوئری گرفتن از بانک اطلاعاتی، یک روش وارد کردن شیءای به context و سیستم change tacking آن است. در این حالت عموما فرض بر این است که ID شیء را نمیدانیم. اما اگر این ID مانند مثال جاری از پیش مشخص بود، نیازی نیست تا ابتدا از بانک اطلاعاتی کوئری گرفت و کل شیء را در حافظه وارد کرد. در این حالت خاص میتوان با استفاده از روش زیر، این ID را وارد سیستم tracking کرد و سپس حالت آنرا به Deleted تغییر داد و در آخر آنرا ذخیره کرد:
var entry = context.Entry(new Member { MemId = 37 }); entry.State = EntityState.Deleted; context.SaveChanges();
روش فوق چنین کوئریهایی را ایجاد میکند:
SET NOCOUNT ON; DELETE FROM [Members] WHERE [MemId] = @p0; SELECT @@ROWCOUNT;
مثال 9: حذف بر اساس یک sub-query
میخواهیم تمام کاربرانی را که هیچگاه رزروی را انجام ندادهاند، حذف کنیم.
این مورد نیز با SQL نویسی مستقیم نیز توسط یک کوئری دستهای قابل انجام است:
delete from members where memid not in (select memid from cd.bookings);
var mems = context.Members.Where(x => !context.Bookings.Select(x => x.MemId).Contains(x.MemId)).ToList(); context.Members.RemoveRange(mems); context.SaveChanges();
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
از نگارشهای پیشین ASP.NET، هنوز هم اطلاعات شیء User مانند User.Identity.Name در ASP.NET Core نیز در دسترس هستند. به این ترتیب زمانیکه کاربری به سیستم وارد شد، دیگر نیازی نیست تا جهت یافتن Name او، از بانک اطلاعاتی کوئری گرفت. خاصیت Name یاد شده به صورت خودکار از کوکی رمزنگاری شدهی او دریافت شده و در اختیار برنامه قرار میگیرد. این Name در ASP.NET Core Identity، اصطلاحا یک User Claim پیشفرض نام دارد و به صورت خودکار ایجاد و مقدار دهی میشود. اکنون این سؤال مطرح میشود که آیا میتوان خاصیت دیگری را به شیء User.Identity اضافه کرد؟
جدول AppUserClaims
جدول AppUserClaims، جزو جداول اصلی ASP.NET Core Identity است و هدف آن ذخیرهی اطلاعات ویژهی کاربران و بازیابی سادهتر آنها از طریق کوکیهای آنها است (همانند User.Identity.Name). زمانیکه کاربری به سیستم وارد میشود، بر اساس UserId او، تمام رکوردهای User Claims متعلق به او از این جدول واکشی شده و به صورت خودکار به کوکی او اضافه میشوند.
در پروژهی DNT Identity از این جدول استفاده نمیشود. چون اطلاعات User Claims مورد نیاز آن، هم اکنون در جدول AppUsers موجود هستند. به همین جهت افزودن این نوع User Claimها به جدول AppUserClaims، به ازای هر کاربر، کاری بیهوده است. سناریویی که استفادهی از این جدول را با مفهوم میکند، ذخیره سازی تنظیمات ویژهی هرکاربر است (خارج از فیلدهای جدول کاربران). برای مثال اگر سایتی را چندزبانه طراحی کردید، میتوانید یک User Claim سفارشی جدید را برای این منظور ایجاد و زبان انتخابی کاربر را به عنوان یک رکورد جدید مخصوص آن در این جدول ویژه ثبت کنید. مزیت آن این است که واکشی و افزوده شدن اطلاعات آن به کوکی شخص، به صورت خودکار توسط فریم ورک صورت گرفته و در حین مرور صفحات توسط کاربر، دیگر نیازی نیست تا اطلاعات زبان انتخابی او را از بانک اطلاعاتی واکشی کرد.
بنابراین برای ذخیره سازی تنظیمات با کارآیی بالای ویژهی هرکاربر، جدول جدیدی را ایجاد نکنید. جدول User Claim برای همین منظور درنظر گرفته شدهاست و پردازش اطلاعات آن توسط فریم ورک صورت میگیرد.
ASP.NET Core Identity چگونه اطلاعات جدول AppUserClaims را پردازش میکند؟
ASP.NET Identity Core در حین لاگین کاربر به سیستم، از سرویس SignInManager خودش استفاده میکند که با نحوهی سفارشی سازی آن پیشتر در قسمت دوم این سری آشنا شدیم. سرویس SignInManager پس از لاگین شخص، از یک سرویس توکار دیگر این فریم ورک به نام UserClaimsPrincipalFactory جهت واکشی اطلاعات User Claims و همچنین Role Claims و افزودن آنها به کوکی رمزنگاری شدهی شخص، استفاده میکند.
بنابراین اگر قصد افزودن User Claim سفارشی دیگری را داشته باشیم، میتوان همین سرویس توکار UserClaimsPrincipalFactory را سفارشی سازی کرد (بجای اینکه الزاما رکوردی را به جدول AppUserClaims اضافه کنیم).
اطلاعات جالبی را هم میتوان از پیاده سازی متد CreateAsync آن استخراج کرد:
1) userId شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserIdClaimType به کوکی او اضافه میشود.
2) userName شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserNameClaimType به کوکی او اضافه میشود.
3) security stamp او (آخرین بار تغییر اطلاعات اکانت کاربر) نیز یک Claim پیشفرض است.
4) اگر نقشهایی به کاربر انتساب داده شده باشند، تمام این نقشها واکشی شده و به عنوان یک Claim جدید به کوکی او اضافه میشوند.
5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه میشوند. در ASP.NET Identity Core نقشها نیز میتوانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).
بنابراین حداقل مدیریت Claims این 5 مورد خودکار است و اگر برای مثال نیاز به Id کاربر لاگین شده را داشتید، نیازی نیست تا آنرا از بانک اطلاعاتی واکشی کنید. چون این اطلاعات هم اکنون در کوکی او موجود هستند.
سفارشی سازی کلاس UserClaimsPrincipalFactory جهت افزودن User Claims سفارشی
تا اینجا دریافتیم که کلاس UserClaimsPrincipalFactory کار مدیریت Claims پیشفرض این فریم ورک را برعهده دارد. در ادامه از این کلاس ارث بری کرده و متد CreateAsync آنرا جهت افزودن Claims سفارشی خود بازنویسی میکنیم. این پیاده سازی سفارشی را در کلاس ApplicationClaimsPrincipalFactory میتوانید مشاهده کنید:
در حین بازنویسی متد CreateAsync، ابتدا base.CreateAsync را فراخوانی کردهایم، تا اخلالی در عملکرد این فریم ورک رخ ندهد و هنوز هم همان مواردی که در قسمت قبل توضیح داده شد، به صورت پیش فرض به کوکی شخص اضافه شوند. سپس در متد addCustomClaims، تعدادی Claim سفارشی خاص خودمان را اضافه کردهایم.
برای مثال نام، نام خانوادگی و نام تصویر شخص به صورت Claimهایی جدید به کوکی او اضافه میشوند. در این حالت دیگر نیازی نیست تا به ازای هر کاربر، جدول AppUserClaims را ویرایش کرد و اطلاعات جدیدی را افزود و یا تغییر داد. همینقدر که کاربر به سیستم لاگین کند، شیء User او به متد Create کلاس UserClaimsPrincipalFactory ارسال میشود. به این ترتیب میتوان به تمام خواص این کاربر دسترسی یافت و در صورت نیاز آنها را به صورت Claimهایی به کوکی او افزود.
پس از تدارک کلاس ApplicationClaimsPrincipalFactory، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
یکبار ApplicationClaimsPrincipalFactory را به عنوان پیاده سازی کنندهی IUserClaimsPrincipalFactory معرفی کردهایم. همچنین یکبار هم سرویس توکار UserClaimsPrincipalFactory را به سرویس سفارشی خودمان هدایت کردهایم. به این ترتیب مطمئن خواهیم شد که همواره از ApplicationClaimsPrincipalFactory ما استفاده خواهد شد (حتی اگر UserClaimsPrincipalFactory اصلی از سیستم تزریق وابستگیها درخواست شود).
چگونه به اطلاعات User Claims در سرویسهای برنامه دسترسی پیدا کنیم؟
برای دسترسی به اطلاعات User Claims نیاز به دسترسی به HttpContext جاری را داریم. در این مورد و تزریق سرویس IHttpContextAccessor جهت تامین آن، در مطلب «بررسی روش دسترسی به HttpContext در ASP.NET Core» پیشتر بحث شدهاست.
به علاوه در کلاس IdentityServicesRegistry، تزریق وابستگیهای سفارشیتری نیز صورت گرفتهاست:
در اینجا اگر نیاز به اطلاعات Claims شیء User را داشتید، میتوانید اینترفیس IPrincipal را هم بجای IHttpContextAccessor، به سازندهی کلاس مدنظر خود تزریق کنید.
چگونه اطلاعات User Claims سفارشی را دریافت کنیم؟
برای کار سادهتر با Claims یک کلاس کمکی به نام IdentityExtensions به پروژه اضافه شدهاست و متدهایی مانند دو متد ذیل را میتوانید در آن مشاهده کنید:
در اینجا نحوهی استخراج اطلاعات را از شیء User و یا User.Identity مشاهده میکنید. تنها کافی است claimType ایی را درخواست کرده و سپس مقدار آنرا از کوکی شخص به نحو فوق واکشی کنیم.
برای نمونه متد GetUserDisplayName این کلاس کمکی، از همان Claims سفارشی که در کلاس ApplicationClaimsPrincipalFactory تعریف کردیم، اطلاعات خود را استخراج میکند و اگر در View ایی خواستید این اطلاعات را نمایش دهید، میتوانید بنویسید:
چگونه پس از ویرایش اطلاعات کاربر، اطلاعات کوکی او را نیز به روز کنیم؟
در پروژه قسمتی وجود دارد جهت ویرایش اطلاعات کاربران (UserProfileController). اگر کاربری برای مثال نام و نام خانوادگی خود را ویرایش کرد، میخواهیم بلافاصله متد GetUserDisplayName اطلاعات صحیح و به روزی را از کوکی او دریافت کند. برای اینکار یا میتوان او را وادار به لاگین مجدد کرد (تا پروسهی رسیدن به متد CreateAsync کلاس ApplicationClaimsPrincipalFactory طی شود) و یا روش بهتری نیز وجود دارد:
در اینجا تنها کافی است متد RefreshSignInAsync را مجددا بر اساس اطلاعات ویرایش شدهی کاربر، فراخوانی کنیم تا کوکی او را بلافاصله به روز کند و این روش نیازی به اجبار به لاگین مجدد کاربر را ندارد.
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.
جدول AppUserClaims
جدول AppUserClaims، جزو جداول اصلی ASP.NET Core Identity است و هدف آن ذخیرهی اطلاعات ویژهی کاربران و بازیابی سادهتر آنها از طریق کوکیهای آنها است (همانند User.Identity.Name). زمانیکه کاربری به سیستم وارد میشود، بر اساس UserId او، تمام رکوردهای User Claims متعلق به او از این جدول واکشی شده و به صورت خودکار به کوکی او اضافه میشوند.
در پروژهی DNT Identity از این جدول استفاده نمیشود. چون اطلاعات User Claims مورد نیاز آن، هم اکنون در جدول AppUsers موجود هستند. به همین جهت افزودن این نوع User Claimها به جدول AppUserClaims، به ازای هر کاربر، کاری بیهوده است. سناریویی که استفادهی از این جدول را با مفهوم میکند، ذخیره سازی تنظیمات ویژهی هرکاربر است (خارج از فیلدهای جدول کاربران). برای مثال اگر سایتی را چندزبانه طراحی کردید، میتوانید یک User Claim سفارشی جدید را برای این منظور ایجاد و زبان انتخابی کاربر را به عنوان یک رکورد جدید مخصوص آن در این جدول ویژه ثبت کنید. مزیت آن این است که واکشی و افزوده شدن اطلاعات آن به کوکی شخص، به صورت خودکار توسط فریم ورک صورت گرفته و در حین مرور صفحات توسط کاربر، دیگر نیازی نیست تا اطلاعات زبان انتخابی او را از بانک اطلاعاتی واکشی کرد.
بنابراین برای ذخیره سازی تنظیمات با کارآیی بالای ویژهی هرکاربر، جدول جدیدی را ایجاد نکنید. جدول User Claim برای همین منظور درنظر گرفته شدهاست و پردازش اطلاعات آن توسط فریم ورک صورت میگیرد.
ASP.NET Core Identity چگونه اطلاعات جدول AppUserClaims را پردازش میکند؟
ASP.NET Identity Core در حین لاگین کاربر به سیستم، از سرویس SignInManager خودش استفاده میکند که با نحوهی سفارشی سازی آن پیشتر در قسمت دوم این سری آشنا شدیم. سرویس SignInManager پس از لاگین شخص، از یک سرویس توکار دیگر این فریم ورک به نام UserClaimsPrincipalFactory جهت واکشی اطلاعات User Claims و همچنین Role Claims و افزودن آنها به کوکی رمزنگاری شدهی شخص، استفاده میکند.
بنابراین اگر قصد افزودن User Claim سفارشی دیگری را داشته باشیم، میتوان همین سرویس توکار UserClaimsPrincipalFactory را سفارشی سازی کرد (بجای اینکه الزاما رکوردی را به جدول AppUserClaims اضافه کنیم).
اطلاعات جالبی را هم میتوان از پیاده سازی متد CreateAsync آن استخراج کرد:
public virtual async Task<ClaimsPrincipal> CreateAsync(TUser user)
2) userName شخص پس از لاگین از طریق User Claims ایی با نوع Options.ClaimsIdentity.UserNameClaimType به کوکی او اضافه میشود.
3) security stamp او (آخرین بار تغییر اطلاعات اکانت کاربر) نیز یک Claim پیشفرض است.
4) اگر نقشهایی به کاربر انتساب داده شده باشند، تمام این نقشها واکشی شده و به عنوان یک Claim جدید به کوکی او اضافه میشوند.
5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه میشوند. در ASP.NET Identity Core نقشها نیز میتوانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).
بنابراین حداقل مدیریت Claims این 5 مورد خودکار است و اگر برای مثال نیاز به Id کاربر لاگین شده را داشتید، نیازی نیست تا آنرا از بانک اطلاعاتی واکشی کنید. چون این اطلاعات هم اکنون در کوکی او موجود هستند.
سفارشی سازی کلاس UserClaimsPrincipalFactory جهت افزودن User Claims سفارشی
تا اینجا دریافتیم که کلاس UserClaimsPrincipalFactory کار مدیریت Claims پیشفرض این فریم ورک را برعهده دارد. در ادامه از این کلاس ارث بری کرده و متد CreateAsync آنرا جهت افزودن Claims سفارشی خود بازنویسی میکنیم. این پیاده سازی سفارشی را در کلاس ApplicationClaimsPrincipalFactory میتوانید مشاهده کنید:
public override async Task<ClaimsPrincipal> CreateAsync(User user) { var principal = await base.CreateAsync(user).ConfigureAwait(false); addCustomClaims(user, principal); return principal; } private static void addCustomClaims(User user, IPrincipal principal) { ((ClaimsIdentity) principal.Identity).AddClaims(new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.Integer), new Claim(ClaimTypes.GivenName, user.FirstName ?? string.Empty), new Claim(ClaimTypes.Surname, user.LastName ?? string.Empty), new Claim(PhotoFileName, user.PhotoFileName ?? string.Empty, ClaimValueTypes.String), }); }
برای مثال نام، نام خانوادگی و نام تصویر شخص به صورت Claimهایی جدید به کوکی او اضافه میشوند. در این حالت دیگر نیازی نیست تا به ازای هر کاربر، جدول AppUserClaims را ویرایش کرد و اطلاعات جدیدی را افزود و یا تغییر داد. همینقدر که کاربر به سیستم لاگین کند، شیء User او به متد Create کلاس UserClaimsPrincipalFactory ارسال میشود. به این ترتیب میتوان به تمام خواص این کاربر دسترسی یافت و در صورت نیاز آنها را به صورت Claimهایی به کوکی او افزود.
پس از تدارک کلاس ApplicationClaimsPrincipalFactory، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
services.AddScoped<IUserClaimsPrincipalFactory<User>, ApplicationClaimsPrincipalFactory>(); services.AddScoped<UserClaimsPrincipalFactory<User, Role>, ApplicationClaimsPrincipalFactory>();
چگونه به اطلاعات User Claims در سرویسهای برنامه دسترسی پیدا کنیم؟
برای دسترسی به اطلاعات User Claims نیاز به دسترسی به HttpContext جاری را داریم. در این مورد و تزریق سرویس IHttpContextAccessor جهت تامین آن، در مطلب «بررسی روش دسترسی به HttpContext در ASP.NET Core» پیشتر بحث شدهاست.
به علاوه در کلاس IdentityServicesRegistry، تزریق وابستگیهای سفارشیتری نیز صورت گرفتهاست:
services.AddScoped<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>()?.HttpContext?.User ?? ClaimsPrincipal.Current);
چگونه اطلاعات User Claims سفارشی را دریافت کنیم؟
برای کار سادهتر با Claims یک کلاس کمکی به نام IdentityExtensions به پروژه اضافه شدهاست و متدهایی مانند دو متد ذیل را میتوانید در آن مشاهده کنید:
public static string FindFirstValue(this ClaimsIdentity identity, string claimType) { return identity?.FindFirst(claimType)?.Value; } public static string GetUserClaimValue(this IIdentity identity, string claimType) { var identity1 = identity as ClaimsIdentity; return identity1?.FindFirstValue(claimType); }
برای نمونه متد GetUserDisplayName این کلاس کمکی، از همان Claims سفارشی که در کلاس ApplicationClaimsPrincipalFactory تعریف کردیم، اطلاعات خود را استخراج میکند و اگر در View ایی خواستید این اطلاعات را نمایش دهید، میتوانید بنویسید:
@User.Identity.GetUserDisplayName()
چگونه پس از ویرایش اطلاعات کاربر، اطلاعات کوکی او را نیز به روز کنیم؟
در پروژه قسمتی وجود دارد جهت ویرایش اطلاعات کاربران (UserProfileController). اگر کاربری برای مثال نام و نام خانوادگی خود را ویرایش کرد، میخواهیم بلافاصله متد GetUserDisplayName اطلاعات صحیح و به روزی را از کوکی او دریافت کند. برای اینکار یا میتوان او را وادار به لاگین مجدد کرد (تا پروسهی رسیدن به متد CreateAsync کلاس ApplicationClaimsPrincipalFactory طی شود) و یا روش بهتری نیز وجود دارد:
// reflect the changes, in the current user's Identity cookie await _signInManager.RefreshSignInAsync(user).ConfigureAwait(false);
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.
Bun is a fast all-in-one JavaScript runtime
Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun
Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun
Bun is a new JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in
source on github
source on github