intern pool جدول نگهداری رشتهها در داتنت
انواع ارجاعی
قبلا در مورد مقادیر ارجاعی صحبت کردیم. در اینجا نیز به این موضوع اشاره میکنیم که هر مقدار ارجاعی، نمونهای ایجاد شده از یک نوع ارجاعی میباشد. انواع ارجاعی در واقع ساختارهایی هستند که جهت گروه بندی دادهها و عملکرد بین آنها استفاده میشوند. در سایر زبانهای برنامه نویسی شیء گرا، به انواع ارجاعی، کلاس و به مقادیر ارجاعی، شیء میگویند. در جاوا اسکریپت نیز، به مقادیر ارجاعی و یا نمونههای ایجاد شده از انواع ارجاعی، شیء میگویند. به انواع ارجاعی، توصیف گر شیء نیز میگویند؛ زیرا ویژگیها و متدهای آن شیء را معرفی و توصیف مینماید.
نحوهی ایجاد شیء از نوع ارجاعی Object
از آنجاییکه نوع ارجاعی Object هیچ ویژگی و متد خاصی ندارد، متداولترین نوع ارجاعی جهت ایجاد اشیاء سفارشی میباشد. به دو روش میتوان نمونهای را از یک Object ایجاد نمود. روش اول استفاده از عملگر new و بصورت زیر میباشد:
var person = new Object (); person . name = "Meysam" ; person . age = 32 ;
با استفاده از عملگر new، شیء person را
از نوع Object
ایجاد نمودیم که شامل دو ویژگی (Property) به
نامهای name و age میباشد. در واقع شیء person
ساختاری را جهت نگهداری اطلاعات یک شخص معرفی میکند. این عمل موجب جلوگیری از
پراکندگی تعریف متغیرها و گروه بندی آنها در قالب یک شیء میشود. روش دوم استفاده
از Object Literal Notation یا
نماد تحت الفظی شیء و بصورت زیر میباشد:
var person = { name : "Meysam" , age : 32 };
Object Literal Notation ،
دستور میانبری برای ایجاد یک شیء از نوع Object میباشد. در مثال فوق هم، همانند
روش اول، شیء person را
با دو ویژگی name و age ایجاد
نمودهایم. در این روش، نام ویژگیها میتوانند به
صورت رشتهای و عددی نیز به یک شیء اختصاص یابد.
var person = { "name" : "Meysam" , "age" : 32 };
معمولا از دو روش فوق زمانی استفاده میشود که میخواهیم اشیایی را ایجاد نماییم که ویژگیهای آنها فقط خواندنی باشند. با استفاده
از روش دوم، حتی میتوان یک شیء خالی را ایجاد نمود که در ابتدا هیچ ویژگی ای
ندارد و در مراحل بعد، ویژگیهایی را به آن
اضافه نمود.
var person = {}; // var person = new Object(); person . name = "Meysam" ; person . age = 32 ;
در مثال فوق شیء person بدون ویژگیها تعریف شده است؛ سپس به آن ویژگیهایی را اضافه نمودهایم.
استفاده از روش Object Literal Notation ، یکی از روشهای محبوب برنامه نویسان جاوا اسکریپت میباشد. زیرا با کمترین کد و بصورت بصری، شیء ای را ایجاد نموده و مثلا به یک متد ارسال مینمایند. به مثال زیر توجه کنید:
function displayInfo ( arg ) { var output = "" ; if ( arg . name != undefined ) output += "Name: " + arg . name + "\n" ; if ( arg . age != undefined ) output += "Age: " + arg . age + "\n" ; return output ; } alert (displayInfo ({ name : "Meysam" , age : 32 })); alert (displayInfo ({ name : "Sohrab" }));
در این مثال یک تابع تعریف شده است که آرگومان
ورودی آن یک شیء میباشد. در تابع بررسی میشود که اگر ویژگی name و یا age
برای این آرگومان تعریف شده بود، خروجی تابع را تولید نماید. در واقع این ویژگیها اختیاری میباشند و میتوانند ارسال نگردند. در زمان فراخوانی تابع نیز شیء ای را
بصورت Object Literal Notation
ایجاد نموده و به تابع ارسال کردیم.
این الگو برای ارسال آرگومان، زمانی استفاده میشود که تعداد زیادی آرگومان اختیاری داریم و میخواهیم به تابع ارسال نماییم. معمولا کار با آرگومانهای نامی (Named Arguments) راحتتر است ولی زمانیکه تعداد آرگومانهای اختیاری زیاد باشند، مدیریت و نگهداری آنها سخت و طاقت فرسا میگردد و ظاهر زشتی را به تابع میدهد. بهترین راهکار جهت مدیریت چنین شرایطی این است که آرگومانهای اجباری را به صورت آرگومانهای نامی تعریف کنیم و آرگومانهای اختیاری را به صورت یک شیء به تابع ارسال کنیم.
نکتهی دیگری که باید به آن توجه نمود این است که جهت دسترسی به ویژگیهای یک شیء از (.) استفاده میشود. همچنین میتوان به یک ویژگی با استفاده از [] و بصورت یک آرایه دسترسی داشت که در این صورت نام ویژگی بصورت رشتهای در [] ذکر خواهد شد.
alert ( person . name ); alert ( person [ "name" ]);
در عمل تفاوتی بین دو مورد فوق وجود ندارد. مهمترین
مزیت استفاده از [] این است که میتوانید توسط یک متغیر به ویژگیهای یک شیء دسترسی
داشته باشید. همچنین اگر نام ویژگی شامل کاراکترهایی باشد که خطای گرامری رخ میدهد یا از اسامی رزرو شده استفاده کرده باشید، میتوانید از [] جهت دسترسی به
ویژگی استفاده نمایید.
var propertyName = "name" ; alert ( person [ propertyName ]); alert ( person [ "first name" ]);
در دستور alert اول، با استفاده از یک متغیر به ویژگی name از شیء person دسترسی پیدا نمودیم. در دستور آخر نیز، به دلیل وجود space در نام ویژگی، مجبور هستیم جهت دسترسی به ویژگی first name از [] استفاده نماییم.
بررسی نوع ارجاعی Function
توابع در واقع یک شیء و نمونهای از نوع ارجاعی Function میباشند که میتوانند همانند سایر اشیاء ویژگیها و متدهای خاص خود را داشته باشند. بنابراین میتوان در یک عبارت، تابعی را به یک شیء نسبت داد. به مثال زیر توجه کنید:
var sum = function ( a , b ) { return a + b ; }; alert ( sum ( 10 , 20 ));
خروجی :
30
شیء sum را تعریف نموده و یک تابع را به آن اختصاص دادیم که شامل دو آرگومان ورودی میباشد. حال میتوان با شیء sum همانند یک تابع رفتار نمود و تابع مورد نظر را فراخوانی کرد. توجه داشته باشید که هیچ نامی را در زمان تعریف تابع به آن اختصاص ندادهایم. به این شکل تعریف تابع، Function Expression میگویند.
همانند سایر اشیاء، نام تابع نیز اشارهگری به تابع میباشد. بنابراین میتوان توابع را نیز به یکدیگر نسبت داد. به مثال زیر توجه کنید:
function sum ( a , b ) { return a + b ; } alert ( sum ( 10 , 10 )); var anotherSum = sum ; alert ( anotherSum ( 10 , 10 )); sum = null ; alert ( anotherSum ( 10 , 10 )); alert ( sum ( 10 , 10 )); // Error: Object is not a function;
خروجی :
20
20
20
Error: Object is not a function
ابتدا تابعی را به نام sum ایجاد نمودیم که دو عدد را با هم جمع میکند. دقت داشته باشید که به این شکل تعریف تابع sum ، اعلان تابع یا Function Declaration میگویند. سپس متغیری را به نام anotherSum ، تعریف نموده و sum را به آن نسبت دادیم. توجه داشته باشید که در زمان انتساب یک تابع به یک متغیر نباید () را ذکر کنیم، زیرا ذکر () به منزلهی فراخوانی تابع و اختصاص خروجی آن به متغیر میباشد و نه انتساب اشاره گر تابع به آن متغیر. فراخوانی sum و anotherSum خروجی یکسانی را دارند؛ زیرا هر دو به یک تابع اشاره میکنند. در خطوط بعدی، شیء sum را با مقدار null تنظیم نمودیم. در واقع با این کار اشارهگر sum برابر null شده است؛ یعنی دیگر به هیچ تابعی اشاره نمیکند. ولی تابع همچنان در حافظه وجود دارد؛ زیرا اشارهگر دیگری به نام anotherSum در حال اشاره نمودن به آن میباشد. در مرحلهی بعدی که sum را فراخوانی نمودیم، به دلیل null بودن آن، خطا رخ خواهد داد.
بازنگری مجدد به مبحث Overloading
در اینجا فقط میخواهیم اشارهای کنیم به مبحث Overloading که قبلا در مورد آن بحث کردیم تا دلیل فنی عدم وجود Overloading را در جاوا اسکریپت درک کنیم. همانطور که قبلا بیان شد، نام تابع در واقع اشاره گری به تابع میباشد؛ بنابراین تعریف دو تابع هم نام، همانند اختصاص مجدد مقدار یا تغییر مقدار یک متغیر میباشد. به مثال زیر توجه کنید:
function calc ( num1 , num2 ) { return num1 + num2 ; } function calc ( num1 , num2 ) { return num1 - num2 ; }
همانطور که پیشتر نیز عنوان شد، تابع دوم جایگزین تابع اول میگردد. در واقع تعریف فوق همانند تعریف زیر میباشد:
var calc = function ( num1 , num2 ) { return num1 + num2 ; }; calc = function ( num1 , num2 ) { return num1 - num2 ; };
همانطور که میبینید، ابتدا متغیری به نام calc تعریف شدهاست و با یک تابع مقداردهی اولیه شده است. سپس با تابع دوم مقداردهی مجدد گردیده است و دیگر به تابع قبلی اشاره نمیکند. بنابراین همیشه تابع آخر جایگزین توابع قبلی میگردد.
Function Declaration در مقابل Function Expression
این دو روش تعریف و استفاده از توابع تقریبا مشابه هم میباشند و فقط یک تفاوت اصلی بین آنها وجود دارد و آن به نحوهی رفتار موتور پردازشی جاوا اسکریپت بر میگردد. Function Declaration قبل از اینکه کدهای جاوا اسکریپت خوانده و اجرا شوند، خوانده شده و در دسترس خواهند بود؛ اما Function Expression تا زمانی که روال اجرای کد به این خط از کد نرسد، اجرا نخواهد شد و در دسترس نخواهد بود. به مثال Function Declaration زیر توجه کنید:
alert ( sum ( 10 , 20 )); function sum ( a , b ) { return a + b ; }
خروجی :
30
قبل از اعلان تابع sum ، این تابع فراخوانی شده است؛ ولی هیچ خطایی رخ نمیدهد. زیرا قبل از اجرای دستورات، تابع sum خوانده و در دسترس خواهد بود. اما اگر تابع فوق بصورت Function Expression تعریف شده بود، خطا رخ میداد و برنامه اجرا نمیشد.
alert ( sum ( 10 , 20 )); // Error: undefined is not a function var sum = function ( a , b ) { return a + b ; };
خروجی :
Error: undefined is not a function
همانطور که میبینید، در خط اول برنامه، خطای اجرایی رخ داده است. زیرا هنوز روال اجرایی برنامه به خط تعریف تابع sum نرسیدهاست. بنابراین تابع sum در دسترس نخواهد بود و فراخوانی آن موجب بروز خطا میگردد.
ارسال تابع به عنوان ورودی یا خروجی توابع دیگر
همانطور که قبلا بیان شد، نام تابع در واقع یک متغیر میباشد که به تابع مورد نظر اشاره میکند. بنابراین میتوان همانند یک متغیر با آن رفتار نموده و به عنوان آرگومان ورودی و یا مقدار خروجی یک تابع آن را ارسال نمود. به مثال زیر توجه کنید:
function add ( a , b ) { return a + b ; } function mult ( a , b ) { return a * b ; } function calc ( a , b , fn ) { return a * b + fn ( a , b ); } alert ( calc ( 10 , 20 , add )); alert ( calc ( 10 , 20 , mult ));
خروجی :
230
400
دو تابع به نامهای add و mult با دو آرگومان ورودی تعریف شدهاند که به ترتیب حاصل جمع و حاصل ضرب دو آرگومان را بر میگردانند. تابع calc نیز با 3 آرگومان ورودی تعریف شدهاست که قصد داریم برای آرگومان سوم یک تابع را ارسال نماییم. تابع calc در 2 مرحله فراخوانی شدهاست که در یک مرحله تابع add و در مرحلهی دیگر تابع mult به عنوان آرگومان ورودی ارسال شدهاند. همانطور که از قبل میدانید، نام تابع اشارهگری به خود تابع میباشد. در تابع calc نیز با فراخوانی آرگومان fn در واقع داریم تابع ارسالی را فراخوانی مینماییم. حال به مثال زیر توجه کنید که یک تابع را به عنوان خروجی بر میگرداند:
function createFunction ( a , b ) { return function ( c ) { var d = ( a + b ) * c ; return d ; }; } var fn = createFunction ( 10 , 20 ); alert ( fn ( 30 ));
خروجی :
900
تابع createFunction دارای 2 آرگومان ورودی میباشد و یک تابع را با 1 آرگومان ورودی بر میگرداند. در ابتدا تابع createFunction را با آرگومانهای 10 و 20 فراخوانی نمودیم. خروجی این تابع که خود یک تابع با یک آرگومان ورودی میباشد، به عنوان خروجی برگردانده شده و در متغیر fn قرار میگیرد. سپس تابع fn با آرگومان ورودی 30 فراخوانی میگردد که مقادیر 10 و 20 را با هم جمع نموده و در 30 ضرب مینماید.
تکمیل ساختار پروژهی IDP
تا اینجا برای IDP، یک پروژهی خالی وب را ایجاد و به مرور، آنرا تکمیل کردیم. اما اکنون نیاز است پشتیبانی از بانک اطلاعاتی را نیز به آن اضافه کنیم. برای این منظور چهار پروژهی Class library کمکی را نیز به Solution آن اضافه میکنیم:
- DNT.IDP.DomainClasses
در این پروژه، کلاسهای متناظر با موجودیتهای جداول مرتبط با اطلاعات کاربران قرار میگیرند.
- DNT.IDP.DataLayer
این پروژه Context برنامه و Migrations آنرا تشکیل میدهد. همچنین به همراه تنظیمات و Seed اولیهی اطلاعات بانک اطلاعاتی نیز میباشد.
رشتهی اتصالی آن نیز در فایل DNT.IDP\appsettings.json ذخیره شدهاست.
- DNT.IDP.Common
الگوریتم هش کردن اطلاعات، در این پروژهی مشترک بین چند پروژهی دیگر قرار گرفتهاست. از آن جهت هش کردن کلمات عبور، در دو پروژهی DataLayer و همچنین Services استفاده میکنیم.
- DNT.IDP.Services
کلاس سرویس کاربران که با استفاده از DataLayer با بانک اطلاعاتی ارتباط برقرار میکند، در این پروژه قرار گرفتهاست.
ساختار بانک اطلاعاتی کاربران IdentityServer
در اینجا ساختار بانک اطلاعاتی کاربران IdentityServer، بر اساس جداول کاربران و Claims آنها تشکیل میشود:
namespace DNT.IDP.DomainClasses { public class User { [Key] [MaxLength(50)] public string SubjectId { get; set; } [MaxLength(100)] [Required] public string Username { get; set; } [MaxLength(100)] public string Password { get; set; } [Required] public bool IsActive { get; set; } public ICollection<UserClaim> UserClaims { get; set; } public ICollection<UserLogin> UserLogins { get; set; } } }
ساختار Claims او نیز به صورت زیر تعریف میشود که با تعریف یک Claim استاندارد، سازگاری دارد:
namespace DNT.IDP.DomainClasses { public class UserClaim { public int Id { get; set; } [MaxLength(50)] [Required] public string SubjectId { get; set; } public User User { get; set; } [Required] [MaxLength(250)] public string ClaimType { get; set; } [Required] [MaxLength(250)] public string ClaimValue { get; set; } } }
namespace DNT.IDP.DomainClasses { public class UserLogin { public int Id { get; set; } [MaxLength(50)] [Required] public string SubjectId { get; set; } public User User { get; set; } [Required] [MaxLength(250)] public string LoginProvider { get; set; } [Required] [MaxLength(250)] public string ProviderKey { get; set; } } }
در پروژهی DNT.IDP.DataLayer در پوشهی Configurations آن، کلاسهای UserConfiguration و UserClaimConfiguration را مشاهده میکنید که حاوی اطلاعات اولیهای برای تشکیل User 1 و User 2 به همراه Claims آنها هستند. این اطلاعات را دقیقا از فایل استاتیک Config که در قسمتهای قبل تکمیل کردیم، به این دو کلاس جدید IEntityTypeConfiguration منتقل کردهایم تا به این ترتیب متد GetUsers فایل استاتیک Config را با نمونهی دیتابیسی آن جایگزین کنیم.
سرویسی که از طریق Context برنامه با بانک اطلاعاتی ارتباط برقرار میکند، چنین ساختاری را دارد:
public interface IUsersService { Task<bool> AreUserCredentialsValidAsync(string username, string password); Task<User> GetUserByEmailAsync(string email); Task<User> GetUserByProviderAsync(string loginProvider, string providerKey); Task<User> GetUserBySubjectIdAsync(string subjectId); Task<User> GetUserByUsernameAsync(string username); Task<IEnumerable<UserClaim>> GetUserClaimsBySubjectIdAsync(string subjectId); Task<IEnumerable<UserLogin>> GetUserLoginsBySubjectIdAsync(string subjectId); Task<bool> IsUserActiveAsync(string subjectId); Task AddUserAsync(User user); Task AddUserLoginAsync(string subjectId, string loginProvider, string providerKey); Task AddUserClaimAsync(string subjectId, string claimType, string claimValue); }
تنظیمات نهایی این سرویسها و Context برنامه نیز در فایل DNT.IDP\Startup.cs جهت معرفی به سیستم تزریق وابستگیها، صورت گرفتهاند. همچنین در اینجا متد initializeDb را نیز مشاهده میکنید که با فراخوانی متد context.Database.Migrate، تمام کلاسهای Migrations پروژهی DataLayer را به صورت خودکار به بانک اطلاعاتی اعمال میکند.
غیرفعال کردن صفحهی Consent در Quick Start UI
در «قسمت چهارم - نصب و راه اندازی IdentityServer» فایلهای Quick Start UI را به پروژهی IDP اضافه کردیم. در ادامه میخواهیم قدم به قدم این پروژه را تغییر دهیم.
در صفحهی Consent در Quick Start UI، لیست scopes درخواستی برنامهی کلاینت ذکر شده و سپس کاربر انتخاب میکند که کدامیک از آنها، باید به برنامهی کلاینت ارائه شوند. این صفحه، برای سناریوی ما که تمام برنامههای کلاینت توسط ما توسعه یافتهاند، بیمعنا است و صرفا برای کلاینتهای ثالثی که قرار است از IDP ما استفاده کنند، معنا پیدا میکند. برای غیرفعال کردن آن کافی است به فایل استاتیک Config مراجعه کرده و خاصیت RequireConsent کلاینت مدنظر را به false تنظیم کرد.
تغییر نام پوشهی Quickstart و سپس اصلاح فضای نام پیشفرض کنترلرهای آن
در حال حاضر کدهای کنترلرهای Quick Start UI داخل پوشهی Quickstart برنامهی IDP قرار گرفتهاند. با توجه به اینکه قصد داریم این کدها را تغییر دهیم و همچنین این پوشه در اساس، همان پوشهی استاندارد Controllers است، ابتدا نام این پوشه را به Controllers تغییر داده و سپس در تمام کنترلرهای ذیل آن، فضای نام پیشفرض IdentityServer4.Quickstart.UI را نیز به فضای نام متناسبی با پوشه بندی پروژهی جاری تغییر میدهیم. برای مثال کنترلر Account واقع در پوشهی Account، اینبار دارای فضای نام DNT.IDP.Controllers.Account خواهد شد و به همین ترتیب برای مابقی کنترلها عمل میکنیم.
پس از این تغییرات، عبارات using موجود در Viewها را نیز باید تغییر دهید تا برنامه در زمان اجرا به مشکلی برنخورد. البته ASP.NET Core 2.1 در زمان کامپایل برنامه، تمام Viewهای آنرا نیز کامپایل میکند و اگر خطایی در آنها وجود داشته باشد، امکان بررسی و رفع آنها پیش از اجرای برنامه، میسر است.
و یا میتوان جهت سهولت کار، فایل DNT.IDP\Views\_ViewImports.cshtml را جهت معرفی این فضاهای نام جدید ویرایش کرد تا نیازی به تغییر Viewها نباشد:
@using DNT.IDP.Controllers.Account; @using DNT.IDP.Controllers.Consent; @using DNT.IDP.Controllers.Grants; @using DNT.IDP.Controllers.Home; @using DNT.IDP.Controllers.Diagnostics; @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
تعامل با IdentityServer از طریق کدهای سفارشی
پس از تشکیل «ساختار بانک اطلاعاتی کاربران IdentityServer» و همچنین تهیه سرویسهای متناظری جهت کار با آن، اکنون نیاز است مطمئن شویم IdentityServer از این بانک اطلاعاتی برای دریافت اطلاعات کاربران خود استفاده میکند.
در حال حاضر، با استفاده از متد الحاقی AddTestUsers معرفی شدهی در فایل DNT.IDP\Startup.cs، اطلاعات کاربران درون حافظهای برنامه را از متد ()Config.GetUsers دریافت میکنیم.
بنابراین اولین قدم، بررسی ساختار متد AddTestUsers است. برای این منظور به مخزن کد IdentityServer4 مراجعه کرده و کدهای متد الحاقی AddTestUsers را بررسی میکنیم:
public static class IdentityServerBuilderExtensions { public static IIdentityServerBuilder AddTestUsers(this IIdentityServerBuilder builder, List<TestUser> users) { builder.Services.AddSingleton(new TestUserStore(users)); builder.AddProfileService<TestUserProfileService>(); builder.AddResourceOwnerValidator<TestUserResourceOwnerPasswordValidator>(); return builder; } }
- سپس سرویس پروفایل کاربران را اضافه کردهاست. این سرویس با پیاده سازی اینترفیس IProfileService تهیه میشود. کار آن اتصال یک User Store سفارشی به سرویس کاربران و دریافت اطلاعات پروفایل آنها مانند Claims است.
- در آخر TestUserResourceOwnerPasswordValidator، کار اعتبارسنجی کلمهی عبور و نام کاربری را در صورت استفادهی از Flow ویژهای به نام ResourceOwner که استفادهی از آن توصیه نمیشود (ROBC Flow)، انجام میدهد.
برای جایگزین کردن AddTestUsers، کلاس جدید IdentityServerBuilderExtensions را در ریشهی پروژهی IDP با محتوای ذیل اضافه میکنیم:
using DNT.IDP.Services; using Microsoft.Extensions.DependencyInjection; namespace DNT.IDP { public static class IdentityServerBuilderExtensions { public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder) { // builder.Services.AddScoped<IUsersService, UsersService>(); builder.AddProfileService<CustomUserProfileService>(); return builder; } } }
سپس یک ProfileService سفارشی را ثبت کردهایم. این سرویس، با پیاده سازی IProfileService به صورت زیر پیاده سازی میشود:
namespace DNT.IDP.Services { public class CustomUserProfileService : IProfileService { private readonly IUsersService _usersService; public CustomUserProfileService(IUsersService usersService) { _usersService = usersService; } public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var subjectId = context.Subject.GetSubjectId(); var claimsForUser = await _usersService.GetUserClaimsBySubjectIdAsync(subjectId); context.IssuedClaims = claimsForUser.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); } public async Task IsActiveAsync(IsActiveContext context) { var subjectId = context.Subject.GetSubjectId(); context.IsActive = await _usersService.IsUserActiveAsync(subjectId); } } }
در متدهای آن، ابتدا subjectId و یا همان Id منحصربفرد کاربر جاری سیستم، دریافت شده و سپس بر اساس آن میتوان از usersService، جهت دریافت اطلاعات مختلف کاربر، کوئری گرفت و نتیجه را در خواص context جاری، برای استفادههای بعدی، ذخیره کرد.
اکنون به کلاس src\IDP\DNT.IDP\Startup.cs مراجعه کرده و متد AddTestUsers را با AddCustomUserStore جایگزین میکنیم:
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddDeveloperSigningCredential() .AddCustomUserStore() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients());
اتصال IdentityServer به User Store سفارشی
در ادامه، سازندهی کنترلر DNT.IDP\Quickstart\Account\AccountController.cs را بررسی میکنیم:
public AccountController( IIdentityServerInteractionService interaction, IClientStore clientStore, IAuthenticationSchemeProvider schemeProvider, IEventService events, TestUserStore users = null) { _users = users ?? new TestUserStore(TestUsers.Users); _interaction = interaction; _clientStore = clientStore; _schemeProvider = schemeProvider; _events = events; }
- IClientStore پیاده سازی محل ذخیره سازی اطلاعات کلاینتها را ارائه میدهد که در حال حاضر توسط متد استاتیک Config در اختیار آن قرار میگیرد.
- IEventService رخدادهایی مانند لاگین موفقیت آمیز یک کاربر را گزارش میدهد.
- در آخر، TestUserStore تزریق شدهاست که میخواهیم آنرا با User Store سفارشی خودمان جایگزین کنیم. بنابراین در ابتدا TestUserStore را با UserStore سفارشی خودمان جایگزین میکنیم:
private readonly TestUserStore _users; private readonly IUsersService _usersService; public AccountController( // ... IUsersService usersService) { _usersService = usersService; // ... }
پس از معرفی فیلد usersService_، اکنون در قسمت زیر از آن استفاده میکنیم:
در اکشن متد لاگین، جهت بررسی صحت نام کاربری و کلمهی عبور و همچنین یافتن کاربر متناظر با آن:
public async Task<IActionResult> Login(LoginInputModel model, string button) { //... if (ModelState.IsValid) { if (await _usersService.AreUserCredentialsValidAsync(model.Username, model.Password)) { var user = await _usersService.GetUserByUsernameAsync(model.Username);
افزودن امکان ثبت کاربران جدید به برنامهی IDP
پس از اتصال قسمت login برنامهی IDP به بانک اطلاعاتی، اکنون میخواهیم امکان ثبت کاربران را نیز به آن اضافه کنیم.
این قسمت شامل تغییرات ذیل است:
الف) اضافه شدن RegisterUserViewModel
این ViewModel که فیلدهای فرم ثبتنام را تشکیل میدهد، ابتدا با نام کاربری و کلمهی عبور شروع میشود:
public class RegisterUserViewModel { // credentials [MaxLength(100)] public string Username { get; set; } [MaxLength(100)] public string Password { get; set; }
public class RegisterUserViewModel { // ... // claims [Required] [MaxLength(100)] public string Firstname { get; set; } [Required] [MaxLength(100)] public string Lastname { get; set; } [Required] [MaxLength(150)] public string Email { get; set; } [Required] [MaxLength(200)] public string Address { get; set; } [Required] [MaxLength(2)] public string Country { get; set; }
ب) افزودن UserRegistrationController
این کنترلر، RegisterUserViewModel را دریافت کرده و سپس بر اساس آن، شیء User ابتدای بحث را تشکیل میدهد. ابتدا نام کاربری و کلمهی عبور را در جدول کاربران ثبت میکند و سپس سایر خواص این ViewModel را در جدول UserClaims:
varuserToCreate=newUser { Password=model.Password.GetSha256Hash(), Username=model.Username, IsActive=true }; userToCreate.UserClaims.Add(newUserClaim("country",model.Country)); userToCreate.UserClaims.Add(newUserClaim("address",model.Address)); userToCreate.UserClaims.Add(newUserClaim("given_name",model.Firstname)); userToCreate.UserClaims.Add(newUserClaim("family_name",model.Lastname)); userToCreate.UserClaims.Add(newUserClaim("email",model.Email)); userToCreate.UserClaims.Add(newUserClaim("subscriptionlevel","FreeUser"));
این فایل، view متناظر با ViewModel فوق را ارائه میدهد که توسط آن، کاربری میتواند اطلاعات خود را ثبت کرده و وارد سیستم شود.
د) اصلاح فایل ViewImports.cshtml_ جهت تعریف فضای نام UserRegistration
در RegisterUser.cshtml از RegisterUserViewModel استفاده میشود. به همین جهت بهتر است فضای نام آنرا به ViewImports اضافه کرد.
ه) افزودن لینک ثبت نام به صفحهی لاگین در Login.cshtml
این لینک دقیقا در ذیل چکباکس Remember My Login اضافه شدهاست.
اکنون اگر برنامه را اجرا کنیم، ابتدا مشاهده میکنیم که صفحهی لاگین به همراه لینک ثبت نام ظاهر میشود:
و پس از کلیک بر روی آن، صفحهی ثبت کاربر جدید به صورت زیر نمایش داده خواهد شد:
برای آزمایش، کاربری را ثبت کنید. پس از ثبت اطلاعات، بلافاصله وارد سیستم خواهید شد. البته چون در اینجا subscriptionlevel به FreeUser تنظیم شدهاست، این کاربر یکسری از لینکهای برنامهی MVC Client را به علت نداشتن دسترسی، مشاهده نخواهد کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی 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 وارد کنید.
تصویر امنیتی و یا کپچا برای تشخیص و احراز انسان بودن استفاده کننده استفاده میشود و بصورت تصویری که استخراج نوشتههای درون آن برای روبوتها بسیار سخت و یا نشدنی است ایجاد میشود و دارای انواع و اقسام متفاوتی است. در این میان برای استفاده از این امکان نمونه هایی در زبانهای مختلف تهیه شده که بسته به سلیقه و نیاز مورد استفاده قرار گرفته شده است. در این مقاله قصد داریم با بررسی تصویر امنیتی که در وبلاگ کنونی استفاده شده آنرا تا حدودی بازسازی کنیم.
تصویر آشنای کاربران سایت برای ورود به قسمت مدیریت که در صفحه مورد نظر از تصویر امنیتی استفاده شده است:
با توجه به آدرس لینک تصویر مشخص است که برای تولید این تصویر از هندلر استفاده شده و با توجه به پارامترهایی که به آن داده شده تصویر مورد نظر را ایجاد میکند و با فرمت مشخصی بر میگرداند:https://www.dntips.ir /CaptchaImage/Show? text=H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ &foreColor=%231B0172 &fontSize=12 &fontName=Tahoma
پارامترهای پاس داده شده به ترتیب ذیل برای اهدافی قرار داده شده اند:
نام |
مورد استفاده |
مقدار |
text |
متنی که بصورت کد شده حاوی متن مورد استفاده تصویر امنیتی است |
H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ |
foreColor |
رنگ مربوط به نوشتههای تصویر |
%231B0172 |
fontSize |
سایز فونت |
12 |
fontName |
نام فونت |
Tahoma |
بنظر اهم پارامتر مورد نیاز همان متن است که بصورت کد شده در بالا به آن اشاره شد. برای این منظور باید کلاسی تهیه شود که حاوی متدهای:
1- انتخاب یه عدد تصادفی از بین دامنه ای که به آن داده میشود.
برای مثال یه عد از بین 100 تا 9999 که بصورت تصادفی انتخاب میشود
2- تبدیل به معادل حرفی آن
برای مثال از عدد "7062" به حروف آن یعنی "هفت هزار و شصت و دو" استخراج شود
3- کد کردن حرف تولید شده
حرف "هفت هزار و شصت و دو" را کد کرده و بصورت متنی شبیه
"H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ "
تبدیل کند
4- دیکد کردن و استخراج
البته این مورد با توجه به استفاده ای که ما داریم نیازی به پیاده سازی نیست
در کنار تصوریر امنیتی از یک فیلد مخفی نیز برای نگهداری مقدار کد شده برای مقایسه با ورودی کاربر استفاده شده که در قسمت بعد ضمن ایجاد نمونه کاربردی بیشتر با قسمتهای مختلف آن آشنا میشویم.
سؤالات متداول
- میخواهم برنامهام را بر روی کامپیوتر مشتری بدون دردسر نصب کنم؛ با حداقل حجم و دردسر توزیع. به علاوه بدون نیاز به وصله پینه کردن اسمبلیهای تجاری دریافت شده.
پاسخ: PdfReport از چند اسمبلی دات نتی تشکیل شده است که نیاز به نصب خاصی ندارد. همچنین حجم فشرده شده آن نیز زیر 2 مگابایت است. بنابراین از جهت توزیع مشکل خاصی نخواهید داشت. همچنین کل این مجموعه سورس باز است و مشکلات متداول همراه با گزارش سازهای تجاری را به همراه ندارد.
- میخواهم فایل گزارش، به همراه برنامه و فایل exe آن و نه جدای از آن توزیع شود.
پاسخ: روش کار با PdfReport اصطلاحا code-first است. یعنی یک یا چند کلاس تهیه شده توسط شما، کار تهیه گزارش را انجام میدهند و تمام اینها کامپایل شده و به همراه فایل اجرایی یا اسمبلیهای خاصی که برای آن درنظر میگیرید، کامپایل و توزیع خواهند شد. همچنین با توجه به code-first بودن آن و عدم وابستگی به فناوری خاصی، این گزارشات در برنامههای وب و ویندوز نیز میتوانند بدون نیاز به تغییری در کدهای شما مورد استفاده قرار گیرند.
- برای کار با PdfReport از کجا باید شروع کرد؟
پاسخ: لطفا برچسب PdfReport را در سایت جاری دنبال نمائید. نحوه استفاده از قابلیتهای آن قدم به قدم توضیح داده شدهاند.
- میخواهم برای صرفه جویی در کاغذ چاپی، گزارش چند ستونهای را تهیه کنم.
پاسخ: لطفا مراجعه کنید به مثال ایجاد قالبهای سفارشی ستونها.
- میخواهم گزارشی را تولید کنم که حجم متن فیلدهای آن مشخص نیست. یکی ممکن است نصف صفحه باشد دیگری دو صفحه و یا بعضی تنها یک سطر.
پاسخ: در PdfReport ارتفاع هر سطر به صورت خودکار بر مبنای حجم وارد شده محاسبه و تنظیم میگردد. به عبارتی این تنظیم ثابت نیست و سبب حذف محتوای ارائه شده در آنها یا محو شدن ردیفهای دیگر نمیگردد.
- نیاز به گزارش سازی دارم که بدون مشکل با ORMها کار کند. برای مثال در حین کار با Entity framework مستقیما بتواند با اشیاء و لیستهای حاصل از آن کار کند.
پاسخ: یکی از انواع منابع داده تعریف شده در PdfReport جهت کار با ORMها طراحی شده است که مثالی از نحوه استفاده از آنرا در مثال EF Code first همراه با مجموعه مثالهای این کتابخانه میتوانید ملاحظه نمائید.
- آیا این کتابخانه میتواند از فایل سیستم (و نه صرفا بانک اطلاعاتی) هم تصاویر را دریافت و در گزارشات قرار دهد؟
پاسخ: بلی. لطفا به مثال ImageFilePath مراجعه نمائید.
- میخواهم یک گزارش ساز پویا در برنامه داشته باشم. فقط کوئری غیر مشخصی را به آن بدهم و حاصل آن یک گزارش باشد.
پاسخ: امکان تهیه گزارشهای پویا نیز در PdfReport پیش بینی شده است. توضیحات بیشتر همچنین این امکان در حین کار با ORMها نیز وجود دارد.
- میخواهم بدون استفاده از بانک اطلاعاتی نیز بتوانم گزارشی را تهیه کنم. برای مثال یک لیست جنریک تشکیل شده در حافظه دارم.
پاسخ: برای این منظور تنها کافی است از منبع داده صحیحی استفاده نمائید. برای اطلاعات بیشتر به مثال IList مراجعه کنید.
- میخواهم از بانکهای اطلاعاتی دیگری بجز SQL Server استفاده کنم.
پاسخ: میتوانید یک نمونه مثال استفاده از PdfReport را با بانک اطلاعاتی SQLite، در اینجا مشاهده کنید.
public static string GetFileName(int code) { switch (code) { case 0: return "/Images/User/Weather/Tornado.png"; //... }
public class YahooWeatherRssItem { public int Code { get; set; } //... public string FileName { get { return Util.GetFileName(Code); } } }
روش اول: دریافت اطلاعات سمت سرور به کمک درخواستهای Ajax
استفاده از Ajax یکی از روشهای کلاسیک دریافت اطلاعات سمت سرور در کدهای جاوا اسکریپتی است.
<script type="text/javascript"> var products = []; $(function() { $.getJSON("/home/products", function(response) { products = response.products; }); }); </script>
- مزایا: استفاده از Ajax، روشی بسیار متداول و شناخته شدهاست و به کمک انواع و اقسام روشهای بازگشت JSON از سرور، میتوان با آن کار کرد.
- معایب: درخواست Ajax، صرفا پس از بارگذاری اولیهی صفحه به سمت سرور ارسال خواهد شد و در این بین، کاربر وقفهای را مشاهده خواهد کرد. همچنین در اینجا بجای یک درخواست از سرور، حداقل دو درخواست باید ارسال شوند؛ یکی برای بارگذاری صفحهی اصلی و دیگری برای دریافت اطلاعات Ajax ایی از سرور به صورت غیرهمزمان.
روش دوم: دریافت اطلاعات از یک فایل جاوا اسکریپتی خارجی
اطلاعات سمت کاربر را از یک فایل جاوا اسکریپتی خارجی الحاق شدهی به صفحهی جاری نیز میتوان تهیه کرد:
<script src="/file.js"></script>
این روش نیز تقریبا مانند حالت یک درخواست Ajax ایی کار میکند و اطلاعات مورد نیاز را در طی یک درخواست جداگانه، پس از بارگذاری صفحهی اصلی، از سرور دریافت خواهد کرد. البته در حالت کار با Ajax، میتوان در طی یک callback، نتیجه را دریافت کرد و سپس عکس العمل نشان داد؛ اما در اینجا callback ایی وجود ندارد.
روش سوم: استفاده از SignalR
در SignalR ابتدا سعی میشود تا با استفاده از Web Sockets ارتباطی ماندگار بین کلاینت و سرور برقرار شود و سپس در این حالت، سرور میتواند مدام اطلاعاتی، مانند تغییرات دادههای خود را به سمت کاربر، جهت نمایش و یا محاسبات خاص خود ارسال کند. اگر حالت Web Socket میسر نباشد (توسط سرور یا کلاینت پشتیبانی نشود)، به حالتهای دیگری مانند server events, forever frames, long polling سوئیچ خواهد کرد. اطلاعات بیشتر
روش چهارم: قرار دادن اطلاعات سمت سرور در کدهای HTML صفحه
روش متداول دیگری جهت تامین اطلاعات جاوا اسکریپتی سمت کاربر، قرار دادن آنها در ویژگیهای data-* ارائه شده در HTML5 است.
<ul> @foreach (var product in products) { <li id="product@product.Id" data-rank="@product.Rank">@product.Name</li> } </ul>
اکنون برای دسترسی به مقدار data-rank سطری مانند product1، در کدهای جاوا اسکریپتی صفحه میتوان نوشت:
<script type="text/javascript"> var product1Rank = $("#product1").data("rank"); </script>
روش پنجم: قرار دادن اطلاعات سمت سرور در کدهای جاوا اسکریپتی صفحه
این روش همانند روش چهارم است، با این تفاوت که اینبار اطلاعات مورد نیاز، مستقیما به یک متغیر جاوا اسکریپتی انتساب داده شدهاست:
<script type="text/javascript"> var product1Name = "@product1.Name"; </script>
روش ششم: انتساب یک شیء دات نتی به یک متغیر جاوا اسکریپتی
این روش همانند روش پنجم است، با این تفاوت که اینبار قصد داریم بجای یک مقدار ثابت رشتهای یا عددی، برای مثال، آرایهای از اشیاء را به یک متغیر جاوا اسکریپتی انتساب دهیم. در اینجا ابتدا اطلاعات مورد نظر را به فرمت JSON تبدیل میکنیم:
//سمت سرور [HttpGet] public ActionResult Index() { var array = new[] { "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and/or Barbuda" }; ViewBag.JsonString = new JavaScriptSerializer().Serialize(array); return View(); }
//سمت کلاینت <script type="text/javascript"> var jsonArray = @Html.Raw(@ViewBag.JsonString); </script>
و یا اینکار را به صورت خلاصه به شکل زیر نیز میتوان در سمت کاربر انجام داد:
<script type="text/javascript"> var model = @Html.Raw(Json.Encode(Model)); // your js code here </script>
MVC Scaffolding #1
کل سری ASP.NET MVC
به همراه کل سری EF Code First
MVC Scaffolding چیست؟
MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامههای ASP.NET MVC مبتنی بر EF Code First.
بررسی مقدماتی MVC Scaffolding
امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژههای ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندیها و قابلیتهای سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [DisplayName("Is Complete")] public bool IsComplete { set; get; } [StringLength(450)] public string Description { set; get; } } }
همانطور که ملاحظه میکنید در قسمت قالبها، تولید کنترلرهایی با اکشن متدهای ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آنرا پذیرفتهایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:
الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدلهای برنامه ایجاد میگردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه میکند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلیهای دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)
معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شدهای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی میتواند شامل فیلدهای بیشتری باشد و نباید آنرا مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).
مزیتها:
قسمت عمدهای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شدهاند. کارهای اولیهای که با هر روش و الگوی شناخته شدهای قصد پیاده سازی آنها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمدهای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرمهای آنرا بخواهید با jQuery Ajax پیاده سازی کنید یا کنترلهای پیش فرض را با افزونههای jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.
نصب بسته اصلی MVC Scaffolding توسط NuGet
بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console میتوان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
Attempting to resolve dependency 'T4Scaffolding'. Attempting to resolve dependency 'T4Scaffolding.Core'. Attempting to resolve dependency 'EntityFramework'. Successfully installed 'T4Scaffolding.Core 1.0.0'. Successfully installed 'T4Scaffolding 1.0.8'. Successfully installed 'MvcScaffolding 1.0.9'. Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1. Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1. Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز میتوان پیاده سازی کرد:
Scaffold Controller Task
نکته و مزیت مهم دیگری که در اینجا در دسترس میباشد، سوئیچهای خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچهای قابل استفاده است.
برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding
در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
public class TasksController : Controller { private readonly ITaskRepository taskRepository; // If you are using Dependency Injection, you can delete the following constructor public TasksController() : this(new TaskRepository()) { } public TasksController(ITaskRepository taskRepository) { this.taskRepository = taskRepository; }
public interface ITaskRepository : IDisposable { IQueryable<Task> All { get; } IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties); Task Find(int id); void InsertOrUpdate(Task task); void Delete(int id); void Save(); }
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزنهای تعریف شده را تک موجودیتی میکند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیتها به کلاسهای مخزن واگذار شدهاند و کنترلرها به این نحو خلوتتر گردیدهاند، یک مرحله پیشرفت محسوب میشود.