کتابخانه sammy
LINQ در JavaScript
defaultChecks() { const author = { firstName: "Vahid", lastName: "N" }; console.log(author.lastname); author.lastName.trimStart(); author.firstName.charCodeAt("1"); }
- خاصیت lastname در شیء author وجود خارجی ندارد.
- نوع رشتهای، به همراه متد trimStart نیست.
- متد charCodeAt یک عدد را به عنوان پارامتر قبول میکند.
اما باید درنظر داشت که بسیاری از قابلیتهای بررسی کد TypeScript، به صورت پیشفرض فعال نیستند که در ادامه آنها را برای یافتن پیش از موعود بسیاری از مشکلات، فعالسازی خواهیم کرد.
نصب افزونهی TSLint در VSCode
جهت مشاهدهی بهتر خطاهای کامپایلر TypeScript، پیش از کامپایل نهایی کدها، میتوان از افزونهی TSLint استفاده کرد. برای نصب آن، ابتدا باید بستهی ذیل را نصب کرد:
> npm install -g tslint typescript
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونههایی مانند ریشارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه میدهد.
فعالسازی بررسی نال و نوعهای نال پذیر
strictNullChecks یکی از مهمترین پرچمهای تنظیمات کامپایلر تایپاسکریپت است. برای افزودن آن، به فایل tsconfig.json مراجعه کرده و پرچم آنرا به true تنظیم کنید:
{ "compilerOptions": { "strictNullChecks": true } }
برای مثال، متد ذیل را در نظر بگیرید:
getSessionItem(key: string): any { const data = window.sessionStorage.getItem(key); return JSON.parse(data); }
[ts] Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'. const data: string | null
برای رفع این مشکل تنها کافی است بررسی کنیم که آیا data نال است یا خیر؟ و اگر خیر، آنگاه آنرا به متد JSON.parse ارسال کنیم:
getSessionItem(key: string): any { const data = window.sessionStorage.getItem(key); if (data) { return JSON.parse(data); } else { return null; } }
گزارش returnهای فراموش شده
در متد ذیل، یک return فراموش شده وجود دارد و تمام شرطهای برنامه به یک خروجی مشخص، منتهی نمیشوند:
noImplicitReturns(a: number) { if (a > 10) { return a; } // No return in this branch }
{ "compilerOptions": { "noImplicitReturns": true } }
[ts] Not all code paths return a value.
تشخیص کدهای مرده
قطعه کدی که پس از یک return قرار بگیرد، یک کد مرده نامیده میشود. با تنظیم پرچم allowUnreachableCode در فایل tsconfig.json به false، میتوان کامپایلر TypeScript را وادار کرد تا اینگونه موارد را به عنوان خطا گزارش کند:
{ "compilerOptions": { "allowUnreachableCode": false } }
allowUnreachableCode() { if (false) { console.log("Unreachable code"); } const a = 1; if (a > 0) { return 10; // reachable code } return 0; console.log("Unreachable code"); }
تشخیص پارامترها و متغیرهای استفاده نشده
دو متد ذیل را درنظر بگیرید:
unusedLocals() { const a = "foo"; // Error: 'a' is declared but its value is never read return "bar"; } unusedParameters(n: number) { n = 0; // Never read }
برای فعالسازی بررسی یک چنین مواردی باید دو پرچم ذیل را در فایل tsconfig.json به true تنظیم کرد:
{ "compilerOptions": { "noUnusedLocals": true, "noUnusedParameters": true } }
[ts] 'a' is declared but its value is never read. [ts] 'n' is declared but its value is never read.
یافتن خواصی که نباید در یک شیء وجود داشته باشند
در مثال ذیل، خاصیت baz در تعاریف اصلی نوعهای x و y وجود ندارد:
excessPropertyForObjectLiterals() { let x: { foo: number }; x = { foo: 1, baz: 2 }; // Error, excess property 'baz' let y: { foo: number, bar?: number }; y = { foo: 1, baz: 2 }; // Error, excess property 'baz' }
{ "compilerOptions": { "suppressExcessPropertyErrors": false } }
[ts] Type '{ foo: number; baz: number; }' is not assignable to type '{ foo: number; }'. Object literal may only specify known properties, and 'baz' does not exist in type '{ foo: number; }'. (property) baz: number
یافتن breakهای فراموش شده در عبارات switch
در مثال زیر، یک break فراموش شدهاست:
fallthroughCasesInSwitchStatement(a: number) { switch (a) { case 0: break; case 1: a += 1; case 2: a += 2; break; } }
{ "compilerOptions": { "noFallthroughCasesInSwitch": true } }
یافتن ایندکسهای تعریف نشدهی در اشیاء
در مثال زیر، شیء x دارای خاصیت b نیست؛ اما دقیقا با این ایندکس مورد استفاده قرار گرفتهاست:
indexingObjectsLackingIndexSignatures() { const x = { a: 0 }; x["a"] = 1; // ok x["b"] = 1; // Error, type '{ a: number; }' has no index signature. }
{ "compilerOptions": { "suppressImplicitAnyIndexErrors": false } }
[ts] Element implicitly has an 'any' type because type '{ a: number; }' has no index signature.
اجبار به تعریف صریح نوعها در TypeScript
عمدهی قابلیت TypeScript در یافتن خطاها به تعاریف نوعها و راهنمایی کامپایلر آن در این زمینه بر میگردد. اما چون این زبان سازگاری کاملی را با JavaScript دارد، تعریف نوعها در آن اجباری نیست و در این حالت اگر نوعی تعریف نشده باشد، به any تفسیر میشود. جهت اجبار به تعریف نوعها در TypeScript میتوان پرچم noImplicitAny را در فایل tsconfig.json به true تنظیم کرد:
{ "compilerOptions": { "noImplicitAny": true } }
noImplicitAny(args) { // Error: Parameter 'args' implicitly has an 'any' type. console.log(args); }
noImplicitAnyArgs(args: string[]) { // ok with the type information console.log(args); }
یک نکتهی تکمیلی
اگر از دستور ng build --watch برای ساخت برنامههای Angular استفاده میکنید، تغییرات فوق زمانی تاثیر داده خواهند شد که یکبار این برنامه را بسته و مجددا اجرا کنید.
چرا به ابزارهای مدیریت حالت نیاز داریم؟
به محض رد شدن از مرز پیاده سازی امکانات اولیهی یک برنامه، نیاز به ابزارهای مدیریت حالت نمایان میشوند؛ خصوصا زمانیکه نیاز است با اطلاعات قابل توجهی سر و کار داشت. مهمترین دلیل استفادهی از یک ابزار مدیریت حالت، مدیریت منطق تجاری برنامه است. منطق نمایشی برنامه مرتبط است به نحوهی نمایش اجزای آن در صفحه؛ مانند نمایش یک صفحهی مودال، تغییر رنگ عناصر با عبور کرسر ماوس از روی آنها و در کل منطقی که مرتبط و یا وابستهی به هدف اصلی برنامه نیست. از سوی دیگر منطق تجاری برنامه مرتبط است با مدیریت، تغییر و ذخیره سازی اشیاء تجاری مورد نیاز آن؛ مانند اطلاعات حساب کاربری شخص و دریافت اطلاعات برنامه از یک API که مختص به برنامهی خاص ما است و به همین دلیل نیاز به ابزاری برای مدیریت بهینهی آن وجود دارد. برای مثال اینکه در کجا باید منطق تجاری و نمایشی را به هم متصل کرد، میتواند چالش بر انگیر باشد. چگونه باید اطلاعات کاربر را ذخیره کرد؟ چگونه React باید متوجه شود که اطلاعات ما تغییر کردهاست و در نتیجهی آن کامپوننتی را مجددا رندر کند؟ یک ابزار مدیریت حالت، تمام این مسایل را به نحو یکدستی در سراسر برنامه، مدیریت میکند.
اگر از یک ابزار مدیریت حالت استفاده نکنیم، مجبور خواهیم شد تمام اطلاعات منطق تجاری را در داخل state کامپوننتها ذخیره کنیم که توصیه نمیشود؛ چون مقیاس پذیر نیست. برای مثال فرض کنید قرار است تمام اطلاعات state را داخل یک کامپوننت ذخیره کنیم. هر زمانیکه بخواهیم این state را از طریق یک کامپوننت فرزند تغییر دهیم، نیاز خواهد بود این اطلاعات را به والد آن کامپوننت ارسال کنیم که اگر از تعداد زیادی کامپوننت تو در تو تشکیل شده باشد، زمانبر و به همراه کدهای تکراری زیادی خواهد بود. همچنین اینکار سبب رندر مجدد کل برنامه با هر تغییری در state آن میشود که غیرضروری بوده و کارآیی برنامه را کاهش میدهد. به علاوه در این بین مشخص نیست هر قسمت از state، از کدام کامپوننت تامین شدهاست. به همین جهت نیاز به روشی برای مدیریت حالت در بین کامپوننتهای برنامه وجود دارد.
داشتن تنها یک محل برای ذخیره سازی state در برنامه
همانطور که در قسمت 8 ترکیب کامپوننتها در سری React 16x بررسی کردیم، هر کامپوننت در React، دارای state خاص خودش است و این state از سایر کامپوننتها کاملا مستقل و ایزولهاست. این مورد با بزرگتر شدن برنامه و برقراری ارتباط بین کامپوننتها، مشکل ایجاد میکند. برای مثال اگر بخواهیم دکمهای را در صفحه قرار داده و توسط این دکمه درخواست صفر شدن مقدار هر کدام از شمارشگرها را صادر کنیم، با صفر کردن value هر کدام از این کامپوننتها، اتفاقی رخ نمیدهد. چون state محلی این کامپوننتها، با سایر اجزای صفحه به اشتراک گذاشته نمیشود و باید آنرا تبدیل به یک controlled component کرد، بطوریکه دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود. تازه این روش در مورد کامپوننتهایی صدق میکند که رابطهی والد و فرزندی بین آنها وجود دارد. اگر چنین رابطهای وجود نداشت، باید state را به یک سطح بالاتر انتقال داد. برای مثال باید state کامپوننت Counters را به والد آن که کامپوننت App است، منتقل کرد. پس از آن چون کامپوننتهای ما، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت. این مورد هم مانند مثال انتقال اطلاعات کاربر لاگین شدهی به سیستم، به تمام زیر قسمتهای برنامه، نیاز به ارسال اطلاعات از طریق props یک کامپوننت، به کامپوننت بعدی را دارد و به همین ترتیب برای مابقی که به props drilling مشهور است و روش پسندیدهای نیست.
Redux چیست؟ ذخیره سازی کل درخت state یک برنامه، در یک محل. به این ترتیب به یک شیء جاوا اسکریپتی بزرگ خواهیم رسید که در برگیرندهی تمام state برنامهاست. یکی از مزایای آن امکان serialize و deserialize کل این شیء، به سادگی است. برای مثال توسط متد JSON.stringify میتوان آنرا در جائی ذخیره کرد و سپس آنرا به صورت یک شیء جاو اسکریپتی در زمانی دیگر بازیابی کرد. یکی از مزایای آن، امکان بازیابی دقیق شرایط کاربری است که دچار مشکل شدهاست و سپس دیباگ و رفع مشکل او، در زمانی دیگر.
تاریخچهای از سیستمهای مدیریت حالت
همه چیز با AngularJS 1x شروع شد که از data binding دو طرفه پشتیبانی میکرد. هرچند این روش برای همگام نگه داشتن View و مدل برنامه، مفید است، اما در Viewهای پیچیده، برنامه را کند میکند. در همین زمان فیسبوک، روش مدیریت حالتی را به نام Flux ارائه داد که از data binding یک طرفه پشتیبانی میکرد. به این معنا که در این روش، همواره اطلاعات از View به مدل، جریان پیدا میکند. کار کردن با آن سادهاست؛ چون نیازی نیست حدس زده شود که اکنون جریان اطلاعات از کدام سمت است. اما مشکل آن عدم هماهنگی model و view، در بعضی از حالات است. Flux از این جهت به وجود آمد که مدیریت حالت در برنامههای React آن زمان، پیچیده بود و مقیاس پذیری کمی داشت (پیش از ارائهی Context و Hooks). در کل Flux صرفا یکسری الگوی مدیریت حالت را بیان میکند و یک کتابخانهی مجزا نیست. بر مبنای این الگوها و قراردادها، میتوان کتابخانههای مختلفی را ایجاد کرد. از این رو در سال 2015، کتابخانههای زیادی مانند Reflux, Flummox, MartyJS, Alt, Redux و غیره برای پیاده سازی آن پدید آمدند. در این بین، کتابخانهی Redux ماندگار شد و پیروز این نبرد بود!
توابع خالص و ناخالص (Pure & Impure Functions)
پیش از شروع بحث، نیاز است با یکسری از واژهها مانند توابع خالص و ناخالص آشنا شد. این نکات از این جهت مهم هستند که Redux فقط با توابع خالص کار میکند.
توابع خالص: تعدادی آرگومان را دریافت کرده و بر اساس آنها، مقداری را باز میگردانند.
// Pure const add = (a, b) => { return a + b; }
توابع ناخالص: این نوع توابع سبب تغییراتی در متغیرهایی خارج از میدان دید خود میشوند و یا به همراه یک سری اثرات جانبی (side effects) مانند تعامل با دنیای خارج (وجود یک console.log در آن تابع و یا دریافت اطلاعاتی از یک API خارجی) هستند.
// Impure const b; const add = (a) => { return a + b; }
// Impure const add = (a, b) => { console.log('lolololol'); return a + b; }
// Impure const add = (a, b) => { Api.post('/add', { a, b }, (response) => { // Do something. }); };
روشهایی برای جلوگیری از تغییرات در اشیاء در جاوا اسکریپت
ایجاد تغییرات در آرایهها و اشیاء (Mutating arrays and objects) نیز ناخالصی ایجاد میکند؛ از این جهت که سبب تغییراتی در دنیای خارج (خارج از میدان دید تابع) میشویم. به همین جهت نیاز به روشهایی وجود دارد که از این نوع تغییرات جلوگیری کرد:
// Copy object const original = { a: 1, b: 2 }; const copy = Object.assign({}, original);
برای مثال در React، برای انجام رندر نهایی، در پشت صحنه کار مقایسهی اشیاء صورت میگیرد. به همین جهت اگر همان شیءای را که ردیابی میکند تغییر دهیم، دیگر نمیتواند به صورت مؤثری فقط قسمتهای تغییر کردهی آنرا تشخیص داده و کار رندر را فقط بر اساس آنها انجام دهد و مجبور خواهد شد کل یک شیء را بارها و بارها رندر کند که اصلا بهینه نیست. به همین جهت، ایجاد تغییرات مستقیم در شیءای که به state آن انتساب داده میشود، مجاز نیست.
متد Object.assign، چندین شیء را نیز میتواند با هم یکی کند و شیء جدیدی را تشکیل دهد:
// Extend object const original = { a: 1, b: 2 }; const extension = { c: 3 }; const extended = Object.assign({}, original, extension);
// Copy object const original = { a: 1, b: 2 }; const copy = { ...original };
// Extend object const original = { a: 1, b: 2 }; const extension = { c: 3 }; const extended = { ...original, ...extension };
روشهایی برای جلوگیری از تغییرات در آرایهها در جاوا اسکریپت
متد slice آرایهها نیز بدون ذکر آرگومانی، یک کپی از آرایهی اصلی را ایجاد میکند:
// Copy array const original = [1, 2, 3]; const copy = [1, 2, 3].slice();
// Copy array const original = [1, 2, 3]; const copy = [ ...original ];
// Extend array const original = [1, 2, 3]; const extended = original.concat(4); const moreExtended = original.concat([4, 5]);
معادل قطعه کد فوق در ES-6 و به همراه spread operator آن به صورت زیر است:
// Extend array const original = [1, 2, 3]; const extended = [ ...original, 4 ]; const moreExtended = [ ...original, ...extended, 5 ];
مفاهیم ابتدایی Redux
در Redux برای ایجاد تغییرات در شیء کلی state، از مفهومی به نام dispatch actions استفاده میشود. action در اینجا به معنای رخدادن چیزی است؛ مانند کلیک بر روی یک دکمه و یا دریافت اطلاعاتی از یک API. در این حالت مقایسهای بین وضعیت قبلی state و وضعیت فعلی آن صورت میگیرد و تغییرات مورد نیاز جهت اعمال به UI، محاسبه خواهند شد.
اصلیترین جزء Redux، تابعی است به نام Reducer. این تابع، یک تابع خالص است و دو آرگومان را دریافت میکند:
تابع Reducer، بر اساس action و یا رخدادی، ابتدا کل state برنامه را دریافت میکند و سپس خروجی آن بر اساس منطق این تابع، یک state جدید خواهد بود. اکنون که این state جدید را داریم، برنامهی React ما میتواند به تغییرات آن گوش فرا داده و بر اساس آن، UI را به روز رسانی کند. به این ترتیب کار اصلی مدیریت state، به خارج از برنامهی React منتقل میشود.
در این تصویر، تابع action creator را هم ملاحظه میکند که کاملا اختیاری است. یک action میتواند یک رشته و یا یک عدد باشد. با پیچیده شدن برنامه، نیاز به ارسال یکسری متادیتا و یا اطلاعات بیشتری از اکشن رسیدهاست. کار action creator، ایجاد شیء action، به صورت یک دست و یکنواخت است تا دیگر نیازی به ایجاد دستی آن نباشد.
مزایای کار با Redux
- داشتن یک مکان مرکزی برای ذخیره سازی کلی حالت برنامه (به آن «source of truth» و یا store هم گفته میشود): به این ترتیب مشکل ارسال خواص در بین کامپوننتهای عمیق و چند سطحی، برطرف شده و هر زمانیکه نیاز بود، از آن اطلاعاتی را دریافت و یا با قالب خاصی، آنرا به روز رسانی میکنند.
- رسیدن به بهروز رسانیهای قابل پیش بینی state: هرچند در حالت کار با Redux، یک شیء بزرگ جاوا اسکریپتی، کل state برنامه را تشکیل میدهد، اما امکان کار مستقیم با آن و تغییرش وجود ندارد. به همین جهت است که برای کار با آن، باید رویدادی را از طریق actionها به تابع Reducer آن تحویل داد. چون Reducer یک تابع خالص است، با دریافت یک سری ورودی مشخص، همواره یک خروجی مشخص را نیز تولید میکند. به همین جهت قابلیت ضبط و تکرار را پیدا میکند؛ همان بحث serialize و deseriliaze، توسط ابزاری مانند: logrocket. به علاوه قابلیت undo و redo را نیز میتوان به این ترتیب پیاده سازی کرد (state جدید محاسبه شده، مشخص است، کل state قبلی را نیز داریم یا میتوان ذخیره کرد و سپس برای undo، آنرا جایگزین state جدید نمود). افزونهی redux dev tools نیز قابلیت import و export کل state را به همراه دارد.
- چون تابع Reducer، یک تابع خالص است و همواره خروجیهای مشخصی را به ازای ورودیهای مشخصی، تولید میکند، آزمایش کردن، پیاده سازی و حتی logging آن نیز سادهتر است. در این بین حتی یک افزونهی مخصوص نیز برای دیباگ آن تهیه شدهاست: redux-devtools-extension. تابع خالص، تابعی است که به همراه اثرات جانبی نیست (side effects)؛ به همین جهت عملکرد آن کاملا قابل پیش بینی بوده و آزمون پذیری آن به دلیل نداشتن وابستگیهای خارجی، بسیار بالا است.
Context API خود React چطور؟
در قسمت 33 سری React 16x، مفهوم React Context را بررسی کردیم. پس از معرفی آن با React 16.3، مقالات زیادی منتشر شدند که ... Redux مردهاست (!) و یا بجای Redux از React context استفاده کنید. اما واقعیت این است که React Redux در پشت صحنه از React context استفاده میکند و تابع connect آن دقیقا به همین زیر ساخت متصل میشود.
کار با Redux مزایایی مانند کارآیی بالاتر، با کاهش رندرهای مجدد کامپوننتها، دیباگ سادهتر با افزونههای اختصاصی و همچنین سفارشی سازی، مانند نوشتن میانافزارها را به همراه دارد. اما شاید واقعا نیازی به تمام این امکانات را هم نداشته باشید؛ اگر هدف، صرفا انتقال سادهتر اطلاعات بوده و برنامهی مدنظر نیز کوچک است. React Context برخلاف Redux، نگهدارندهی state نیست و بیشتر هدفش محلی برای ذخیره سازی اطلاعات مورد استفادهی در چندین و چند کامپوننت تو در تو است. هرچند شبیه به Redux میتوان اشارهگرهایی از متدها را به استفاده کنندگان از آن ارسال کرد تا سبب بروز رویدادها و اکشنهایی در کامپوننت تامین کنندهی Contrext شوند (یا یک کتابخانهی ابتدایی شبیه به Redux را توسط آن تهیه کرد). بنابراین برای انتخاب بین React Context و Redux باید به اندازهی برنامه، تعداد نفرات تیم، آشنایی آنها با مفاهیم Redux دقت داشت.
یک اینترفیس را به اسم ICustomService ایجاد کردم که یک Prop به اسم InjectType دارد و مشخص میکند به چه صورتی این سرویس به ServiceCollection تزریق شود. از طرفی با استفاده از Order، الویت اضافه شدن سرویس به ServiceCollection را مشخص میکنیم و در نهایت با ImplementationType مشخص میکنیم سرویسی که اضافه شده، باید به یک اینترفیس Map شود یا خیر؟ اما مهمتر از اینکه ویژگیهای تزریق وابستگی مشخص شود، مشخص میکند چه سرویسهایی توسط ما اضافه شدهاند و از سرویسهای nop تفکیک میشوند.
namespace Nop.Services { public interface ICustomService { protected InjectType Inject { get; } protected int Order { get; } protected ImplementationType implementationType { get; } } public enum ImplementationType { WithInterface = 0, WithoutInterface = 1 } public enum InjectType { Scopped=0, Transit=1, SingleTon=2 } }
قانون اول
برای هر سرویسی که ایجاد میکنیم و میخواهیم به DI معرفی کنیم، آن سرویس باید ICustomService را پیاده سازی کرده باشد؛ دقیقا به خاطر دو دلیلی که در بالا به آنها اشاره شد.
قانون دوم
هر کلاسی که Interface مرتبط به سرویسها را پیاده سازی میکند، باید prop InjectType را در سازندهی خودش مقدار دهی کند. بدین شکل متوجه میشویم از چه طریقی باید تزریق انجام شود. تا اینجا یک چارچوب را مشخص کردیم تا سرویسها را بتوانیم تشخیص دهیم\ اما هنوز کار اصلی باقی ماندهاست. برای نمونه میتوان کد زیر را در نظر گرفت :
namespace Nop.Services { public interface IMyCustomService: ICustomService { int ok(); } }
namespace Nop.Services { public class MyCustomService : IMyCustomService { public InjectType Inject { get; } public int Order { get; } public ImplementationType implementationType { get; } public MyCustomService() { implementationType = ImplementationType.WithInterface; Inject = InjectType.Scopped; Order = 1; } public int ok() { return 10; } } }
تعیین نقطه شروع
کد زیر در واقع نقطهی اتصال سرویسهای نوشته شده و اتمام کار تزریق وابستگی است. با توجه به پیاده سازیهای انجام شدهی توسط سرویسها میتوان با Reflection سرویسهای نوشته شده را تشخیص داد که در نهایت با ویژگیهایی که در سرویسها پیاده سازی شده موجود است، به ServiceCollection اضافه میشوند.
namespace Nop.Web.Framework.Infrastructure { public class CustomDependencyInjection : NopStartup { private static bool IsSubInterface(Type t1, Type t2) { if (!t2.IsAssignableFrom(t1)) return false; if (t1.BaseType == null) return true; return !t2.IsAssignableFrom(t1.BaseType); } public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) { //-------------Get All Services------------- var asm = AppDomain.CurrentDomain .GetAssemblies() .Single(x => x.FullName.Contains("Nop.Services")); //-------------find Services that inheriance of ICustomService------------- var types = asm.DefinedTypes.Where(x => IsSubInterface(x, typeof(ICustomService))); //-----------Get All Custom Service Classess------- var allRelatedClassServices = types .Where(x => x.IsClass) .OrderBy(x=>(Int32)x.GetProperty("Order") .GetValue(Activator.CreateInstance(x), null)); //-----------Get All Custom Service Interfaces------- var allRelatedInterfaceServices = types.Where(x => x.IsInterface); //-----------Matche Class Services To Related Interface Services------- TypeInfo interfaceService=null; foreach (var classService in allRelatedClassServices) { //-----------detect Implementation Type for service----------- var implementationValue = (ImplementationType)classService.GetProperty("implementationType") .GetValue(Activator.CreateInstance(classService), null); //-----------detect inject type for service----------- var InjectValue = (InjectType)classService.GetProperty("Inject") .GetValue(Activator.CreateInstance(classService), null); //-----------get related interface for service class----------- if (implementationValue == ImplementationType.WithInterface) interfaceService = allRelatedInterfaceServices.Single(x => x.Name == $"I{classService.Name}"); //----------finally Add Custom Service To Service Collection----------- switch (InjectValue) { case InjectType.Scopped: if(interfaceService!=null) services.AddScoped(interfaceService, classService); else services.AddScoped(classService); break; case InjectType.Transit: if (interfaceService != null) services.AddTransient(interfaceService, classService); else services.AddTransient(classService); break; case InjectType.SingleTon: if (interfaceService != null) services.AddSingleton(interfaceService, classService); else services.AddSingleton(classService); break; default: break; } interfaceService = null; } } } }
لینک گیتهاب
SQL Indexing
دلیل استفاده از ایندکس چیست؟
این سوالی است که ممکن است هر توسعه دهندهای به آن در ابتدا پاسخ دهد: «جهت بالابردن سرعت و کارآیی!» حال اگر بپرسیم چگونه؟ توضیحات چندان دقیقی ارائه نمیشود.
ایندکس چیست؟
ایندکس شیءای از دیتابیس است میتواند برروی یک یا چند ستون ایجاد شود (تا 16 ستون). هنگامیکه ایندکسی ایجاد میگردد، ساختار دادهای (BTree) جهت بهینه سازی عملیات مقایسه نیز ایجاد میشود. اس کیو ال سرور بدون داشتن ایندکس، برای دریافت اطلاعات درخواستی مجبور است کل ردیفهای جدول را جستجو نماید. این کار مانند این است که شما بدون اطلاع از شماره صفحه (محل) عنوان درخواستی، به دنبال آن در صفحات یک کتاب باشید. حال اگر به ایندکس (فهرست) کتاب مراجعه کنید به سرعت و حداقل اتلاف وقت میتوانید محل یا شماره صفحهی عنوان مورد نظر را، بدون جستجوی کلیهی صفحات کتاب، پیدا کنید و به آن مراجعه کنید. ایندکس جدول نیز اجازه میدهد بدون جستجوی کلیه رکوردها، رکورد مورد نظر را دریافت نمایید.مثال:
SELECT [computer_id],[nic_device_id],[nic_vendor_id],[nic_desc] FROM [eXpress].[dbo].[nics]
فرض کنید در جدول بالا ایندکس گذاری انجام نشده باشد و قصد داشته باشید رکوردهایی را دریافت نمایید که در آنها computer_id>5100 باشد. اس کیو ال سرور مجبور است کلیه رکوردهای جدول را جهت اعمال شرط بررسی نماید.
حال، برروی ستون computer_id ایندکسی را اعمال مینماییم و شرط computer_id>5100 را مجدد بررسی میکنیم. اس کیو ال از محل رکوردهای با مقادیر بزرگتر از 5100 اطلاع دارد و از جستجوی کل جدول اجتناب میکند. چرا؟ بدلیل اینکه براساس این ستون مرتب شده است.
انواع ایندکس
دو نوع ایندکس اصلی وجود دارد: ایندکس خوشهای و ایندکس غیرخوشهای
ایندکس خوشهای
نحوهی ذخیره سازی فیزیکی رکوردها را تغییر میدهد. هنگامیکه یک ایندکس خوشهای را ایجاد میکنید، بر روی یک ستون (یا ترکیبی از چند ستون)، اس کیو ال سرور رکوردها را براساس ستون/ها بصورت صعودی مرتب شده (مانند یک دیکشنری که کلیه کلمات بصورت الفبایی قرار گرفتهاند) ذخیره مینماید.
بوسیله ایندکس زیر تمام رکوردها براساس ستون computer_id مرتب شده ذخیره میگردند.CREATE CLUSTERED INDEX [IX_CLUSTERED_COMPUTER_ID] ON [dbo].[nics] ([computer_id] ASC)
همانطور که اشاره شد، رکوردها بصورت مرتب شده براساس ستون انتخاب شدهی در
جدول نگهداری میشوند. اما این مرتب سازی توسط ساختار BTree بهشرح زیر انجام
خواهد شد. جدول زیر را در نظر داشته باشید:
فرض کنید بعد ایندکس گذاری ستون StudId جدول فوق، درخت BTree زیر ایجاد میگردد که این ساختار بهصورت جداگانهای بر روی دیسک ذخیره میگردد. در این درخت، مقدار گره سمت چپ ریشه از آن کمتر و مقدار گره سمت راست ریشه از آن بیشتر است (البته عکس این فرض نیز امکان پذیر است).
و سپس کوئریهای زیر را صادر میکنید:
Select * from student where studid = 103; Select * from student where studid = 107;
با ایندکس گذاری، کوئری اول، بعد از اولین عمل مقایسه و کوئری دوم بعد از 3 عمل مقایسه پیدا میشود؛ بهشرح زیر:
- مقایسه 107 با 103 و انتقال به گره سمت راست
- مقایسه 107 با 106 و انتقال به گره سمت راست
- مقایسه 107 با 107 و یافتن مقدار درخواستی و بازگشت رکورد
در صورتیکه تعداد رکوردها کم باشند، تفاوت کارآیی جداول دارای ایندکس و بدون ایندکس قابل لمس نخواهد بود.
ایندکس غیرخوشهای
این نوع ایندکس، تغییری در نحوهی ذخیره سازی رکوردها انجام نمیدهند. ولی شیء دیگری را که شامل ستون/هایی که قرار است ایندکس شوند و اشارهگر به رکورد (RID) هستند، در جدول ایجاد میکند. برای مثالی از ایندکس غیرخوشهای در دنیای واقعی، میتوان به فهرست انتهای کتابها که شامل عناوین و شماره صفحهی مربوطه میباشد، اشاره کرد.
نکته: RID به موقعیت فیزیکی رکورد اشاره خواهد کرد و شامل شناسه، شماره صفحه و تعداد رکوردهای در یک صفحه میباشد.
برای درک بهتر به سناریوی زیر دقت کنید:
کتابی داریم که شامل 1200 صفحه میباشد و فهرست مطالب آن شامل عناوین و
شماره صفحات عناوین میباشد. حال اگر عنوان درخواستی A در صفحات 700، 300،
800 قرار داشته باشد، برای رفتن به این صفحات، مراحل زیر را برای هر یک طی
خواهید کرد:
- یافتن شماره صفحه عنوان درخواستی با مراجعه به فهرست انتهای کتاب.
- در ادامه شما صفحهای را در میانهی کتاب، باز میکنید؛ چون عدد 700 مقداری از نصف 1200 برزگتر است.
- چند صفحه به جلو رفته، شماره صفحه 750 خواهد بود و هنوز به شرط مورد نظر نرسیدهاید.
- پس مجددا چند صفحه به عقب بازگشته تا به صفحهی مورد نظر، 700، برسید.
مراحل فوق برای یافتن عنوان A واقع شدهی در صفحه 700 انجام شد که همین مراحل نیز برای سایر صفحات میتواند انجام شود. در این مثال، صفحه فهرست مطالب کتاب، به ایندکس غیرخوشهای تعبیر خواهد شد.
این نوع ایندکسها جهت ستون هایی مفید هستند که مقادیر آن تکرار خواهد شد؛ مانند جدولی با بیش از چند میلیون رکورد که دارای ستون نوع حساب است، ولی تعداد نوع حساب منحصر بفرد محدودی را خواهد داشت. فرض کنید مقادیر منحصر بفرد، ستون نوع حساب A، B، C باشد. زمانیکه برروی این ستون ایندکس گذاری غیرخوشهای انجام میشود، فهرست ما دارای سه عنوان خواهد بود که هر عنوان به صفحات مربوط به همان عنوان اشاره خواهد کرد. به این ترتیب هنگامیکه برروی نوع حساب عملیات جستجو انجام شود، اس کیو ال میداند رکوردهای نوع حساب مثلا A در کدام صفحات قرار دارد و بهسرعت رکوردهای متناظر را پیدا مینماید.
A: 300, 700, 800 B: 100, 110 C: 600, 1200
ایندکس غیرخوشه ای توسط دستور زیر ایجاد میگردد:
CREATE NONCLUSTERED INDEX [IX_NONCLUSTERED_COMPUTER_ID] ON [dbo].[nics] ([computer_id] ASC)
نکته: یک جدول میتواند بیش از یک ایندکس غیرخوشهای و فقط و
فقط یک ایندکس خوشهای داشته باشد.
ارتباط ایندکس خوشهای و غیر خوشهای
اشارهگر به رکورد (RID) در یک جدول دارای ایندکس خوشهای، کلید ایندکس خوشهای خواهد بود.
مزایا و معایب ایندکس
مزایا:جدولی بدون ایندکس خوشهای، heap table شناخته میشود. یک جدول هیپ، دادهی مرتب شده نخواهد داشت و به منظور دریافت اطلاعات، اس کیو ال سرور مجبور است کل ردیفهای جدول را بررسی نماید که این عملیات Scan نامیده میشود. ولی در صورت استفاده از ایندکس خوشهای برروی یک ستون، اس کیو ال، جهت یافتن اطلاعات مورد جستجو با توجه به BTree عملیات جستجو را از ریشه شروع، از شاخهها عبور کرده و به برگ که همان اطلاعات درخواستی است میرسد که این عملیات Seek نامیده میشود. عملیات Seek طبیعتا از Scan سریعتر است.
ایندکس غیرخوشهای، شامل مجموعهای از ستونها و ارجاعاتی به رکوردها یا کلید ایندکس خوشهای است (ارتباط بین ایندکس غیر خوشهای با خوشهای). بهدلیل حجم کم این نوع ایندکس، میتواند ردیفها یا کلیدهای ایندکس خوشه ای بیشتری در صفحهی ایندکس وجود داشته باشد که باعث افزایش کارآیی I/O میگردد.
معایب:
ایندکس گذاری، در طی عملیات درج، ویرایش و حذف، باعث سربار میگردد. هنگامیکه تغییری بر روی رکوردهای جدول انجام میشود، سبب تغییراتی نیز بر روی ایندکسها میگردد (هنگامیکه برگهای از کتابی جدا شود، نیاز است شماره صفحات و فهرست انتهایی کتاب مجددا بهروز گردد) که این تغییرات باعث ایجاد هزینه میشود. بنابراین خیلی اهمیت دارد که هنگام طراحی ایندکس گذاری به سربارها نیز توجه کنید. بهعنوان مثال هنگامیکه توسط دستور Delete رکوردی را از جدولی حذف نمایید، نیاز است رکوردها مجددا مرتب شوند که این یک سربار است.
ایندکس گذاری ، سرباری بنام bookmark lookup دارد. bookmark lookup فرآیندی جهت یافتن سایر ستونهایی است که در ایندکس گذاری وجود ندارند و براساس RID هستند.
CREATE TABLE tableName ( jsonData CHAR(250) -- or VARCHAR, TEXT, BLOB ); INSERT INTO tableName VALUES ( '{ "name": "User1", "age": 41}' ); SELECT * FROM tableName; { "name": "User1", "age": 41}
SELECT * FROM tableName WHERE jsonData REGEXP 'User1';
CREATE TABLE tableName ( jsonData JSON ); INSERT INTO tableName VALUES ( '{ "name": "User1", "age": 41, "name": "User2"}' ); SELECT * FROM tableName; {"age": 41, "name": "User2"}
- ابتدا بررسی خواهد شد که سند JSON معتبر باشد؛ در غیر اینصورت ذخیرهسازی با مشکل مواجه خواهد شد.
- از فیلدهایی که کلید تکراری دارند، صرفنظر خواهند شد. در مثال بالا دوبار فیلد name را مقداردهی کردهایم. در اینجالت key/value دوم لحاظ شدهاست. البته میبایستی اصل first key wins لحاظ میشد، اما این مورد به عنوان یک باگ گزارش شدهاست و در نسخههای 8 به بعد رفع شدهاست (https://forums.mysql.com/read.php?3,660500,660500 - https://bugs.mysql.com/bug.php?id=86866).
- فاصلههای اضافی بین کلیدها حذف شدهاند.
- برای جستجوی بهتر، کلیدهای آبجکت JSON به صورت مرتب شده ذخیره شدهاند.
{ "id": "1", "sku": "asdf123", "name": "Lorem ipsum jacket", "price": 12.45, "discount": 10, "offerEnd": "October 5, 2020 12:11:00", "new": false, "rating": 4, "saleCount": 54, "category": ["fashion", "men"], "tag": ["fashion", "men", "jacket", "full sleeve"], "variation": [ { "color": "white", "image": "/assets/img/product/fashion/1.jpg", "size": [ { "name": "x", "stock": 3 }, { "name": "m", "stock": 2 }, { "name": "xl", "stock": 5 } ] }, { "color": "black", "image": "/assets/img/product/fashion/8.jpg", "size": [ { "name": "x", "stock": 4 }, { "name": "m", "stock": 7 }, { "name": "xl", "stock": 9 }, { "name": "xxl", "stock": 1 } ] }, { "color": "brown", "image": "/assets/img/product/fashion/3.jpg", "size": [ { "name": "x", "stock": 1 }, { "name": "m", "stock": 2 }, { "name": "xl", "stock": 4 }, { "name": "xxl", "stock": 0 } ] } ], "image": [ "/assets/img/product/fashion/1.jpg", "/assets/img/product/fashion/3.jpg", "/assets/img/product/fashion/6.jpg", "/assets/img/product/fashion/8.jpg", "/assets/img/product/fashion/9.jpg" ], "description": { "shortDescription": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur.", "fullDescription": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" } }
SELECT JSON_PRETTY( JSON_EXTRACT(data, "$.category") ) FROM experiments.productMetadata; /* [ "fashion", "men" ] [ "fashion", "women" ] [ "fashion", "men" ] */
JSON_EXTRACT(data, "$.tag[1]") JSON_EXTRACT(data, "$.description.shortDescription")
SELECT JSON_EXTRACT(data, "$.*.shortDescription") FROM experiments.productMetadata;
SELECT JSON_KEYS(data) FROM experiments.productMetadata; -- ["id", "new", "sku", "tag", "name", "image", "price", "rating", "category", "discount", "offerEnd", "saleCount", "variation", "description"] -- ["id", "new", "sku", "tag", "name", "image", "price", "rating", "category", "discount", "saleCount", "variation", "description"]
SELECT JSON_KEYS(data, "$.description") FROM experiments.productMetadata; -- ["fullDescription", "shortDescription"] -- ["fullDescription", "shortDescription"]
SELECT JSON_CONTAINS(data, "10", "$.discount") FROM experiments.productMetadata; -- 1 -- 0
SELECT JSON_CONTAINS_PATH(data, "one", "$.description", "$.address", "$.website") FROM experiments.productMetadata;
SELECT JSON_SEARCH(data, 'one', 'fashion') FROM experiments.productMetadata; -- "$.tag[0]" -- "$.tag[0]"
اگر به کدهای مثال رسمی ASP.NET Identity نگاهی بیندازید، میبینید که کلاس مربوط به جدول کاربران ApplicationUser نام دارد، ولی در سیستم IRIS نام آن User است. بهتر است که ما هم نام کلاس خود را از User به ApplicationUser تغییر دهیم چرا که مزایای زیر را به دنبال دارد:
1- به راحتی میتوان کدهای مورد نیاز را از مثال Identity کپی کرد.
2- در سیستم Iris، بین کلاس User متعلق به پروژه خودمان و User مربوط به HttpContext تداخل رخ میداد که با تغییر نام کلاس User دیگر این مشکل را نخواهیم داشت.
برای این کار وارد پروژه Iris.DomainClasses شده و نام کلاس User را به ApplicationUser تغییر دهید. دقت کنید که این تغییر نام را از طریق Solution Explorer انجام دهید و نه از طریق کدهای آن. پس از این تغییر ویژوال استودیو میپرسد که آیا نام این کلاس را هم در کل پروژه تغییر دهد که شما آن را تایید کنید.
برای آن که نام جدول Users در دیتابیس تغییری نکند، وارد پوشهی Entity Configuration شده و کلاس UserConfig را گشوده و در سازندهی آن کد زیر را اضافه کنید:
ToTable("Users");
برای نصب ASP.NET Identity دستور زیر را در کنسول Nuget وارد کنید:
Get-Project Iris.DomainClasses, Iris.Datalayer, Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.EntityFramework
همچنین بهتر است که به کلاس CustomRole، یک property به نام Description اضافه کنید تا توضیحات فارسی نقش مورد نظر را هم بتوان ذخیره کرد:
public class CustomRole : IdentityRole<int, CustomUserRole> { public CustomRole() { } public CustomRole(string name) { Name = name; } public string Description { get; set; } }
نکته: پیشنهاد میکنم که اگر میخواهید مثلا نام CustomRole را به IrisRole تغییر دهید، این کار را از طریق find and replace انجام ندهید. با همین نامهای پیش فرض کار را تکمیل کنید و سپس از طریق خود ویژوال استودیو نام کلاس را تغییر دهید تا ویژوال استودیو به نحو بهتری این نامها را در سرتاسر پروژه تغییر دهد.
سپس کلاس ApplicationUser پروژه IRIS را باز کرده و تعریف آن را به شکل زیر تغییر دهید:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
اکنون میتوانید propertyهای Id، UserName، PasswordHash و Email را حذف کنید؛ چرا که در کلاس پایه IdentityUser تعریف شده اند.
وارد Iris.DataLayer شده و کلاس IrisDbContext را به شکل زیر ویرایش کنید:
public class IrisDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IUnitOfWork
public DbSet<ApplicationUser> Users { get; set; }
public IrisDbContext() : base("IrisDbContext") { }
همچنین درون متد OnModelCreating کدهای زیر را پس از فراخوانی متد (base.OnModelCreating(modelBuilder جهت تعیین نام جداول دیتابیس بنویسید:
modelBuilder.Entity<CustomRole>().ToTable("AspRoles"); modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims"); modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles"); modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins");
Add-Migration UpdateDatabaseToAspIdentity
public partial class UpdateDatabaseToAspIdentity : DbMigration { public override void Up() { CreateTable( "dbo.UserClaims", c => new { Id = c.Int(nullable: false, identity: true), UserId = c.Int(nullable: false), ClaimType = c.String(), ClaimValue = c.String(), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.UserLogins", c => new { LoginProvider = c.String(nullable: false, maxLength: 128), ProviderKey = c.String(nullable: false, maxLength: 128), UserId = c.Int(nullable: false), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId }) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.UserRoles", c => new { UserId = c.Int(nullable: false), RoleId = c.Int(nullable: false), ApplicationUser_Id = c.Int(), }) .PrimaryKey(t => new { t.UserId, t.RoleId }) .ForeignKey("dbo.Users", t => t.ApplicationUser_Id) .ForeignKey("dbo.AspRoles", t => t.RoleId, cascadeDelete: true) .Index(t => t.RoleId) .Index(t => t.ApplicationUser_Id); CreateTable( "dbo.AspRoles", c => new { Id = c.Int(nullable: false, identity: true), Description = c.String(), Name = c.String(nullable: false, maxLength: 256), }) .PrimaryKey(t => t.Id) .Index(t => t.Name, unique: true, name: "RoleNameIndex"); AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "SecurityStamp", c => c.String()); AddColumn("dbo.Users", "PhoneNumber", c => c.String()); AddColumn("dbo.Users", "PhoneNumberConfirmed", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "TwoFactorEnabled", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "LockoutEndDateUtc", c => c.DateTime()); AddColumn("dbo.Users", "LockoutEnabled", c => c.Boolean(nullable: false)); AddColumn("dbo.Users", "AccessFailedCount", c => c.Int(nullable: false)); } public override void Down() { DropForeignKey("dbo.UserRoles", "RoleId", "dbo.AspRoles"); DropForeignKey("dbo.UserRoles", "ApplicationUser_Id", "dbo.Users"); DropForeignKey("dbo.UserLogins", "ApplicationUser_Id", "dbo.Users"); DropForeignKey("dbo.UserClaims", "ApplicationUser_Id", "dbo.Users"); DropIndex("dbo.AspRoles", "RoleNameIndex"); DropIndex("dbo.UserRoles", new[] { "ApplicationUser_Id" }); DropIndex("dbo.UserRoles", new[] { "RoleId" }); DropIndex("dbo.UserLogins", new[] { "ApplicationUser_Id" }); DropIndex("dbo.UserClaims", new[] { "ApplicationUser_Id" }); DropColumn("dbo.Users", "AccessFailedCount"); DropColumn("dbo.Users", "LockoutEnabled"); DropColumn("dbo.Users", "LockoutEndDateUtc"); DropColumn("dbo.Users", "TwoFactorEnabled"); DropColumn("dbo.Users", "PhoneNumberConfirmed"); DropColumn("dbo.Users", "PhoneNumber"); DropColumn("dbo.Users", "SecurityStamp"); DropColumn("dbo.Users", "EmailConfirmed"); DropTable("dbo.AspRoles"); DropTable("dbo.UserRoles"); DropTable("dbo.UserLogins"); DropTable("dbo.UserClaims"); } }
AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false, defaultValue:true));
Update-Database
Get-Project Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.Owin
باز از پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer کلاسهای ApplicationRoleManager، ApplicationSignInManager، ApplicationUserManager، CustomRoleStore، CustomUserStore، EmailService و SmsService را به پوشه EFServcies پروژهی Iris.ServiceLayer کپی کنید.
x.For<IIdentity>().Use(() => (HttpContext.Current != null && HttpContext.Current.User != null) ? HttpContext.Current.User.Identity : null); x.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<IrisDbContext>(); x.For<IrisDbContext>().HybridHttpOrThreadLocalScoped() .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>()); x.For<DbContext>().HybridHttpOrThreadLocalScoped() .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>()); x.For<IUserStore<ApplicationUser, int>>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>(); x.For<IRoleStore<CustomRole, int>>() .HybridHttpOrThreadLocalScoped() .Use<RoleStore<CustomRole, int, CustomUserRole>>(); x.For<IAuthenticationManager>() .Use(() => HttpContext.Current.GetOwinContext().Authentication); x.For<IApplicationSignInManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationSignInManager>(); x.For<IApplicationRoleManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationRoleManager>(); // map same interface to different concrete classes x.For<IIdentityMessageService>().Use<SmsService>(); x.For<IIdentityMessageService>().Use<IdentityEmailService>(); x.For<IApplicationUserManager>().HybridHttpOrThreadLocalScoped() .Use<ApplicationUserManager>() .Ctor<IIdentityMessageService>("smsService").Is<SmsService>() .Ctor<IIdentityMessageService>("emailService").Is<IdentityEmailService>() .Setter<IIdentityMessageService>(userManager => userManager.SmsService).Is<SmsService>() .Setter<IIdentityMessageService>(userManager => userManager.EmailService).Is<IdentityEmailService>(); x.For<ApplicationUserManager>().HybridHttpOrThreadLocalScoped() .Use(context => (ApplicationUserManager)context.GetInstance<IApplicationUserManager>()); x.For<ICustomRoleStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomRoleStore>(); x.For<ICustomUserStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>();
اگر ()HttpContext.Current.GetOwinContext شناسایی نمیشود دلیلش این است که متد GetOwinContext یک متد الحاقی است که برای استفاده از آن باید پکیج نیوگت زیر را نصب کنید:
Install-Package Microsoft.Owin.Host.SystemWeb
تغییرات Iris.Web
using System; using Iris.Servicelayer.Interfaces; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; using Owin; using StructureMap; namespace Iris.Web { public class Startup { public void Configuration(IAppBuilder app) { configureAuth(app); } private static void configureAuth(IAppBuilder app) { ObjectFactory.Container.Configure(config => { config.For<IDataProtectionProvider>() .HybridHttpOrThreadLocalScoped() .Use(() => app.GetDataProtectionProvider()); }); //ObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase(); // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = ObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity() } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process. app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); // Enables the application to remember the second login verification factor such as phone or email. // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from. // This is similar to the RememberMe option when you log in. app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); app.CreatePerOwinContext( () => ObjectFactory.Container.GetInstance<IApplicationUserManager>()); // Uncomment the following lines to enable logging in with third party login providers //app.UseMicrosoftAccountAuthentication( // clientId: "", // clientSecret: ""); //app.UseTwitterAuthentication( // consumerKey: "", // consumerSecret: ""); //app.UseFacebookAuthentication( // appId: "", // appSecret: ""); //app.UseGoogleAuthentication( // clientId: "", // clientSecret: ""); } } }
تا به این جای کار اگر پروژه را اجرا کنید نباید هیچ مشکلی مشاهده کنید. در بخش بعدی کدهای مربوط به کنترلرهای ورود، ثبت نام، فراموشی کلمه عبور و ... را با سیستم Identity پیاده سازی میکنیم.