اشتراکها
بررسی Blazor و Razor Component
اشتراکها
کنترولر های چاق در ASP.NET MVC!
اشتراکها
آزمون A/B در ASP.NET MVC
نوع Span به همراه NET Core 2.1. ارائه شد. یکی از مهمترین مزایای آن امکان دسترسی به قسمتی از حافظه (توسط متد Split آن)، بدون ایجاد سربار کپی یا تخصیص مجدد حافظهای برای دسترسی به آن است. قدم بعدی، بسط این قابلیت به امکانات ذاتی زبان #C است؛ تحت عنوان ویژگی Ranges که امکان دسترسی مستقیم به بازهای/قسمتی از آرایهها، رشتهها و یا Spanها را میسر میکند.
معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
یعنی از آخر شروع به شمارش کرده و 1 واحد از آن کم میکنیم (این عدد 1 را بهخاطر داشته باشید) و یا اگر بخواهیم از LINQ استفاده کنیم میتوان از متد Last آن استفاده کرد:
همچنین اگر بخواهیم دومین عنصر از آخر آنرا دریافت کنیم:
نیز مجددا از آخر شروع به شمارش کرده و 2 واحد، از آن کم میکنیم (این عدد 2 را نیز بهخاطر داشته باشید).
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
این قطعه کد یعنی به دنبال ایندکس X، از آخر آرایه هستیم.
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
آرایهی فوق، 9 عضو دارد. در این حالت اولین عنصر آن با ایندکس صفر قابل دسترسی است. در همین حالت همین ایندکس را اگر از آخر محاسبه کنیم، 9 خواهد بود و همینطور برای مابقی.
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
Index یک struct و نوع جدید در C# 8.0 میباشد که در فضای نام System قرار گرفتهاست. سه سطر فوق دقیقا به یک معنا هستند و هر کدام خلاصه شده و ساده شدهی سطر قبلی است.
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
در این مثال دو نمونه کاربرد fromEnd با مقدار false و سپس true را ملاحظه میکنید. در حالتیکه Index i5 = 5 تعریف شدهاست، دسترسی به عناصر آرایه همانند قبل از ایندکس صفر و از آغاز شروع میشود و نه از ایندکس یک.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
اکنون اگر بخواهیم به بازهی مشخصی درون این آرایه دسترسی پیدا کنیم، میتوان حداقل به یکی از دو روش زیر عمل کرد:
یا میتوان برای مثال توسط LINQ، از متدهای Skip و Take آن برای جدا کردن بازهای از آرایهی numbers استفاده کرد و یا حتی میتوان از روش کپی کردن آرایهها به آرایهای جدید نیز کمک گرفت که در هر دو حالت، مراحلی که باید طی شوند قابل توجه است. با ارائهی C# 8.0، این نوع دسترسیها جزئی از قابلیتهای زبان شدهاند.
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
3..1 به معنای انتخاب بازهای از المان 2 تا المان 3 است. در اینجا به واژهی «المان» دقت کنید که معادل ایندکس آن در آرایه نیست. یعنی عدد ابتدای یک بازه دقیقا به ایندکس آن در آرایه اشاره میکند و عدد انتهای بازه، به ایندکس ماقبل آن (از این جهت که بتوان توسط 0^، انتهای بازه را مشخص کرد؛ بدون دریافت استثنای index out of range). به همین جهت به ابتدای بازه میگویند inclusive و به انتهای آن exclusive:
1^..1 به معنای انتخاب بازهای از المان 2، تا المان یکی مانده به آخر است:
همچنین [0^..0] نیز به معنای کل بازه است.
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
توسط range expression میتوان به صورت زیر بازنویسی کرد:
بنابراین همانطور که مشاهده میکنید، ذکر بازهی 4..1 به صورت حلقهی for (int i = 1; i < 4; i++) تفسیر میشود و نه حلقهی for (int i = 1; i <= 4; i++)
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
Range نیز یک struct و نوع جدید در C# 8.0 میباشد که در فضای نام System قرار گرفتهاست. سه سطر فوق دقیقا به یک معنا هستند و هر کدام خلاصه شده و ساده شدهی سطر قبلی است.
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
همانطور که ملاحظه میکنید، Range را میتوان به عنوان پارامتر متدها نیز استفاده و بر روی آرایهها اعمال کرد؛ اما با <List<T سازگار نیست.
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
چند مثال دیگر:
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
در این مثال، آرایهی اصلی را ابتدا تبدیل به یک Span کردهایم و سپس بازهای از روی آن انتخاب شدهاست. به همین جهت است که زمانیکه از ref locals برای تغییر عضوی از این بازه استفاده میشود، این تغییر بر روی آرایهی اصلی نیز تاثیر میگذارد.
معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
var integerArray = new int[3]; var lastItem = integerArray[integerArray.Length - 1];
var integerList = integerArray.ToList(); integerList.Last();
var secondToLast = integerArray[integerArray.Length - 2];
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
var lastItem = integerArray[^1];
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
var words = new string[] { // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
Console.WriteLine($"The last word is {words[words.Length - 1]}");
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var index = new Index(value: 1, fromEnd: true); Index indexStruct = ^1; var indexShortHand = ^1;
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Console.WriteLine(a[a.Length – 2]); // will write 8 on console. Console.WriteLine(a[^2]); // will write 8 on console. Index i5 = 5; Console.WriteLine(a[i5]); //will write 5 on console. Index i2fromEnd = ^2; Console.WriteLine(a[i2fromEnd]); // will write 8 on console.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
var numbers = Enumerable.Range(1, 10).ToArray();
var (start, end) = (1, 7); var length = end - start; // Using LINQ var subset1 = numbers.Skip(start).Take(length); // Or using Array.Copy var subset2 = new int[length]; Array.Copy(numbers, start, subset2, 0, length);
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" };
var fromIndexToX = myArray[1..3]; // = [Item2, Item3]
var fromIndexToXFromTheEnd = myArray[1..^1]; // = [ "Item2", "Item3", "Item4" ]
ذکر انتهای بازه اجباری نیست و اگر ذکر نشود به معنای 0^ است. برای مثال ..1 به معنای انتخاب بازهای از المان 2، تا آخرین المان است:
var fromAnIndexToTheEnd = myArray[1..]; // = [ "Item2", "Item3", "Item4", "Item5" ]
ذکر ابتدای بازه نیز اجباری نیست و اگر ذکر نشود به معنای عدد صفر است. برای مثال 3.. به معنای انتخاب بازهای از اولین المان، تا سومین المان است:
var fromTheStartToAnIndex = myArray[..3]; // = [ "Item1", "Item2", "Item3" ]
اگر ابتدا و انتهای بازه ذکر نشوند، کل بازه و تمام عناصر آن بازگشت داده میشوند:
var entireRange = myArray[..]; // = [ "Item1", "Item2", "Item3", "Item4", "Item5" ]
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" }; for (int i = 1; i <= 3; i++) { Console.WriteLine(myArray[i]); }
foreach (var item in myArray[1..4]) // = [ "Item2", "Item3", "Item4" ] { Console.WriteLine(item); }
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Main() { var skip2CharactersAndTake2Characters = _numbers[2..4]; // صرفنظر کردن از دو عنصر اول و سپس انتخاب دو عنصر var skipFirstAndLastCharacter = _numbers[1..^1]; // صرفنظر کردن از دو عنصر اول و آخر var last3Characters = _numbers[^3..]; // انتخاب بازهای شامل سه عنصر آخر var first4Characters = _numbers[0..4]; // دریافت بازهای از 4 عنصر اول var rangeStartFrom2 = _numbers[2..]; // دریافت بازهای شروع شده از المان دوم تا آخر var skipLast3Characters = _numbers[..^3]; // صرفنظر کردن از سه المان آخر var rangeAll = _numbers[..]; // انتخاب کل بازه } } }
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var range = new Range(1, 4); Range rangeStruct = 1..4; var rangeShortHand = 1..4;
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Print(Range range) => Console.WriteLine($"{range} => {string.Join(", ", _numbers[range])}"); static void Main() { Print(1..3); // 1..3 => 2, 3 Print(..3); // 0..3 => 1, 2, 3 Print(3..); // 3..^0 => 4, 5, 6, 7, 8, 9, 10 Print(1..^1); // 1..^1 => 2, 3, 4, 5, 6, 7, 8, 9 Print(^2..^1); // ^2..^1 => 9 } } }
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
Console.WriteLine("123456789"[1..4]); // Would output 234
var helloWorldStr = "Hello, World!"; var hello = helloWorldStr[..5]; Console.WriteLine(hello); // Output: Hello var world = helloWorldStr[7..]; Console.WriteLine(world); // Output: World! var world2 = helloWorldStr[^6..]; // Take the last 6 characters Console.WriteLine(world); // Output: World!
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
var arr = (new[] { 1, 4, 8, 11, 19, 31 }).AsSpan(); var range = arr[2..5]; ref int elt1 = ref range[1]; elt1 = -1; int copiedElement = range[2]; copiedElement = -2; Console.WriteLine($"range[1]: {range[1]}, range[2]: {range[2]}"); // output: range[1]: -1, range[2]: 19 Console.WriteLine($"arr[3]: {arr[3]}, arr[4]: {arr[4]}"); // output: arr[3]: -1, arr[4]: 19
همیشه نمیتوان کاربران را وادار به استفادهی از صفحهی لاگین برنامهی IDP کرد. ممکن است کاربران بخواهند توسط سطوح دسترسی خود در یک شبکهی ویندوزی به سیستم وارد شوند و یا از Social identity providers مانند تلگرام، گوگل، فیسبوک، توئیتر و امثال آنها برای ورود به سیستم استفاده کنند. برای مثال شاید کاربری بخواهد توسط اکانت گوگل خود به سیستم وارد شود. همچنین مباحث two-factor authentication را نیز باید مدنظر داشت؛ برای مثال ارسال یک کد موقت از طریق ایمیل و یا SMS و ترکیب آن با روش فعلی ورود به سیستم جهت بالا بردن میزان امنیت برنامه.
در این مطلب نحوهی یکپارچه سازی Windows Authentication دومینهای ویندوزی را با IdentityServer بررسی میکنیم.
کار با تامین کنندههای هویت خارجی
اغلب کاربران، دارای اکانت ثبت شدهای در جای دیگری نیز هستند و شاید آنچنان نسبت به ایجاد اکانت جدیدی در IDP ما رضایت نداشته باشند. برای چنین حالتی، امکان یکپارچه سازی IdentityServer با انواع و اقسام IDPهای دیگر نیز پیش بینی شدهاست. در اینجا تمام اینها، روشهای مختلفی برای ورود به سیستم، توسط یک کاربر هستند. کاربر ممکن است توسط اکانت خود در شبکهی ویندوزی به سیستم وارد شود و یا توسط اکانت خود در گوگل، اما در نهایت از دیدگاه سیستم ما، یک کاربر مشخص بیشتر نیست.
نگاهی به شیوهی پشتیبانی از تامین کنندههای هویت خارجی توسط Quick Start UI
Quick Start UI ای را که در «قسمت چهارم - نصب و راه اندازی IdentityServer» به IDP اضافه کردیم، دارای کدهای کار با تامین کنندههای هویت خارجی نیز میباشد. برای بررسی آن، کنترلر DNT.IDP\Controllers\Account\ExternalController.cs را باز کنید:
زمانیکه کاربر بر روی یکی از تامین کنندههای لاگین خارجی در صفحهی لاگین کلیک میکند، اکشن Challenge، نام provider مدنظر را دریافت کرده و پس از آن returnUrl را به اکشن متد Callback به صورت query string ارسال میکند. اینجا است که کاربر به تامین کنندهی هویت خارجی مانند گوگل منتقل میشود. البته مدیریت حالت Windows Authentication و استفاده از اکانت ویندوزی در اینجا متفاوت است؛ از این جهت که از returnUrl پشتیبانی نمیکند. در اینجا اطلاعات کاربر از اکانت ویندوزی او به صورت خودکار استخراج شده و به لیست Claims او اضافه میشود. سپس یک کوکی رمزنگاری شده از این اطلاعات تولید میشود تا در ادامه از محتویات آن استفاده شود.
در اکشن متد Callback، اطلاعات کاربر از کوکی رمزنگاری شدهی متد Challenge استخراج میشود و بر اساس آن هویت کاربر در سطح IDP شکل میگیرد.
فعالسازی Windows Authentication برای ورود به IDP
در ادامه میخواهیم برنامه را جهت استفادهی از اکانت ویندوزی کاربران جهت ورود به IDP تنظیم کنیم. برای این منظور باید نکات مطلب «فعالسازی Windows Authentication در برنامههای ASP.NET Core 2.0» را پیشتر مطالعه کرده باشید.
پس از فعالسازی Windows Authentication در برنامه، اگر برنامهی IDP را توسط IIS و یا IIS Express و یا HttpSys اجرا کنید، دکمهی جدید Windows را در قسمت External Login مشاهده خواهید کرد:
یک نکته: برچسب این دکمه را در حالت استفادهی از مشتقات IIS، به صورت زیر میتوان تغییر داد:
اتصال کاربر وارد شدهی از یک تامین کنندهی هویت خارجی به کاربران بانک اطلاعاتی برنامه
سازندهی کنترلر DNT.IDP\Controllers\Account\ExternalController.cs نیز همانند کنترلر Account که آنرا در قسمت قبل تغییر دادیم، از TestUserStore استفاده میکند:
بنابراین در ابتدا آنرا با IUsersService تعویض خواهیم کرد:
و سپس تمام ارجاعات قبلی به users_ را نیز توسط امکانات این سرویس اصلاح میکنیم:
الف) در متد FindUserFromExternalProvider
سطر قدیمی
به این صورت تغییر میکند:
در این حالت امضای این متد نیز باید اصلاح شود تا async شده و همچنین User را بجای TestUser بازگشت دهد:
ب) متد AutoProvisionUser قبلی
نیز باید حذف شود؛ زیرا در ادامه آنرا با صفحهی ثبت نام کاربر، جایگزین میکنیم.
مفهوم «Provisioning a user» در اینجا به معنای درخواست از کاربر، جهت ورود اطلاعاتی مانند نام و نام خانوادگی او است که پیشتر صفحهی ثبت کاربر جدید را برای این منظور در قسمت قبل ایجاد کردهایم و از آن میشود در اینجا استفادهی مجدد کرد. بنابراین در ادامه، گردش کاری ورود کاربر از طریق تامین کنندهی هویت خارجی را به نحوی اصلاح میکنیم که کاربر جدید، ابتدا به صفحهی ثبت نام وارد شود و اطلاعات تکمیلی خود را وارد کند؛ سپس به صورت خودکار به متد Callback بازگشته و ادامهی مراحل را طی نماید:
در اکشن متد نمایش صفحهی ثبت نام کاربر جدید، متد RegisterUser تنها آدرس بازگشت به صفحهی قبلی را دریافت میکند:
اکنون نیاز است اطلاعات Provider و ProviderUserId را نیز در اینجا دریافت کرد. به همین جهت ViewModel زیر را به برنامه اضافه میکنیم:
سپس با داشتن اطلاعات FindUserFromExternalProvider که آنرا در قسمت الف اصلاح کردیم، اگر خروجی آن null باشد، یعنی کاربری که از سمت تامین کنندهی هویت خارجی به برنامهی ما وارد شدهاست، دارای اکانتی در سمت IDP نیست. به همین جهت او را به صفحهی ثبت نام کاربر هدایت میکنیم. همچنین پس از پایان کار ثبت نام نیاز است مجددا به همینجا، یعنی متد Callback که فراخوان FindUserFromExternalProvider است، بازگشت:
در اینجا نحوهی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحهی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده میکنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره میکند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد.
در ادامه نیاز است امضای متد نمایش صفحهی ثبت نام را نیز بر این اساس اصلاح کنیم:
به این ترتیب اطلاعات provider نیز علاوه بر ReturnUrl در اختیار View آن قرار خواهد گرفت. البته RegisterUserViewModel هنوز شامل این خواص اضافی نیست. به همین جهت با ارث بری از RegistrationInputModel، این خواص در اختیار RegisterUserViewModel نیز قرار میگیرند:
اکنون نیاز است RegisterUser.cshtml را اصلاح کنیم:
- ابتدا دو فیلد مخفی دیگر Provider و ProviderUserId را نیز به این فرم اضافه میکنیم؛ از این جهت که در حین postback به سمت سرور به مقادیر آنها نیاز داریم:
- با توجه به اینکه کاربر از طریق یک تامین کنندهی هویت خارجی وارد شدهاست، دیگر نیازی به ورود کلمهی عبور ندارد. به همین جهت خاصیت آنرا در ViewModel مربوطه به صورت Required تعریف نکردهایم:
مابقی این فرم ثبت نام مانند قبل خواهد بود.
پس از آن نیاز است اطلاعات اکانت خارجی این کاربر را در حین postback و ارسال اطلاعات به اکشن متد RegisterUser، ثبت کنیم:
که اینکار را با مقدار دهی UserLogins کاربر در حال ثبت، انجام دادهایم.
همچنین در ادامهی این اکشن متد، کار لاگین خودکار کاربر نیز انجام میشود. با توجه به اینکه پس از ثبت اطلاعات کاربر نیاز است مجددا گردش کاری اکشن متد Callback طی شود، این لاگین خودکار را نیز برای حالت ورود از طریق تامین کنندهی خارجی، غیرفعال میکنیم:
بررسی ورود به سیستم توسط دکمهی External Login -> Windows
پس از این تغییرات، اکنون در حین ورود به سیستم (تصویر ابتدای بحث در قسمت فعالسازی اعتبارسنجی ویندوزی)، گزینهی External Login -> Windows را انتخاب میکنیم. بلافاصله به صفحهی ثبتنام کاربر هدایت خواهیم شد:
همانطور که مشاهده میکنید، IDP اکانت ویندوزی جاری را تشخیص داده و فعال کردهاست. همچنین در اینجا خبری از ورود کلمهی عبور هم نیست.
پس از تکمیل این فرم، بلافاصله کار ثبت اطلاعات کاربر و هدایت خودکار به برنامهی MVC Client انجام میشود.
در ادامه از برنامهی کلاینت logout کنید. اکنون در صفحهی login مجددا بر روی دکمهی Windows کلیک نمائید. اینبار بدون پرسیدن سؤالی، لاگین شده و وارد برنامهی کلاینت خواهید شد؛ چون پیشتر کار اتصال اکانت ویندوزی به اکانتی در سمت IDP انجام شدهاست.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
یک نکته: برای آزمایش برنامه جهت فعالسازی Windows Authentication بهتر است برنامهی IDP را توسط IIS Express اجرا کنید و یا اگر از IIS Express استفاده نمیکنید، نیاز است UseHttpSys فایل program.cs را مطابق توضیحات «یک نکتهی تکمیلی: UseHttpSys و استفادهی از HTTPS» فعال کنید.
در این مطلب نحوهی یکپارچه سازی Windows Authentication دومینهای ویندوزی را با IdentityServer بررسی میکنیم.
کار با تامین کنندههای هویت خارجی
اغلب کاربران، دارای اکانت ثبت شدهای در جای دیگری نیز هستند و شاید آنچنان نسبت به ایجاد اکانت جدیدی در IDP ما رضایت نداشته باشند. برای چنین حالتی، امکان یکپارچه سازی IdentityServer با انواع و اقسام IDPهای دیگر نیز پیش بینی شدهاست. در اینجا تمام اینها، روشهای مختلفی برای ورود به سیستم، توسط یک کاربر هستند. کاربر ممکن است توسط اکانت خود در شبکهی ویندوزی به سیستم وارد شود و یا توسط اکانت خود در گوگل، اما در نهایت از دیدگاه سیستم ما، یک کاربر مشخص بیشتر نیست.
نگاهی به شیوهی پشتیبانی از تامین کنندههای هویت خارجی توسط Quick Start UI
Quick Start UI ای را که در «قسمت چهارم - نصب و راه اندازی IdentityServer» به IDP اضافه کردیم، دارای کدهای کار با تامین کنندههای هویت خارجی نیز میباشد. برای بررسی آن، کنترلر DNT.IDP\Controllers\Account\ExternalController.cs را باز کنید:
[HttpGet] public async Task<IActionResult> Challenge(string provider, string returnUrl) [HttpGet] public async Task<IActionResult> Callback()
در اکشن متد Callback، اطلاعات کاربر از کوکی رمزنگاری شدهی متد Challenge استخراج میشود و بر اساس آن هویت کاربر در سطح IDP شکل میگیرد.
فعالسازی Windows Authentication برای ورود به IDP
در ادامه میخواهیم برنامه را جهت استفادهی از اکانت ویندوزی کاربران جهت ورود به IDP تنظیم کنیم. برای این منظور باید نکات مطلب «فعالسازی Windows Authentication در برنامههای ASP.NET Core 2.0» را پیشتر مطالعه کرده باشید.
پس از فعالسازی Windows Authentication در برنامه، اگر برنامهی IDP را توسط IIS و یا IIS Express و یا HttpSys اجرا کنید، دکمهی جدید Windows را در قسمت External Login مشاهده خواهید کرد:
یک نکته: برچسب این دکمه را در حالت استفادهی از مشتقات IIS، به صورت زیر میتوان تغییر داد:
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Configure<IISOptions>(iis => { iis.AuthenticationDisplayName = "Windows Account"; iis.AutomaticAuthentication = false; });
اتصال کاربر وارد شدهی از یک تامین کنندهی هویت خارجی به کاربران بانک اطلاعاتی برنامه
سازندهی کنترلر DNT.IDP\Controllers\Account\ExternalController.cs نیز همانند کنترلر Account که آنرا در قسمت قبل تغییر دادیم، از TestUserStore استفاده میکند:
public ExternalController( IIdentityServerInteractionService interaction, IClientStore clientStore, IEventService events, TestUserStore users = null) { _users = users ?? new TestUserStore(TestUsers.Users); _interaction = interaction; _clientStore = clientStore; _events = events; }
private readonly IUsersService _usersService; public ExternalController( // ... IUsersService usersService) { // ... _usersService = usersService; }
الف) در متد FindUserFromExternalProvider
سطر قدیمی
var user = _users.FindByExternalProvider(provider, providerUserId);
var user = await _usersService.GetUserByProviderAsync(provider, providerUserId);
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)> FindUserFromExternalProvider(AuthenticateResult result)
private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable<Claim> claims) { var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); return user; }
مفهوم «Provisioning a user» در اینجا به معنای درخواست از کاربر، جهت ورود اطلاعاتی مانند نام و نام خانوادگی او است که پیشتر صفحهی ثبت کاربر جدید را برای این منظور در قسمت قبل ایجاد کردهایم و از آن میشود در اینجا استفادهی مجدد کرد. بنابراین در ادامه، گردش کاری ورود کاربر از طریق تامین کنندهی هویت خارجی را به نحوی اصلاح میکنیم که کاربر جدید، ابتدا به صفحهی ثبت نام وارد شود و اطلاعات تکمیلی خود را وارد کند؛ سپس به صورت خودکار به متد Callback بازگشته و ادامهی مراحل را طی نماید:
در اکشن متد نمایش صفحهی ثبت نام کاربر جدید، متد RegisterUser تنها آدرس بازگشت به صفحهی قبلی را دریافت میکند:
[HttpGet] public IActionResult RegisterUser(string returnUrl)
namespace DNT.IDP.Controllers.UserRegistration { public class RegistrationInputModel { public string ReturnUrl { get; set; } public string Provider { get; set; } public string ProviderUserId { get; set; } public bool IsProvisioningFromExternal => !string.IsNullOrWhiteSpace(Provider); } }
namespace DNT.IDP.Controllers.Account { [SecurityHeaders] [AllowAnonymous] public class ExternalController : Controller { public async Task<IActionResult> Callback() { var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result); if (user == null) { // user = AutoProvisionUser(provider, providerUserId, claims); var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl }); var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" , new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId }); return Redirect(continueWithUrl); }
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره میکند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد.
در ادامه نیاز است امضای متد نمایش صفحهی ثبت نام را نیز بر این اساس اصلاح کنیم:
namespace DNT.IDP.Controllers.UserRegistration { public class UserRegistrationController : Controller { [HttpGet] public IActionResult RegisterUser(RegistrationInputModel registrationInputModel) { var vm = new RegisterUserViewModel { ReturnUrl = registrationInputModel.ReturnUrl, Provider = registrationInputModel.Provider, ProviderUserId = registrationInputModel.ProviderUserId }; return View(vm); }
namespace DNT.IDP.Controllers.UserRegistration { public class RegisterUserViewModel : RegistrationInputModel {
اکنون نیاز است RegisterUser.cshtml را اصلاح کنیم:
- ابتدا دو فیلد مخفی دیگر Provider و ProviderUserId را نیز به این فرم اضافه میکنیم؛ از این جهت که در حین postback به سمت سرور به مقادیر آنها نیاز داریم:
<inputtype="hidden"asp-for="ReturnUrl"/> <inputtype="hidden"asp-for="Provider"/> <inputtype="hidden"asp-for="ProviderUserId"/>
@if (!Model.IsProvisioningFromExternal) { <div> <label asp-for="Password"></label> <input type="password" placeholder="Password" asp-for="Password" autocomplete="off"> </div> }
پس از آن نیاز است اطلاعات اکانت خارجی این کاربر را در حین postback و ارسال اطلاعات به اکشن متد RegisterUser، ثبت کنیم:
namespace DNT.IDP.Controllers.UserRegistration { public class UserRegistrationController : Controller { [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> RegisterUser(RegisterUserViewModel model) { // ... if (model.IsProvisioningFromExternal) { userToCreate.UserLogins.Add(new UserLogin { LoginProvider = model.Provider, ProviderKey = model.ProviderUserId }); } // add it through the repository await _usersService.AddUserAsync(userToCreate); // ... } }
همچنین در ادامهی این اکشن متد، کار لاگین خودکار کاربر نیز انجام میشود. با توجه به اینکه پس از ثبت اطلاعات کاربر نیاز است مجددا گردش کاری اکشن متد Callback طی شود، این لاگین خودکار را نیز برای حالت ورود از طریق تامین کنندهی خارجی، غیرفعال میکنیم:
if (!model.IsProvisioningFromExternal) { // log the user in // issue authentication cookie with subject ID and username var props = new AuthenticationProperties { IsPersistent = false, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; await HttpContext.SignInAsync(userToCreate.SubjectId, userToCreate.Username, props); }
بررسی ورود به سیستم توسط دکمهی External Login -> Windows
پس از این تغییرات، اکنون در حین ورود به سیستم (تصویر ابتدای بحث در قسمت فعالسازی اعتبارسنجی ویندوزی)، گزینهی External Login -> Windows را انتخاب میکنیم. بلافاصله به صفحهی ثبتنام کاربر هدایت خواهیم شد:
همانطور که مشاهده میکنید، IDP اکانت ویندوزی جاری را تشخیص داده و فعال کردهاست. همچنین در اینجا خبری از ورود کلمهی عبور هم نیست.
پس از تکمیل این فرم، بلافاصله کار ثبت اطلاعات کاربر و هدایت خودکار به برنامهی MVC Client انجام میشود.
در ادامه از برنامهی کلاینت logout کنید. اکنون در صفحهی login مجددا بر روی دکمهی Windows کلیک نمائید. اینبار بدون پرسیدن سؤالی، لاگین شده و وارد برنامهی کلاینت خواهید شد؛ چون پیشتر کار اتصال اکانت ویندوزی به اکانتی در سمت IDP انجام شدهاست.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.
یک نکته: برای آزمایش برنامه جهت فعالسازی Windows Authentication بهتر است برنامهی IDP را توسط IIS Express اجرا کنید و یا اگر از IIS Express استفاده نمیکنید، نیاز است UseHttpSys فایل program.cs را مطابق توضیحات «یک نکتهی تکمیلی: UseHttpSys و استفادهی از HTTPS» فعال کنید.
اشتراکها
بررسی جزئیات Entity Framework Core
This talk covers:
- What has changed and what remains the same compared to EF6
- EF Migrations: from powershell to the new dnx commands
- Configuration and Initialisation using Dependency Injection.
- DbContext lifecycle management
- How to hack / override EF behaviour.
- Is it ready for Production use? My suggestions for the team
اشتراکها