مطالب
احراز هویت مبتنی بر فرم در شیرپوینت
از دغدغه‌های همیشگی در راه اندازی پرتال‌های مبتنی بر شیرپوینت سیستم احراز هویت آن است. این سیستم بصورت پیش فرض بر مبنای Windows Authentication است و ناگفته پیداست این نوع احراز هویت تنها در شبکه‌های محلی کاربرد دارد آنهم در صورتی که همه کاربران و سطوح دسترسی، بدرستی در AD تعریف شده باشد و نیز
یک سری مشکلات دیگر که بیشتر به توسعه شیرپوینت در شرکت و انتقال آن و انطباق آن با محیط پروژه برمیگردد.
به عبارت دیگر شما به عنوان یک توسعه دهنده ویا نَصاب(!) شیرپوینت خیلی نباید درگیر سیستم احراز هویت پیش فرض مشتری بشوید. برای اینکار بهترین گزینه استفاده از احراز هویت بر مبنای فرم (Form Based Authentication - FBA) است که برای ما برنامه نویسان Asp.net بسیار آشناست؛ سیستم احراز هویتی خوش دست و فراگیر با قاعده‌های مشخص.

متاسفانه اکثر راهکارهایی که در وب پیرامون راه اندازی FBA در شیرپوینت معرفی شده‌اند دارای اشکالات ریز و درشتی هستند و یا اینکه یک یا چند گام از فرایند را توضیح نداده‌اند و معمولا در پایان یکجای کار لنگ میزند و FBA بخوبی عملیاتی نمیشود.
بر همین اساس بر آن شدم تا با بررسی چندتا از این مقالات موجود و نیز تجربه عملی خودم این راهکارها را ترکیب کنم که نتیجه اش فرایند شش مرحله ای زیر شده است. 
 برای راه اندازی FBA بر روی SharePoint 2010 باید مراحل زیر را به ترتیب انجام داد.
1. ساخت بانک اطلاعاتی FBA بر روی MSSQL
بانک اطلاعاتی FBA ساختار مشخصی از جداول و SP‌ها دارد که تا حد امکان بهتر است در ساختار پیش فرض آن تغییری ایجاد نکنیم. برای ایجاد این بانک کافی است به محل نصب دات نت فریم 2.0 که بصورت پیش فرض در مسیر"C:\Windows\Microsoft.NET\Framework64\v2.0.50727 " قرار دارد  بروید و روی فایل "aspnet_regsql.exe "  کلیک کنید و مراحل نصب را تا آخر پیش بروید.

در ادامه MSSQL را باز کنید و مطئمن شوید که بانک FBA به درستی ایجاد شده باشد.

2.وارد IIS شده و در بخش روت سرور ،توجه شود که حتما این تغییرات در روت سرور اعمال شود تا همه سایت‌ها از آن ارث بری کنند و تنظیمات شما یکسان در همه Web Application‌ها اعمال شود.
این مرحله  Connection String و Role provider و Membership provider مورد نظرتان را اضافه کنید. 

موقع اضافه کردن Provider‌‌ها باید توجه داشت که Application Name برابر "/" قرار داده شود.

 3.به بخش مدیریت شیرپوینت رفته و یک WebApplication جدید ایجاد کنید.

توجه داشت که در بخش Authentication بایدگزینه    Claims Authentication را انتخاب کنید. 

و نیز برای تنظیمات مربوط به FBA باید نام Provider هایی را که در مرحله قبل ایجاده کرده بودید را در این بخش قرار دهید.
اگر پرتال شما بازدید کننده‌های ناشناس(بازدید کنندگانی که عضو نیستند و در سیستم FBA نام کاربری ندارند) را نیز پشتیبانی میکند باید گزینه اول(Allow anonymous) را Yes کنید.
اگر تمایلی ندارید که دیگر احراز هویت مبتنی بر ویندوز فعال باشد تیک Enable Windows Authentication را بردارید.

 

4.حال دوباره سراغ IIS میرویم و روی وب جدیدی که ایجاد کرده ایم کلیک میکنیم و سپس روی آیکون Net Users  کلیک میکنیم

 اگر با پیغامی مبنی بر تعیین Provider پیش فرض مواجه شدیم مقادیر پیش فرض Provider‌ها را برابر با نام Provider هایی که خودمان تعریف کرده ایم قرار میدهیم. 

در ادامه چند کاربر و رول هم به عنوان کاربران اولیه و مدیران پرتال ایجاد میکنیم .

 

5.حال دوباره به بخش مدیریت برمیگردیم و یک Site Collection جدید روی Web Application مورد نظر ایجاد میکنیم.

در اینجا بهتر است مدیر اول را از کاربران ویندوزی انتخاب کرد و مدیر دوم را از بخش کاربران FBA

نکته بی ربط:اگر تمپلتی از قبل برای سایتتان دارید در بخش انتخاب نوع سایت بعد از انتخاب زبان باید به تب سوم(Custom) بروید و تنها گزینه موجود را انتخاب کنید.با این کار در مراحل بعد تمپلت مورد نظرتان را در سایت آپلود میکنید و آن را بر سایت جاری اعمال میکنید.
 در این مرحله بعد از ایجاد موفقیت آمیز Site collection دوباره به IIS سری بزنید و پس از انتخاب Web Application مورد نظر بروی Authentication کلیک کنید و مطمئن شوید که Form Authentication مقدارش Enable میباشد. در این بخش اگر هر دو حالت Form Base Authentication و Windows Authentication فعال باشد IIS در گوشه سمت راست به شما خطایی نمایش میدهد مبنی بر اینکه شما نباید هر دو حالت را همزمان فعال کنید. البته خیلی این تذکر را جدی نگیرید و بکارتان ادامه دهید و در نهایت بعد از اینکه مراحل را کامل انجام دادید و از اجرای کامل FBA اطمینان حاصل نمودید،میتوانید برگردید و حالت Windows Authentication را غیر فعال کنید.
 6.تنظیمات STS:
علاوه بر تنظیمات مربوط به بخش Web Application باید تنظیمات FBA را روی Application سرویس‌ها نیز انجام داد تا سرویس‌های WCF نیز پشتیبانی از FBA را بپذیرند. برای تنظیمات این بخش روی Security Token Service Application که زیر مجموعه SharePoint Web Services قرار دارد کلیک کنید.

 
در ادامه تنها کافی است Connection string را جهت اتصال به بانکی که در ابتدا ساختیم ایجاد کرده و Provider‌ها را نیز مطابق قبل اضافه کنیم. فقط توجه شود Connection string و Provider‌ها همنام قبلی‌ها نباشند ولی Application Name همچنان برابر با "/" مقدار دهی شود.
هیچ تغییر دیگری در این Application ایجاد نشود.مثلا Authentication به هیچ وجه تغییر نکند و در حالت ویندوزی باقی بماند.
کار تقریبا به پایان رسیده است،میتوانید در پرتال لاگین کنید!
آنچنانکه که در تصویر می‌بینید هر دو حالت ویندوزی و FBA برای احراز هویت فعال می‌باشد.

 پی نوشت:
1.همانطور که احتمالا متوجه شده اید این آموزش با راهکارهای حاضر یک سری تفاوت‌ها داشت که عمده‌ترین آن عدم تغییر در بخش احراز هویت مدیریت شیرپوینت بود. علت این امر نیز به این خاطر است که اساسا هر کاربری به این بخش دسترسی ندارد و تنها مدیر سیستم است که باید به این بخش دسترسی داشته باشد، بر همین اساس ترجیح میدهم احراز هویت آن به همان شکل اولیه(Windows Authentication ) باقی بماند.
2.در این نوشته من از شرح تنظیمات و نکات ریز و بدیهی خوداری کردم با این پیش فرض که خواننده مطلب، بر اصول پایه شیرپوینت و Asp.net آگاهی دارد. در غیر این صورت بهتر است از لینک هایی مرجع زیر کمک بگیرید.
مطالب
آشنایی با ساختار IIS قسمت ششم
در مطلب قبلی روش دستی را برای اضافه کردن ماژول‌های خود، نام بردیم. در اینجا به روش‌های دیگر اضافه کردن ماژول‌ها می‌پردازیم.

استفاده از محیط گرافیکی IIS جهت لیست کردن، اضافه و حذف ماژول‌ها

به بخش modules درIIS بروید. در پنل سمت راست همه امکانات جهت افزودن و ویرایش و حذف وجود دارند:


روش معرفی ماژول در خط فرمان با استفاده از دستور Appcmd

 Appcmd.exe install module   /name:MODULE_NAME /image:PATH_TO_DLL

قسمت name که نام ماژول است  و قسمت image هم مسیر قرار گرفتن فایل dll هست.

برای نمونه:

%windir%\system32\inetsrv\appcmd.exe install module /name:DefaultDocumentModule /image:%windir%\system32\inetsrv\defdoc.dll

در صورتیکه ماژولی که قبلا افزوده شده باشد را بخواهید اضافه کنید، خطای زیر را دریافت خواهید کرد:

ERROR ( message:Failed to add duplicate collection element "DefaultDocumentModule". )

جهت حذف ماژول دستور زیر را صادر کنید:

Appcmd.exe uninstall module MODULE_NAME

نمونه:

%windir%\system32\inetsrv\appcmd.exe uninstall module DefaultDocumentModule

گرفتن کوئری یا لیستی از ماژول‌های فعال برای یک اپلیکیشن یا عمومی:

Appcmd.exe list modules [/app.name:APPLICATION_NAME]

سوپپچ aap.name اختیاری است ولی اگر نام یک اپلیکیشن را به آن بدهید، فقط ماژول‌هایی را که روی این اپلیکیشن اجرا می‌شوند، لیست می‌کند.

نمونه:

%windir%\system32\inetsrv\appcmd.exe list modules /app.name:"Default Web Site"

کد زیر هم نمونه ای برای لیست کردن تمامی ماژول‌های عمومی که بر روی تمامی اپلیکیشن‌ها اجرا می‌شوند:

%windir%\system32\inetsrv\appcmd.exe list modules

خط زیر یک ماژول را برای همه اپلیکیشن‌ها یا اپلیکیشن خاصی فعال می‌کند که بستگی دارد سوییچ type چگونه مقداردهی شده باشد:

Appcmd.exe add module /name:MODULE_NAME /type:MGD_TYPE

برای مثال خط زیر باعث می‌شود ماژول Forms Authentication فقط برای وب اپلیکیشن default web site فعال شود:

%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule /app.name:"Default Web Site"

یا در پایین آن را به صورت عمومی یا global فعال می‌کند:

%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule

برای غیرفعال کردن یک ماژول از دستور زیر استفاده می‌شود:

Appcmd.exe delete module MODULE_NAME [/app.name:APPLICATION_NAME]

اگر غیر فعال کردن یک ماژول در یک اپلیکیشن خاص مدنظر شما باشد دستور زیر نمونه آن است:

%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication /app.name:"Default Web Site"

اگر قصد دارید آن‌را بر روی تمامی اپلیکیشن‌ها غیرفعال کنید، دستور زیر نمونه آن است:

%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication

حفظ کردن یا به خاطر سپردن دستورات بالا ممکن است کار سخت و دشواری باشد، به همین جهت از help کمک بگیرید:

Appcmd.exe module /?

یا به شکل اختصاصی‌تر برای یک دستور

Appcmd.exe install module /?Appcmd add module /?
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت دوم
در قسمت اول در مورد ابزار انتخابی برای توسعه برنامه‌های Cross Platform صحبت کردیم. در این قسمت به آموزش نصب و راه اندازی محیط توسعه می‌پردازیم.

شما می‌توانید در هر یک از سیستم عامل‌های Mac - Windows - Linux و با هر یک از IDE‌های Visual Studio - Visual Studio for mac - Rider کار کنید. برای این که بتوانیم آموزش را کاملا عملی پیش ببریم و وارد جزئیات شویم، در عمل باید یکی را انتخاب و آموزش دهیم و آن Windows - Visual Studio است؛ اگرچه باقی تفاوت خیلی زیادی ندارند.

با توجه به این که کد نوشته شده برای UI و Logic برای هر سه پلتفرم Windows - Android - iOS یکی است و کدهای منحصر به هر پلتفرم، سهم اندکی از پروژه را تشکیل می‌دهد و همچنین تست برنامه برای Windows آسان‌تر و سریع‌تر بوده و امکانات بیشتری را دارد، توصیه اکید می‌کنم برنامه را روی نسخه Windows توسعه دهید و تست کنید و پس از انجام کارهای اصلی پروژه، آن را بر روی Android و iOS نیز تست کنید. این مورد شباهت به برنامه نویسی وب برای مرورگرها را دارد. خیلی از افراد، سایت را بر روی یک مرورگر مثل Chrome یا Firefox توسعه می‌دهند و در نهایت کار را بر روی مرورگرهای موبایل و IE - Edge - Safari و ... تست می‌کنند. همانطور که می‌شود در مرورگر Chrome هم Touch را تست کرد و هم سایزهای مختلف را، همین کارها را در تست نسخه ویندوزی نیز می‌توانید انجام دهید. در کنار این با توجه به رشد فروش تبلت‌های ویندوزی، برای خیلی از برنامه‌ها، ارائه نسخه ویندوزی می‌تواند مفید نیز باشد.

برای شروع بهتر است نسخه‌ای به روز از ویندوز 10 را داشته باشید، یا Pro یا Enterprise. برای بررسی، ابتدا Command Line را باز کنید و دستور ver (مخفف version) را اجرا کنید. چیزی مشابه مقدار
Microsoft Windows Version 10.0.17134.345
را مشاهده خواهید نمود که باید عدد پنج رقمی آن (در این مثال 17134) از 16299 کمتر نباشد. اگر فرض کنیم که فقط یک سیستم داریم که بدون سیستم عامل است، تا این جا یک ISO نصب ویندوز دانلود کرده‌ایم، به حجم 3.7 گیگ که بعد از نصب، 9.5 گیگ از فضای هارد را می‌گیرد. کمی حوصله به خرج دهید (!) و اگر می‌خواهید همه چیز را تمیز انجام دهید، با یک ویندوز تمیز شروع کنید!

آخرین نسخه پایدار ویژوال استودیو در زمان نگارش این مقاله، 2017 - 15.8.7 هست که ما نیاز به نصب Workload های زیر داریم:
Universal Windows Platform development 
Mobile development with .NET 
نصب این دو احتیاج به 5 گیگ دانلود و 14 گیگ فضای روی هارد را دارد که علاوه بر خود Visual Studio و محیط توسعه آن، موارد زیر را نیز برای شما نصب می‌کند:
 Android SDK - Android NDK - JDK (Java) - Windows SDK - iOS SDK
نکات مهم:
۱- اگر قبلا یکی از SDK‌های ذکر شده را دانلود کرده‌اید، لطفا بی خیال آن شوید! اجازه دهید تا ویژوال استودیو همه چیز را دانلود و نصب و کانفیگ کند. Android SDK، برای مثال، بالغ بر 70 گیگ فایل، برای 28 ورژن اندروید است که اگر یکی از آن‌ها را داشته‌اید که برای تست کد نویسی با Java و Android Studio جواب می‌داده، هیچ دلیلی ندارد دقیقا همان نسخه به درد Xamarin هم بخورد!
۲- ترجیحا نسخه Enterprise را نصب کنید.
۳- قسمت‌های عمده فایل‌های دانلودی از سرورهای مایکروسافت دانلود می‌شوند که محدودیتی برای کاربران ایرانی ندارد، ولی قسمت هایی نیز مستقیما از سرورهای گوگل دانلود می‌شوند که متاسفانه روی کاربرهای ایرانی بسته است. با توجه به این که ممکن است استفاده از روش‌های دور زدن تحریم مانند VPN باعث کندی سرعت اینترنت و دانلود شوند، توصیه می‌کنم که ابتدا "Universal Windows Platform development" را نصب کنید (زیرا تماما از سرورهای مایکروسافت دانلود می‌شود) و سپس مجدد Installer را باز کرده و "Mobile development with .NET" را انتخاب کنید و این بار از ابزارهای دور زدن تحریم استفاده کنید.
۴- در سمت راست گزینه‌های قابل نصب، تیک موارد "Google Android Emulator API Level 27" و "Intel Hardware Accelerated Execution Manager (HAXM) global install" را بردارید. در پستی جداگانه آپشن‌های متنوع Emulator‌های اندرویدی را بررسی خواهیم نمود.
۵- بهتر است Administrator سیستم خود باشید.

بعد از اتمام نصب باید Developer mode را فعال کنید که نحوه انجام آن در این لینک شرح داده شده است. به صورت خلاصه به Settings بروید، سپس Update & Security، سپس For developers و در نهایت انتخاب Developer mode از بین گزینه‌های موجود.
ضمن استفاده از ابزارهای دور زدن تحریم (فقط برای ساختن و بیلد کردن اولین پروژه)، ویژوال استودیو را به صورت Run as admin باز کنید و از منوی File > New > Project قسمت Cross-Platform برای CSharp، یک Mobile app Xamarin Forms بسازید که ضمن انگلیسی بودن نام پروژه و فاقد Space بودن آن، ترجیحا در فولدری باشد که مسیر آن فولدر نیز طولانی نباشد، Space و کارکترهای فارسی نیز نداشته باشد.

تنظیماتی که در پنجره New Cross Platform App هستند مناسب بوده و Ok را بزنید! اولین بیلد به علت نیاز به دانلود طول می‌کشد و در صورت بیلد شدن موفقیت آمیز پروژه شما دومین قسمت را با موفقیت طی کرده اید. در قسمت بعدی ساختار پروژه‌های Xamarin Forms را بررسی می‌کنیم و یک مثال ساده می‌نویسیم که لااقل روی ویندوز قابلیت تست را داشته باشد. دقت کنید که همان کد روی Android / iOS نیز کار می‌کند، ولی در پست هایی جداگانه باید در مورد راه اندازی Emulator‌های Android و iOS آموزش هایی را ببینید. در صورت وجود هر گونه مشکل یا سوال نیز در قسمت نظرات همین صفحه در خدمت شما عزیزان هستیم. 
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویس‌ها و تزریق وابستگی‌ها
پیشنیازها (الزامی)

«بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعه‌ی بیشتر»


تزریق وابستگی‌ها (یا Dependency injection = DI) به معنای ارسال نمونه‌ای/وهله‌ای از وابستگی (یک سرویس) به شیء وابسته‌ی به آن (یک کلاینت) است. در فرآیند تزریق وابستگی‌ها، یک کلاس، وهله‌های کلاس‌های دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت می‌کند. بنابراین بجای نوشتن newها در کلاس جاری، آن‌ها را به صورت وابستگی‌هایی در سازنده‌ی کلاس تعریف می‌کنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورک‌هایی که کار وهله سازی این وابستگی‌ها را انجام می‌دهند، IoC Container و یا DI container می‌گوییم (IoC =  inversion of control ).
چندین نوع تزریق وابستگی‌ها وجود دارند که دو حالت زیر، عمومی‌ترین آن‌ها است:
الف) تزریق در سازنده‌ی کلاس: لیست وابستگی‌های یک کلاس، به عنوان پارامترهای سازنده‌ی آن ذکر می‌شوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی می‌کند و سپس IoC Container با وهله سازی آن‌ها، وابستگی‌های مورد نیاز را تامین خواهد کرد.


تزریق وابستگی‌ها در ASP.NET Core

برخلاف نگارش‌های قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شده‌است و این مفهوم، در سراسر اجزای آن به صورت یکپارچه‌ای پشتیبانی می‌شود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی می‌کند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار می‌گیرد و در اینجا شما هستید که مسئول تعریف نحوه‌ی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهله‌ی جدید از آن توسط IoC Container تولید و ارائه می‌شود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین می‌شود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کننده‌ها قرار می‌گیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیء‌ایی خاص در اختیار تمام مصرف کننده‌های آن قرار گیرد (single instance per web request).

طول عمر singleton، برای سرویس‌ها و کلاس‌های config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنش‌ها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژه‌ای درخواست شود، یک وهله‌ی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهله‌ی از وابستگی مورد نظر، در بین تمام اشیاء وابسته‌ی به آن، در طول عمر آن scope تولید می‌شود.
بنابراین در برنامه‌های وب دو نوع singleton برای معرفی کلاس‌های config و نوع scoped برای پیاده سازی تراکنش‌ها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازه‌ی از کلاس‌های مختلف مورد نیاز)، بیشتر از همه به کار برده می‌شوند.


یک مثال کاربردی: بررسی نحوه‌ی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core


مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شده‌است، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشه‌ی src، کلیک راست کرده و گزینه‌ی Add new project را انتخاب کنید و سپس یک پروژه‌ی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونه‌ی سرویس پیام‌ها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services
{
    public interface IMessagesService
    {
        string GetSiteName();
    }
 
    public class MessagesService : IMessagesService
    {
        public string GetSiteName()
        {
            return "DNT";
        }
    }
}
در ادامه به پروژه‌ی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.Services را انتخاب کنید، تا اسمبلی آن‌را بتوان در پروژه‌ی جاری استفاده کرد.


انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{
    "dependencies": {
        // same as before
        "Core1RtmEmptyTest.Services": "1.0.0-*"
    },
در ادامه قصد داریم این سرویس را به متد Configure کلاس Startup تزریق کرده و سپس خروجی رشته‌ای آن‌را توسط میان افزار Run آن نمایش دهیم. برای این منظور فایل Startup.cs را گشوده و امضای متد Configure را به نحو ذیل تغییر دهید:
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
همانطور که در قسمت قبل نیز عنوان شد، متد Configure دارای امضای ثابتی نیست و هر تعداد سرویسی را که نیاز است، می‌توان در اینجا اضافه کرد. یک سری از سرویس‌ها مانند IApplicationBuilder و IHostingEnvironment پیشتر توسط IoC Container توکار ASP.NET Core معرفی و ثبت شده‌اند. به همین جهت، همینقدر که در اینجا ذکر شوند، کار می‌کنند و نیازی به تنظیمات اضافه‌تری ندارند. اما سرویس IMessagesService ما هنوز به این IoC Container معرفی نشده‌است. بنابراین نمی‌داند که چگونه باید این اینترفیس را وهله سازی کند.
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
{ 
    app.Run(async context =>
    {
        var siteName = messagesService.GetSiteName();
        await context.Response.WriteAsync($"Hello {siteName}");
    });
}
در این حالت اگر برنامه را اجرا کنیم، به این خطا برخواهیم خورد:
 System.InvalidOperationException
No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)

System.Exception
Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'.
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
برای رفع این مشکل، به متد ConfigureServices کلاس Startup مراجعه کرده و سیم کشی‌های مرتبط را انجام می‌دهیم. در اینجا باید اعلام کنیم که «هر زمانیکه به IMessagesService رسیدی، یک وهله‌ی جدید (transient) از کلاس MessagesService را به صورت خودکار تولید کن و سپس در اختیار مصرف کننده قرار بده»:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMessagesService, MessagesService>();
    }
در اینجا نحوه‌ی ثبت یک سرویس را در IoC Containser توکار ASP.NET Core ملاحظه می‌کنید. تمام حالت‌های طول عمری که در ابتدای بحث عنوان شدند، یک متد ویژه‌ی خاص خود را در اینجا دارند. برای مثال حالت transient دارای متد ویژه‌ی AddTransient است و همینطور برای سایر حالت‌ها. این متدها به صورت جنریک تعریف شده‌اند و آرگومان اول آن‌ها، اینترفیس سرویس و آرگومان دوم، پیاده سازی آن‌ها است (سیم کشی اینترفیس، به کلاس پیاده سازی کننده‌ی آن).

پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:


و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، می‌داند زمانیکه به IMessagesService رسید، چگونه باید آن‌را وهله سازی کند.


چه سرویس‌هایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شده‌اند؟

در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:


همانطور که ملاحظه می‌کنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شده‌اند که تاکنون با دو مورد از آن‌ها کار کرده‌ایم.


امکان تزریق وابستگی‌ها در همه جا!

در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکته‌ی مهم اینجا است که برخلاف نگارش‌های قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمت‌های مختلف برنامه)، می‌توان این تزریق‌ها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکته‌ی ویژه‌ی نحوه‌ی تزریق وابستگی‌ها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام این‌ها از روز اول کار می‌کنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.


نیاز به تعویض IoC Container توکار ASP.NET Core

قابلیت تزریق وابستگی‌های توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندی‌های اصلی آن طراحی شده‌است و نه بیشتر. بنابراین توسط آن قابلیت‌های پیشرفته‌ای را که سایر IoC Containers ارائه می‌دهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگی‌های توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
 • امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
 • پشتیبانی از Setter Injection
 • امکان انتخاب سازنده‌ای خاص (اگر چندین سازنده تعریف شده باشند)
 • سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
 • پشتیبانی توکار از Lazy و Func
 • امکان وهله سازی از نوع‌های concrete (یا همان کلاس‌های معمولی)
 • پشتیبانی از مفاهیمی مانند Interception و AOP
 • امکان اسکن اسمبلی‌های مختلف جهت یافتن اینترفیس‌ها و اتصال خودکار آن‌ها (طراحی‌های افزونه پذیر)


روش تعویض IoC Container توکار ASP.NET Core با StructureMap

جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگی‌ها در ASP.NET Core 1.0» می‌توانید مطالعه کنید.
یا می‌توانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژه‌ی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شده‌است. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بسته‌ی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
        // same as before  
        "StructureMap.Dnx": "0.5.1-rc2-final"
    },
ب) جایگزین کردن Container استراکچرمپ با Container توکار ASP.NET Core
پس از نصب بسته‌ی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddDirectoryBrowser();
 
        var container = new Container();
        container.Configure(config =>
        {
            config.Scan(_ =>
            {
                _.AssemblyContainingType<IMessagesService>();
                _.WithDefaultConventions();
            });
            //config.For<IMessagesService>().Use<MessagesService>();
 
            config.Populate(services);
        });
        container.Populate(services);
 
        return container.GetInstance<IServiceProvider>();
    }
در اینجا ابتدا خروجی متد ConfigureServices، به IServiceProvider تغییر کرده‌است تا استراکچرمپ این تامین کننده‌ی سرویس‌ها را ارائه دهد. سپس Container مربوط به استراکچرمپ، وهله سازی شده و همانند روال متداول آن، یک سرویس و کلاس پیاده سازی کننده‌ی آن معرفی شده‌اند (و یا هر تنظیم دیگری را که لازم بود باید در اینجا اضافه کنید). در پایان کار متد Configure آن و پس از این متد، نیاز است متدهای Populate فراخوانی شوند (اولی تعاریف را اضافه می‌کند و دومی کار تنظیمات را نهایی خواهد کرد).
سپس وهله‌ای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده می‌شود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شده‌است، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل می‌شود (name هر نامی می‌تواند باشد).
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت سیزدهم
در این قسمت قصد داریم به بررسی نحوه‌ی بهبود Performance در پروژه‌های Xamarin Forms نگاهی بیاندازیم. صد البته امکان پوشش دادن تمامی نکات وجود ندارد و در این قسمت سعی بر پوشش دادن مهم‌ترین آنها را داریم.
توجه داشته باشید که در قسمت نهم به "x:DataType" و در قسمت چهارم به "مواردی مهم در زمینه‌ی بهبود عملکرد پروژه‌های Xamarin در Android" پرداخته بودیم که آن نکات در بهبود سرعت برنامه‌ها تاثیر گذارند. همان طور که در قسمت چهارم گفته شد، همیشه سرعت برنامه را در Release mode تست کنید.

در Xamarin Forms هر کنترل (برای مثال Entry و Button) در زمان اجرا به یک کنترل Native معادل خود تبدیل می‌شود. مشکلی که وجود دارد این است که وقتی از روی Button مربوط به Xamarin Forms، یک Button در Android و ... ساخته می‌شود، آن Button بر روی یک Container قرار می‌گیرد. در واقع به ازای هر کنترل Xamarin Forms، دو کنترل Native ساخته می‌شود(!) و تعداد کنترل‌های بیشتر در برنامه یعنی کندی بیشتر.
وظیفه تبدیل Xamarin forms control به یک Native control بر عهده ‌Renderer هاست. البته اینکه هر Xamarin Forms control به دو Native control تبدیل شود در ذیل بحث Fast Renderers در حال بهبود یافتن است. Fast Renderer یک Renderer است که به ازای هر Xamarin forms control، فقط یک کنترل Native را می‌سازد. راهنمای فعال سازی Fast Renderers را می‌توانید بخوانید و در برنامه‌هایتان اعمال کنید؛ ولی در پروژه‌های مبتنی بر Bit مانند XamApp، این مهم به صورت خودکار فعال می‌شود و نیازی به اقدام جداگانه‌ای نیست.

مورد بعدی در مورد Layout‌های Xamarin Forms است (Grid,StackLayout,FlexLayout و...) در صورتیکه قصد تعریف کردن مواردی چون Tap Gesture و Background را بر روی Layout خود ندارید و از Layout خود فقط انتظار Layout بودن را دارید، می‌توانید آنها را سریع‌تر سازید. مثال قسمت قبل را که برای نمایش هر Product در List View از Flex Layout استفاده می‌کرد، به‌خاطر بیاورید. ما آن Flex را استفاده کردیم تا نام Product، بیست و پنج درصد فضای هر ردیف از List View را بگیرد و ... به آن Flex Layout رنگی داده نشد یا هر چیزی از این قبیل. برای بهبود عملکرد آن List View می‌توانیم از Flex Layout به شکل زیر استفاده کنیم:
<sfListView:SfListView.ItemTemplate>
                <DataTemplate>
                    <FlexLayout
                        x:DataType="model:Product"
                        CompressedLayout.IsHeadless="True"
                        Direction="Row">
                        <Label
                            FlexLayout.Basis="40%"
                            Text="{Binding Name}"
                            VerticalTextAlignment="Center" /> ...
با اضافه کردن CompressedLayout.IsHeadless=True دیگر کنترل Native ای برای آن Flex Layout ساخته نمی‌شود و کنترل Native کمتر یعنی سرعت بیشتر برنامه.
برای این امکان، ذکر دو نکته الزامی است:
۱- استفاده از CompressedLayout.IsHeadless باعث می‌شود تعدادی از امکانات Layout مانند Background Color دیگر کار نکنند. فعال سازی آن فقط در DataTemplate‌های ListView توصیه می‌شود که مثلا 20 عدد Product در مثال قسمت قبل، منجر به ساخته شدن 20 عدد Flex Layout می‌شوند و اگر کاری کنیم که کنترل Native معادل آن ساخته نشود، لااقل 20 بار سود کرده‌ایم! استفاده کردن از CompressLayout در همه جای برنامه، ایده جالبی نیست.
۲- CompressedLayout فقط در Android و iOS اعمال می‌شود و تست کردن آن در UWP عملا فایده‌ای ندارد.

نکته مهم بعدی، بحث نمایش عکس است. در Xamarin Forms یک عکس می‌تواند در رزولوشن‌های مختلف با کمک Drawable در Android و Asset Catalog Image Sets در iOS و ... باشد. همچنین می‌توان عکس را از یک Url گرفت و یا به شکل Embedded در پروژه‌ی مشترک بین سه پلتفرم باشد. می‌توان علاوه بر PNG و JPG از WebP ،SVG و GIF نیز استفاده نمود. اما آنچه که مهم است این است که بعد از یادگیری اصول اولیه نمایش عکس در Xamarin Forms، حتما حتما از FF Image Loading برای نمایش عکس‌ها استفاده شود.
استفاده از FF Image Loading دارای مزایایی چون پشتیبانی از WebP | SVG است. همچنین این کتابخانه فایل‌ها را نیز Cache می‌کند (هم در صورتیکه Url باشند و قرار باشند از طریق اینترنت دریافت شوند و هم Bitmap حاصل از Render کردن آن عکس برای آن Device خاص همانند GlideX در Android). امکان گرد کردن عکس، یا سیاه و سفید نمودن آن و کوهی از امکانات دیگر، استفاده‌ی از این کتابخانه را الزامی می‌کند!
برای نمایش لوگوی Bit در برنامه برای مثال، ابتدا Package مربوطه را در پروژه XamApp نصب می‌کنیم. سپس آن را در Android ،iOS و UWP راه اندازی می‌کنیم و در Android مقدار enableFastRenderer را True می‌دهیم. سپس در Xaml داریم:
        <ffimageloading:CachedImage
            CacheType="All"
            DownsampleToViewSize="true"
            HeightRequest="100"
            HorizontalOptions="Center"
            Source="https://avatars2.githubusercontent.com/u/22663390?s=100"
            VerticalOptions="Center"
            WidthRequest="100" />
پایان
مطالب
آزمایش Web APIs توسط افزونه Rest Client
اگر تجربه‌ی نوشتن Web API را داشته باشید، قطعا نیاز به ابزارها و یا کتابخانه‌هایی را برای تست API‌ها داشته‌اید، تا بتوانید از صحت عملکرد آنها مطمئن شوید. از جمله روش‌ها و یا ابزارهایی که میتوانید برای تست و بررسی API‌ها استفاده کنید، می‌توان به postman (دوره آشنایی با postman ) ، سواگر  و stress اشاره کرد که پیشتر در سایت جاری مقالات مفیدی درباره‌ی آن‌ها نوشته شده‌است که می‌توانید برای آشنایی به آنها مراجعه کنید.

در این مقاله قصد داریم یک افزونه را معرفی نماییم که بر روی Visual Studio Code نصب می‌شود و این امکان را به ما می‌دهد تا بتوانیم API‌ها را فراخوانی و تست کنیم که در ادامه به تفصیل به آن خواهیم پرداخت.
با توجه به اینکه این افزونه مختص VS Code می‌باشد، باید ابتدا vs code را نصب نمایید.
در قسمت افزونه‌ها در بخش جستجو، عبارت Rest client را وارد نمایید و منتظر بمانید تا نتایج، نمایش داده شوند و سپس آن را نصب نمایید.

در زمان انتشار این مقاله، بیش از یک میلیون بار دانلود شده‌است.

قبل از اینکه بتوانیم با نحوه‌ی کار این افزونه آشنا شویم، نیاز داریم API هایی باشند تا بتوانیم آن‌ها را فراخوانی کنیم. هرچند سایت‌هایی نیز هستند که چنین امکانی را فراهم کرده‌اند و میتوانیم API‌های آنها را فراخوانی کنیم. در این مقاله، چند API  ساده را با ASP.NET Core پیاده سازی میکنیم و سپس اقدام به فراخوانی هر کدام از آنها خواهیم کرد.
با توجه به اینکه این مقاله در مورد Web API و آموزش آن نیست، از توضیح کدهای زیر خودداری می‌شود.
 public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
 شبیه سازی یک Repository
 public class PeopleRepository
    {
        private List<Person> _people;
        public PeopleRepository()
        {
            _people = new List<Person> {
            new Person{ Id=1,Age=28,Name="Uthman"},
            new Person{ Id=2,Age=27,Name="Vahid"},
            new Person{ Id=3,Age=26,Name="Hadi"},
            new Person{ Id=4,Age=25,Name="Saman"},
            new Person{ Id=5,Age=20,Name="Sirwan"},
            };
        }

        public List<Person> GetAll()
        {
            return _people;
        }

    }

و چند API  برای کار با کلاس Person :
[Route("api/people")]
    public class PeopleController : Controller
    {
        PeopleRepository _repository;
        public PeopleController()
        {
            _repository = new PeopleRepository();
        }

        [HttpGet("")]
        public IActionResult Get()
        {
            return Ok(_repository.GetAll());
        }

        [HttpGet("{id:int}")]
        public IActionResult Get(int id)
        {
            return Ok(_repository.GetAll().FirstOrDefault(p => p.Id == id));
        }

        [HttpPost]
        public IActionResult Post([FromBody]Person person)
        {
            return Ok(person);
        }

        [HttpPut("{id:int}")]
        public IActionResult Put(int id,[FromBody] Person person)
        {
            if (id != person.Id)
                return BadRequest();
            return Ok(person);
        }

        [HttpDelete("{id:int}")]
        public IActionResult Delete(int id)
        {
            return Ok();
        }

        [HttpPost("avatar")]
        public IActionResult Post(IFormFile file)
        {
            return Ok();
        }
    }
تا به اینجا صرفا چند API را تهیه کرده‌ایم که در ادامه میخواهیم هر کدام از آنها را با افزونه‌ی Rest Client فراخوانی کنیم و با نحوه‌ی عملکرد آن‌ها آشنا شویم.

 مراحل انجام کار
 ابتدا نیاز هست یک فایل را ایجاد نماییم؛ با پسوند http و یا اینکه میتوانید یک فایل متنی را ایجاد نمایید و زبان آن را بر روی HTTP تنظیم نمایید.

اجرای درخواست GET برای نمایش لیست افراد
GET http://localhost:59416/api/people
با نوشتن یک خط فوق میتوانیم API مربوط به لیست افراد را فراخوانی کنیم.

برای فراخونی دستور فوق دو راه پیش رو است:
1- بعد از نوشتن آدرس هر API، یک دکمه بالای آدرس ظاهر می‌شود، تحت عنوان Send Request که با کلیک بر روی این دکمه، درخواست ارسال خواهد شد.
2- انتخاب آدرس فوق و فشردن کلید‌های ctrl+alt+r یا بجای فشردن این کلید‌ها میتوانید در Command  Palette،دستور Send Request را جستجو کنید و انتخاب نمایید:


بعد از اجرا به یکی از روش‌های فوق، در یک پنجره‌ی جدید در سمت راست، نتیجه‌ی درخواست ظاهر میشود که در بخش بالا توضیحاتی در مورد درخواست و در بخش پایین، خروجی آن قابل مشاهده می‌باشد


تا به اینجا توانستیم فقط با نوشتن آدرس API مورد نظر، آن را فراخوانی کنیم.

برای ارسال پارامترهایی در هدر درخواست فقط کافیست دقیقا در خط پایین آدرس، به صورت field-name:field-value، هر پارامتری را که میخواهید، به همراه درخواست ارسال کنید. برای نمونه برای API لیست افراد که در بالا تست کردیم، میتوانیم هدر را به صورت زیر تنظیم نماییم :

GET http://localhost:59416/api/people
Content-Type: application/json


اجرای درخواست GET برای دریافت یک شخص خاص

GET http://localhost:59416/api/people/1

خروجی آن به صورت زیر می‌باشد


درخواست POST برای درج کاربر جدید:

POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

بعد از هدرهای درخواست، یک خط خالی ایجاد کنید و پایین‌تر از خط خالی، میتوانید مقادیر body درخواست را وارد نمایید.


درخواست PUT برای آپدیت یک شخص :

PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

درخواست DELETE برای حذف شخص:

DELETE http://localhost:59416/api/people/3
content-type: application/application/json


ارسال توکن اعتبارسنجی :

در صورتی که یک API نیاز به اعتبار سنجی دارد و باید توکن را به همراه درخواستی ارسال نمایید، می‌توانید در هدر درخواست، همانند زیر، توکن را ارسال نمایید

GET http://localhost:59416/api/people
content-type: application/json
Authorization: Bearer token


آپلود فایل:

یکی از API هایی که در مثال ابتدای مقاله داشتیم، مربوط به آپلود فایل آواتار هست که از ورودی، یک IFormFile را به عنوان ورودی دریافت میکند. برای آپلود فایل به کمک افزونه Rest Client میتوانیم به صورت زیر فایل را ارسال نماییم 

POST http://localhost:59416/api/people/avatar
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

قبل از آدرس فایل، وجود  > ضروری می‌باشد. 


فعال سازی دکمه Send Request به ازای هر آدرس:

اگر در یک فایل، چند آدرس را همانند عکس زیر داشته باشید، فقط یک دکمه‌ی Send Request وجود خواهد داشت که کلیک بر روی آن منجر به فراخوانی اولین url می‌شود.

برای داشتن یک دکمه Send Request به ازای هر API، باید بین هر کدام از API ها، حداقل سه تا # قرار دهید.

### Get All People
GET http://localhost:59416/api/people
content-type: application/json

### Get Person
GET http://localhost:59416/api/people/1


### Create 
POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

### Edit person
PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

### Delete person
DELETE http://localhost:59416/api/people/3
content-type: application/application/json


### Upload Avatar
POST http://localhost:59416/api/people/avatar 
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

افزونه‌ی Rest Client، فراتر از توضیحات این مقاله می‌باشد. در صورت علاقه و برای مطالعه بیشتر در مورد آن، می‌توانید به لینک صفحه افزونه مراجعه نمایید.

مطالب
راهنمای تغییر بخش احراز هویت و اعتبارسنجی کاربران سیستم مدیریت محتوای IRIS به ASP.NET Identity – بخش اول
سیستم مدیریت محتوای IRIS از سیستم‌های اعتبار سنجی و مدیریت کاربران رایج نظیر ASP.NET Membership و یا ASP.NET Simple Membership استفاده نمی‌کند و از یک سیستم احراز هویت سفارشی شده مبتنی بر FormsAuthentication بهره می‌برد. زمانیکه در حال نوشتن پروژه‌ی IRIS بودم هنوز ASP.NET Identity معرفی نشده بود و به دلیل مشکلاتی که سیستم‌های قدیمی ذکر شده داشت، یک سیستم اعتبار سنجی کاربران سفارشی شده را در پروژه پیاده سازی کردم.
برای اینکه با معایب سیستم‌های مدیریت کاربران پیشین و مزایای ASP.NET Identity آشنا شوید، مقاله زیر می‌تواند شروع خیلی خوبی باشد:


به این نکته نیز اشاره کنم که  هنوز هم می‌توان از FormsAuthentication مبتنی بر OWIN استفاده کرد؛ ولی چیزی که برای من بیشتر اهمیت دارد خود سیستم Identity هست، نه نحوه‌ی ورود و خروج به سایت و تولید کوکی اعتبارسنجی.
باید اعتراف کرد که سیستم جدید مدیریت کاربران مایکروسافت خیلی خوب طراحی شده است و اشکالات سیستم‌های پیشین خود را ندارد. به راحتی می‌توان آن را توسعه داد و یا قسمتی از آن را تغییر داد و به جرات می‌توان گفت که پایه و اساس هر سیستم اعتبار سنجی و مدیریت کاربری را که در نظر داشته باشید، به خوبی پیاده سازی کرده است.
در ادامه قصد دارم، چگونگی مهاجرت از سیستم فعلی به سیستم Identity را بدون از دست دادن اطلاعات فعلی شرح دهم.

رفع باگ ثبت کاربرهای تکراری نسخه‌ی کنونی

قبل از این که سراغ پیاده سازی Identity برویم، ابتدا باید یک باگ مهم را در نسخه‌ی قبلی، برطرف نماییم. نسخه‌ی کنونی مدیریت کاربران اجازه‌ی ثبت کاربر با ایمیل و یا نام کاربری تکراری را نمی‌دهد. جلوگیری از ثبت نام کاربر جدید با ایمیل یا نام کاربری تکراری از طریق کدهای زیر صورت گرفته است؛ اما در عمل، همیشه هم درست کار نمی‌کند.
        public AddUserStatus Add(User user)
        {
            if (ExistsByEmail(user.Email))
                return AddUserStatus.EmailExist;
            if (ExistsByUserName(user.UserName))
                return AddUserStatus.UserNameExist;

            _users.Add(user);
            return AddUserStatus.AddingUserSuccessfully;
        }
شاید الان با خود بگویید که چرا برای فیلدهای UserName و Email ایندکس منحصر به فرد تعریف نشده است؟ دلیلش این بوده که در زمان نگارش پروژه، Entity Framework پشتیبانی پیش فرضی از تعریف ایندکس نداشت و نوشتن همین شرط‌ها کافی به نظر می‌رسید. باز هم ممکن است بگویید که مسائل همزمانی چگونه مدیریت شده است و اگر دو کاربر مختلف در یک لحظه، نام کاربری یکسانی را انتخاب کنند، سیستم چگونه از ثبت دو کاربر مختلف با نام کاربری یکسان ممانعت می‌کند؟ 
جواب این است که ممانعتی نمی‌کند و دو کاربر با نام‌های کاربری یکسان ثبت می‌شوند؛ اما من برای وبسایت خودم که تعداد کاربرانش محدود بود این سناریو را محتمل نمی‌دانستم و کد خاصی برای جلوگیری از این اتفاق پیاده سازی نکرده بودم.
با این حال، در حال حاضر نزدیک به 20 کاربر تکراری در دیتابیسی که این سیستم استفاده می‌کند ثبت شده است. اما واقعا آیا دو کاربر مختلف اطلاعات یکسانی وارد کرده‌اند؟
 دلیل رخ دادن این اتفاق این است که کاربری که در حال ثبت نام در سایت است، وقتی که بر روی دکمه‌ی ثبت نام کلیک می‌کند و اطلاعات به سرور ارسال می‌شوند، در سمت سرور بعد از رد شدن از شرطهای تکراری نبودن UserName و Email، قبل از رسیدن به متد SaveChanges برای ذخیره شدن اطلاعات کاربر جدید در دیتابیس، وقفه ای در ترد این درخواست به وجود می‌آید. کاربر که احساس می‌کند اتفاقی رخ نداده است، دوباره بر روی دکمه‌ی ثبت نام کلیک می‌کند و  همان اطلاعات قبلی به سرور ارسال می‌شود و این درخواست نیز دوباره شرط‌های تکراری نبودن اطلاعات را با موفقیت رد می‌کند( چون هنوز SaveChanges درخواست اول فراخوانی نشده است) و این بار SaveChanges درخواست دوم با موفقیت فراخوانی می‌شود و کاربر ثبت می‌شود. در نهایت هم ترد درخواست اول به ادامه‌ی کار خود می‌پردازد و SaveChanges درخواست اول نیز فراخوانی می‌شود و خیلی راحت دو کاربر با اطلاعات یکسان ثبت می‌شود. این سناریو را در ویژوال استادیو با قرار دادن یک break point قبل از فراخوانی متد SaveChanges می‌توانید شبیه سازی کنید.
احتمالا این سناریو با مباحث همزمانی در سیستم عامل و context switch‌های بین ترد‌ها مرتبط است و این context switch‌ها  بین درخواست‌ها و atomic نبودن روند چک کردن اطلاعات و ثبت آن ها، سبب بروز چنین مشکلی میشود.
برای رفع این مشکل می‌توان از غیر فعال کردن یک دکمه در حین انجام پردازش‌های سمت سرور استفاده کرد تا کاربر بی حوصله، نتواند چندین بار بر روی یک دکمه کلیک کند و یا راه حل اصولی‌تر این است که ایندکس منحصر به فرد برای فیلدهای مورد نظر تعریف کنیم.
به طور پیش فرض در ASP.NET Identity برای فیلدهای UserName و Email ایندکس منحصر به فرد تعریف شده است. اما مشکل این است که به دلیل وجود کاربرانی با Email و UserName تکراری در دیتابیس کنونی، امکان تعریف Index منحصر به فرد وجود ندارد و پیش از انجام هر کاری باید این ناهنجاری را در دیتابیس برطرف نماییم.
   
به شخصه معمولا برای انجام کارهایی از این دست، یک کنترلر در برنامه خود تعریف می‌کنم و در آنجا کارهای لازم را انجام می‌دهم.
در اینجا من برای حذف کاربران با اطلاعات تکراری، یک کنترلر به نام Migration و اکشن متدی به نام RemoveDuplicateUsers تدارک دیدم.
using System.Linq;
using System.Web.Mvc;
using Iris.Datalayer.Context;

namespace Iris.Web.Controllers
{
    public class MigrationController : Controller
    {
        public ActionResult RemoveDuplicateUsers()
        {
            var db = new IrisDbContext();

            var lstDuplicateUserGroup = db.Users
                                               .GroupBy(u => u.UserName)
                                               .Where(g => g.Count() > 1)
                                               .ToList();

            foreach (var duplicateUserGroup in lstDuplicateUserGroup)
            {
                foreach (var user in duplicateUserGroup.Skip(1).Where(user => user.UserMetaData != null))
                {
                    db.UserMetaDatas.Remove(user.UserMetaData);
                }
                
                db.Users.RemoveRange(duplicateUserGroup.Skip(1));
            }

            db.SaveChanges();

            return new EmptyResult();
        }
    }
}
در اینجا کاربران بر اساس نام کاربری گروه بندی می‌شوند و گروه‌هایی که بیش از یک عضو داشته باشند، یعنی کاربران آن گروه دارای نام کاربری یکسان هستند و به غیر از کاربر اول گروه، بقیه باید حذف شوند. البته این را متذکر شوم که منطق وبسایت من به این شکل بوده است و اگر منطق کدهای شما فرق می‌کند، مطابق با منطق خودتان این کدها را تغییر دهید.

تذکر: اینجا شاید بگویید که چرا cascade delete برای UserMetaData فعال نیست و بخواهید که آن را اصلاح کنید. پیشنهاد من این است که اکنون از هدف اصلی منحرف نشوید و تمام تمرکز خود را بر روی انتقال به ASP Identity با حداقل تغییرات بگذارید. این گونه نباشد که در اواسط کار با خود بگویید که بد نیست حالا فلان کتابخانه را آپدیت کنم یا این تغییر را بدهم بهتر می‌شود. سعی کنید با حداقل تغییرات رو به جلو حرکت کنید؛ آپدیت کردن کتابخانه‌ها را هم بعدا می‌شود انجام داد.
   

 مقایسه ساختار جداول دیتابیس کاربران IRIS با ASP.NET Identity

ساختار جداول ASP.NET Identity به شکل زیر است:

ساختار جداول سیستم کنونی هم بدین شکل است:

همان طور که مشخص است در هر دو سیستم، بین ساختار جداول و رابطه‌ی بین آن‌ها شباهت‌ها و تفاوت هایی وجود دارد. سیستم Identity دو جدول بیشتر از IRIS دارد و برای جداولی که در سیستم کنونی وجود ندارند نیاز به انجام کاری نیست و به هنگام پیاده سازی Identity، این جداول به صورت خودکار به دیتابیس اضافه خواهند شد.

دو جدول مشترک در این دو سیستم، جداول Users و Roles هستندکه نحوه‌ی ارتباطشان با یکدیگر متفاوت است. در Iris بین User و Role رابطه‌ی یک به چند وجود دارد ولی در Identity، رابطه‌ی بین این دو جدول چند به چند است و جدول واسط بین آن‌ها نیز UserRoles نام دارد.

از آن جایی که من قصد دارم در سیستم جدید هم رابطه‌ی بین کاربر و نقش چند به چند باشد، به پیش فرض‌های Identity کاری ندارم. به رابطه‌ی کنونی یک به چند کاربر و نقشش نیز دست نمی‌گذارم تا در انتها با یک کوئری از دیتابیس، اطلاعات نقش‌های کاربران را به جدول جدیدش منتقل کنم.

جدولی که در هر دو سیستم مشترک است و هسته‌ی آن‌ها را تشکیل می‌دهد، جدول Users است. اگر دقت کنید می‌بینید که این جدول در هر دو سیستم، دارای یک سری فیلد مشترک است که دقیقا هم نام هستند مثل Id، UserName و Email؛ پس این فیلد‌ها از نظر کاربرد در هر دو سیستم یکسان هستند و مشکلی ایجاد نمی‌کنند.

یک سری فیلد هم در جدول User در سیستم IRIS هست که در Identity نیست و بلعکس. با این فیلد‌ها نیز کاری نداریم چون در هر دو سیستم کار مخصوص به خود را انجام می‌دهند و تداخلی در کار یکدیگر ایجاد نمی‌کنند.

اما فیلدی که برای ذخیره سازی پسورد در هر دو سیستم استفاده می‌شود دارای نام‌های متفاوتی است. در Iris این فیلد Password نام دارد و در Identity نامش PasswordHash است.

برای اینکه در سیستم کنونی، نام  فیلد Password جدول User را به PasswordHash تغییر دهیم قدم‌های زیر را بر می‌داریم:

وارد پروژه‌ی DomainClasses شده و کلاس User را باز کنید. سپس نام خاصیت Password را به PasswordHash تغییر دهید.  پس از این تغییر بلافاصله یک گزینه زیر آن نمایان می‌شود که می‌خواهد در تمام جاهایی که از این نام استفاده شده است را به نام جدید تغییر دهد؛ آن را انتخاب کرده تا همه جا Password به PasswordHash تغییر کند.

برای این که این تغییر نام بر روی دیتابیس نیز اعمال شود باید از Migration استفاده کرد. در اینجا من از مهاجرت دستی که بر اساس کد هست استفاده می‌کنم تا هم بتوانم کدهای مهاجرت را پیش از اعمال بررسی و هم تاریخچه‌ای از تغییرات را ثبت کنم.

برای این کار، Package Manager Console را باز کرده و از نوار بالایی آن، پروژه پیش فرض را بر روی DataLayer قرار دهید. سپس در کنسول، دستور زیر را وارد کنید:

Add-Migration Rename_PasswordToPasswordHash_User

اگر وارد پوشه Migrations پروژه DataLayer خود شوید، باید کلاسی با نامی شبیه به 201510090808056_Rename_PasswordToPasswordHash_User ببینید. اگر آن را باز کنید کدهای زیر را خواهید دید: 

  public partial class Rename_PasswordToPasswordHash_User : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.Users", "PasswordHash", c => c.String(nullable: false, maxLength: 200));
            DropColumn("dbo.Users", "Password");
        }
        
        public override void Down()
        {
            AddColumn("dbo.Users", "Password", c => c.String(nullable: false, maxLength: 200));
            DropColumn("dbo.Users", "PasswordHash");
        }
    }

بدیهی هست که این کدها عمل حذف ستون Password را انجام میدهند که سبب از دست رفتن اطلاعات می‌شود. کد‌های فوق را به شکل زیر ویرایش کنید تا تنها سبب تغییر نام ستون Password به PasswordHash شود.

    public partial class Rename_PasswordToPasswordHash_User : DbMigration
    {
        public override void Up()
        {
            RenameColumn("dbo.Users", "Password", "PasswordHash");
        }
        
        public override void Down()
        {
            RenameColumn("dbo.Users", "PasswordHash", "Password");
        }
    }

سپس باز در کنسول دستور Update-Database  را وارد کنید تا تغییرات بر روی دیتابیس اعمال شود.

دلیل اینکه این قسمت را مفصل بیان کردم این بود که می‌خواستم در مهاجرت از سیستم اعتبارسنجی خودتان به ASP.NET Identity دید بهتری داشته باشید.

تا به این جای کار فقط پایگاه داده سیستم کنونی را برای مهاجرت آماده کردیم و هنوز ASP.NET Identity را وارد پروژه نکردیم. در بخش‌های بعدی Identity را نصب کرده و تغییرات لازم را هم انجام می‌دهیم.

مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت اول
در طی چند قسمت، نحوه‌ی طراحی یک سیستم افزونه پذیر را با ASP.NET MVC بررسی خواهیم کرد. عناوین مواردی که در این سری پیاده سازی خواهند شد به ترتیب ذیل هستند:
1- چگونه Area‌های استاندارد را تبدیل به یک افزونه‌ی مجزا و منتقل شده‌ی به یک اسمبلی دیگر کنیم.
2- چگونه ساختار پایه‌ای را جهت تامین نیازهای هر افزونه جهت تزریق وابستگی‌ها تا ثبت مسیریابی‌ها و امثال آن تدارک ببینیم.
3- چگونه فایل‌های CSS ، JS و همچنین تصاویر ثابت هر افزونه را داخل اسمبلی آن قرار دهیم تا دیگر نیازی به ارائه‌ی مجزای آ‌ن‌ها نباشد.
4- چگونه Entity Framework Code-First را با این طراحی یکپارچه کرده و از آن جهت یافتن خودکار مدل‌ها و موجودیت‌های خاص هر افزونه استفاده کنیم؛ به همراه مباحث Migrations خودکار و همچنین پیاده سازی الگوی واحد کار.


در مطلب جاری، موارد اول و دوم بررسی خواهند شد. پیشنیازهای آن مطالب ذیل هستند:
الف) منظور از یک Area چیست؟
ب) توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
ج) آشنایی با تزریق وابستگی‌ها در ASP.NET MVC و همچنین اصول طراحی یک سیستم افزونه پذیر به کمک StructureMap
د) آشنایی با رخدادهای Build


تبدیل یک Area به یک افزونه‌ی مستقل

روش‌های زیادی برای خارج کردن Areaهای استاندارد ASP.NET MVC از یک پروژه و قرار دادن آن‌ها در اسمبلی‌های دیگر وجود دارند؛ اما در حال حاضر تنها روشی که نگهداری می‌شود و همچنین اعضای آن همان اعضای تیم نیوگت و ASP.NET MVC هستند، همان روش استفاده از Razor Generator است.
بنابراین ساختار ابتدایی پروژه‌ی افزونه پذیر ما به صورت ذیل خواهد بود:
1) ابتدا افزونه‌ی Razor Generator را نصب کنید.
2) سپس یک پروژه‌ی معمولی ASP.NET MVC را آغاز کنید. در این سری نام MvcPluginMasterApp برای آن در نظر گرفته شده‌است.
3) در ادامه یک پروژه‌ی معمولی دیگر ASP.NET MVC را نیز به پروژه‌ی جاری اضافه کنید. برای مثال نام آن در اینجا MvcPluginMasterApp.Plugin1 تنظیم شده‌است.
4) به پروژه‌ی MvcPluginMasterApp.Plugin1 یک Area جدید و معمولی را به نام NewsArea اضافه کنید.
5) از پروژه‌ی افزونه، تمام پوشه‌های غیر Area را حذف کنید. پوشه‌های Controllers و Models و Views حذف خواهند شد. همچنین فایل global.asax آن‌را نیز حذف کنید. هر افزونه، کنترلرها و Viewهای خود را از طریق Area مرتبط دریافت می‌کند و در این حالت دیگر نیازی به پوشه‌های Controllers و Models و Views واقع شده در ریشه‌ی اصلی پروژه‌ی افزونه نیست.
6) در ادامه کنسول پاور شل نیوگت را باز کرده و دستور ذیل را صادر کنید:
  PM> Install-Package RazorGenerator.Mvc
این دستور را باید یکبار بر روی پروژه‌ی اصلی و یکبار بر روی پروژه‌ی افزونه، اجرا کنید.


همانطور که در تصویر نیز مشخص شده‌است، برای اجرای دستور نصب RazorGenerator.Mvc نیاز است هربار پروژه‌ی پیش فرض را تغییر دهید.
7) اکنون پس از نصب RazorGenerator.Mvc، نوبت به اجرای آن بر روی هر دو پروژه‌ی اصلی و افزونه است:
  PM> Enable-RazorGenerator
بدیهی است این دستور را نیز باید همانند تصویر فوق، یکبار بر روی پروژه‌ی اصلی و یکبار بر روی پروژه‌ی افزونه اجرا کنید.
همچنین هربار که View جدیدی اضافه می‌شود نیز باید این‌کار را تکرار کنید یا اینکه مطابق شکل زیر، به خواص View جدید مراجعه کرده و Custom tool آن‌را به صورت دستی به RazorGenerator تنظیم نمائید. دستور Enable-RazorGenerator این‌کار را به صورت خودکار انجام می‌دهد.


تا اینجا موفق شدیم Viewهای افزونه را داخل فایل dll آن مدفون کنیم. به این ترتیب با کپی کردن افزونه به پوشه‌ی bin پروژه‌ی اصلی، دیگر نیازی به ارائه‌ی فایل‌های View آن نیست و تمام اطلاعات کنترلرها، مدل‌ها و Viewها به صورت یکجا از فایل dll افزونه‌ی ارائه شده خوانده می‌شوند.


کپی کردن خودکار افزونه به پوشه‌ی Bin پروژه‌ی اصلی

پس از اینکه ساختار اصلی کار شکل گرفت، هربار پس از کامپایل افزونه (یا افزونه‌ها)، نیاز است فایل‌های پوشه‌ی bin آن‌را به پوشه‌ی bin پروژه‌ی اصلی کپی کنیم (پروژه‌ی اصلی در این حالت هیچ ارجاع مستقیمی را به افزونه‌ی جدید نخواهد داشت). برای خودکار سازی این کار، به خواص پروژه‌ی افزونه مراجعه کرده و قسمت Build events آن‌را به نحو ذیل تنظیم کنید:


در اینجا دستور ذیل در قسمت Post-build event نوشته شده است:
 Copy "$(ProjectDir)$(OutDir)$(TargetName).*" "$(SolutionDir)MvcPluginMasterApp\bin\"
و سبب خواهد شد تا پس از هر کامپایل موفق، فایل‌های اسمبلی افزونه به پوشه‌ی bin پروژه‌ی MvcPluginMasterApp به صورت خودکار کپی شوند.


تنظیم فضاهای نام کلیه مسیریابی‌های پروژه

در همین حالت اگر پروژه را اجرا کنید، موتور ASP.NET MVC به صورت خودکار اطلاعات افزونه‌ی کپی شده به پوشه‌ی bin را دریافت و به Application domain جاری اعمال می‌کند؛ برای اینکار نیازی به کد نویسی اضافه‌تری نیست و خودکار است. برای آزمایش آن فقط کافی است یک break point را داخل کلاس RazorGeneratorMvcStart افزونه قرار دهید.
اما ... پس از اجرا، بلافاصله پیام تداخل فضاهای نام را دریافت می‌کنید. خطاهای حاصل عنوان می‌کند که در App domain جاری، دو کنترلر Home وجود دارند؛ یکی در پروژه‌ی اصلی و دیگری در پروژه‌ی افزونه و مشخص نیست که مسیریابی‌ها باید به کدامیک ختم شوند.
برای رفع این مشکل، به فایل NewsAreaAreaRegistration.cs پروژه‌ی افزونه مراجعه کرده و مسیریابی آن‌را به نحو ذیل تکمیل کنید تا فضای نام اختصاصی این Area صریحا مشخص گردد.
using System.Web.Mvc;
 
namespace MvcPluginMasterApp.Plugin1.Areas.NewsArea
{
    public class NewsAreaAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "NewsArea";
            }
        }
 
        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "NewsArea_default",
                "NewsArea/{controller}/{action}/{id}",
                // تکمیل نام کنترلر پیش فرض
                new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                // مشخص کردن فضای نام مرتبط جهت جلوگیری از تداخل با سایر قسمت‌های برنامه
                namespaces: new[] { string.Format("{0}.Controllers", this.GetType().Namespace) }
            );
        }
    }
}
همینکار را باید در پروژه‌ی اصلی و هر پروژه‌ی افزونه‌ی جدیدی نیز تکرار کرد. برای مثال به فایل RouteConfig.cs پروژه‌ی اصلی مراجعه کرده و تنظیم ذیل را اعمال نمائید:
using System.Web.Mvc;
using System.Web.Routing;
 
namespace MvcPluginMasterApp
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                // مشخص کردن فضای نام مرتبط جهت جلوگیری از تداخل با سایر قسمت‌های برنامه
                namespaces: new[] { string.Format("{0}.Controllers", typeof(RouteConfig).Namespace) }
            );
        }
    }
}
بدون تنظیم فضاهای نام هر مسیریابی، امکان استفاده‌ی بهینه و بدون خطا از Areaها وجود نخواهد داشت.


طراحی قرارداد پایه افزونه‌ها

تا اینجا با نحوه‌ی تشکیل ساختار هر پروژه‌ی افزونه آشنا شدیم. اما هر افزونه در آینده نیاز به مواردی مانند منوی اختصاصی در منوی اصلی سایت، تنظیمات مسیریابی اختصاصی، تنظیمات EF و  امثال آن نیز خواهد داشت. به همین منظور، یک پروژه‌ی class library جدید را به نام MvcPluginMasterApp.PluginsBase آغاز کنید.
سپس قرار داد IPlugin را به نحو ذیل به آن اضافه نمائید:
using System;
using System.Reflection;
using System.Web.Optimization;
using System.Web.Routing;
using StructureMap;
 
namespace MvcPluginMasterApp.PluginsBase
{
    public interface IPlugin
    {
        EfBootstrapper GetEfBootstrapper();
        MenuItem GetMenuItem(RequestContext requestContext);
        void RegisterBundles(BundleCollection bundles);
        void RegisterRoutes(RouteCollection routes);
        void RegisterServices(IContainer container);
    }
 
    public class EfBootstrapper
    {
        /// <summary>
        /// Assemblies containing EntityTypeConfiguration classes.
        /// </summary>
        public Assembly[] ConfigurationsAssemblies { get; set; }
 
        /// <summary>
        /// Domain classes.
        /// </summary>
        public Type[] DomainEntities { get; set; }
 
        /// <summary>
        /// Custom Seed method.
        /// </summary>
        //public Action<IUnitOfWork> DatabaseSeeder { get; set; }
    }
 
    public class MenuItem
    {
        public string Name { set; get; }
        public string Url { set; get; }
    }
}
پروژه‌ی این قرارداد برای کامپایل شدن، نیاز به بسته‌های نیوگت ذیل دارد:
PM> install-package EntityFramework
PM> install-package Microsoft.AspNet.Web.Optimization
PM> install-package structuremap.web
همچنین باید به صورت دستی، در قسمت ارجاعات پروژه، ارجاعی را به اسمبلی استاندارد System.Web نیز به آن اضافه نمائید.


توضیحات قرار داد IPlugin

از این پس هر افزونه باید دارای کلاسی باشد که از اینترفیس IPlugin مشتق می‌شود. برای مثال فعلا کلاس ذیل را به افزونه‌ی پروژه اضافه نمائید:
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using MvcPluginMasterApp.PluginsBase;
using StructureMap;
 
namespace MvcPluginMasterApp.Plugin1
{
    public class Plugin1 : IPlugin
    {
        public EfBootstrapper GetEfBootstrapper()
        {
            return null;
        }
 
        public MenuItem GetMenuItem(RequestContext requestContext)
        {
            return new MenuItem
            {
                Name = "Plugin 1",
                Url = new UrlHelper(requestContext).Action("Index", "Home", new { area = "NewsArea" })
            };
        }
 
        public void RegisterBundles(BundleCollection bundles)
        {
            //todo: ...
        }
 
        public void RegisterRoutes(RouteCollection routes)
        {
            //todo: add custom routes.
        }
 
        public void RegisterServices(IContainer container)
        {
            // todo: add custom services.
 
            container.Configure(cfg =>
            {
                //cfg.For<INewsService>().Use<EfNewsService>();
            });
        }
    }
}
در قسمت جاری فقط از متد GetMenuItem آن استفاده خواهیم کرد. در قسمت‌های بعد، تنظیمات EF، تنظیمات مسیریابی‌ها و Bundling و همچنین ثبت سرویس‌های افزونه را نیز بررسی خواهیم کرد.
برای اینکه هر افزونه در منوی اصلی ظاهر شود، نیاز به یک نام، به همراه آدرسی به صفحه‌ی اصلی آن خواهد داشت. به همین جهت در متد GetMenuItem نحوه‌ی ساخت آدرسی را به اکشن متد Index کنترلر Home واقع در Area‌ایی به نام NewsArea، مشاهده می‌کنید.


بارگذاری و تشخیص خودکار افزونه‌ها

پس از اینکه هر افزونه دارای کلاسی مشتق شده از قرارداد IPlugin شد، نیاز است آن‌ها را به صورت خودکار یافته و سپس پردازش کنیم. این‌کار را به کتابخانه‌ی StructureMap واگذار خواهیم کرد. برای این منظور پروژه‌ی جدیدی را به نام MvcPluginMasterApp.IoCConfig آغاز کرده و سپس تنظیمات آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using System.Threading;
using System.Web;
using MvcPluginMasterApp.PluginsBase;
using StructureMap;
using StructureMap.Graph;
 
namespace MvcPluginMasterApp.IoCConfig
{
    public static class SmObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }
 
        private static Container defaultContainer()
        {
            return new Container(cfg =>
            {
                cfg.Scan(scanner =>
                {
                    scanner.AssembliesFromPath(
                        path: Path.Combine(HttpRuntime.AppDomainAppPath, "bin"),
                            // یک اسمبلی نباید دوبار بارگذاری شود
                        assemblyFilter: assembly =>
                        {
                            return !assembly.FullName.Equals(typeof(IPlugin).Assembly.FullName);
                        });
 
                    scanner.WithDefaultConventions(); //Connects 'IName' interface to 'Name' class automatically.
                    scanner.AddAllTypesOf<IPlugin>().NameBy(item => item.FullName);
                });
            });
        }
    }
}
این پروژه‌ی class library جدید برای کامپایل شدن نیاز به بسته‌های نیوگت ذیل دارد:
PM> install-package EntityFramework
PM> install-package structuremap.web
همچنین باید به صورت دستی، در قسمت ارجاعات پروژه، ارجاعی را به اسمبلی استاندارد System.Web نیز به آن اضافه نمائید.

کاری که در کلاس SmObjectFactory انجام شده، بسیار ساده است. مسیر پوشه‌ی Bin پروژه‌ی اصلی به structuremap معرفی شده‌است. سپس به آن گفته‌ایم که تنها اسمبلی‌هایی را که دارای اینترفیس IPlugin هستند، به صورت خودکار بارگذاری کن. در ادامه تمام نوع‌های IPlugin را نیز به صورت خودکار یافته و در مخزن تنظیمات خود، اضافه کن.


تامین نیازهای مسیریابی و Bundling هر افزونه به صورت خودکار

در ادامه به پروژه‌ی اصلی مراجعه کرده و در پوشه‌ی App_Start آن کلاس ذیل را اضافه کنید:
using System.Linq;
using System.Web.Optimization;
using System.Web.Routing;
using MvcPluginMasterApp;
using MvcPluginMasterApp.IoCConfig;
using MvcPluginMasterApp.PluginsBase;
 
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(PluginsStart), "Start")]
 
namespace MvcPluginMasterApp
{
    public static class PluginsStart
    {
        public static void Start()
        {
            var plugins = SmObjectFactory.Container.GetAllInstances<IPlugin>().ToList();
            foreach (var plugin in plugins)
            {
                plugin.RegisterServices(SmObjectFactory.Container);
                plugin.RegisterRoutes(RouteTable.Routes);
                plugin.RegisterBundles(BundleTable.Bundles);
            }
        }
    }
}
بدیهی است در این حالت نیاز است ارجاعی را به پروژه‌ی MvcPluginMasterApp.PluginsBase به پروژه‌ی اصلی اضافه کنیم.
دراینجا با استفاده از کتابخانه‌ای به نام WebActivatorEx (که باز هم توسط نویسندگان اصلی Razor Generator تهیه شده‌است)، یک متد PostApplicationStartMethod سفارشی را تعریف کرده‌ایم.
مزیت استفاده از اینکار این است که فایل Global.asax.cs برنامه شلوغ نخواهد شد. در غیر اینصورت باید تمام این کدها را در انتهای متد Application_Start قرار می‌دادیم.
در اینجا با استفاده از structuremap، تمام افزونه‌های موجود به صورت خودکار بررسی شده و سپس پیشنیازهای مسیریابی و Bundling و همچنین تنظیمات IoC Container مورد نیاز آن‌ها به هر افزونه به صورت مستقل، تزریق خواهد شد.


اضافه کردن منو‌های خودکار افزونه‌ها به پروژه‌ی اصلی

پس از اینکه کار پردازش اولیه‌ی IPluginها به پایان رسید، اکنون نوبت به نمایش آدرس اختصاصی هر افزونه در منوی اصلی سایت است. برای این منظور فایل جدیدی را به نام PluginsMenu.cshtml_، در پوشه‌ی shared پروژه‌ی اصلی اضافه کنید؛ با این محتوا:
@using MvcPluginMasterApp.IoCConfig
@using MvcPluginMasterApp.PluginsBase
@{
    var plugins = SmObjectFactory.Container.GetAllInstances<IPlugin>().ToList();
}
 
@foreach (var plugin in plugins)
{
    var menuItem = plugin.GetMenuItem(this.Request.RequestContext);
    <li>
        <a href="@menuItem.Url">@menuItem.Name</a>
    </li>
}
در اینجا تمام افزونه‌ها به کمک structuremap یافت شده و سپس آیتم‌های منوی آن‌ها به صورت خودکار دریافت و اضافه می‌شوند.
سپس به فایل Layout.cshtml_ پروژه‌ی اصلی مراجعه و توسط فراخوانی Html.RenderPartial، آن‌را در بین سایر آیتم‌های منوی اصلی اضافه می‌کنیم:
<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            @Html.ActionLink("MvcPlugin Master App", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li>@Html.ActionLink("Master App/Home", "Index", "Home", new {area = ""}, null)</li>
                @{ Html.RenderPartial("_PluginsMenu"); }
            </ul>
        </div>
    </div>
</div>
اکنون اگر پروژه را اجرا کنیم، یک چنین شکلی را خواهد داشت:



بنابراین به صورت خلاصه

1) هر افزونه، یک پروژه‌ی کامل ASP.NET MVC است که پوشه‌های ریشه‌ی اصلی آن حذف شده‌اند و اطلاعات آن توسط یک Area جدید تامین می‌شوند.
2) تنظیم فضای نام مسیریابی‌های تمام پروژه‌ها را فراموش نکنید. در غیر اینصورت شاهد تداخل پردازش کنترلرهای هم نام خواهید بود.
3) جهت سهولت کار، می‌توان فایل‌های bin هر افزونه را توسط رخداد post-build، به پوشه‌ی bin پروژه‌ی اصلی کپی کرد.
4) Viewهای هر افزونه توسط Razor Generator در فایل dll آن مدفون خواهند شد.
5) هر افزونه باید دارای کلاسی باشد که اینترفیس IPlugin را پیاده سازی می‌کند. از این اینترفیس برای ثبت اطلاعات هر افزونه یا دریافت اطلاعات سفارشی از آن کمک می‌گیریم.
6) با استفاده از استراکچرمپ و قرارداد IPlugin، منوهای هر افزونه را به صورت خودکار یافته و سپس به فایل layout اصلی اضافه می‌کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
MvcPluginMasterApp-Part1.zip
مطالب
رشته ها و پردازش متن در دات نت به زبان ساده
رشته، مجموعه‌ای از کاراکترهاست که پشت سرهم، در مکانی از حافظه قرار گرفته‌اند. هر کاراکتر حاوی یک شماره سریال در جدول یونیکد هست. به طور پیش فرض دات نت برای هر کاراکتر (نوع داده char) شانزده بیت در نظر گرفته است که برای 65536 کاراکتر کافی است.
برای نگهداری از رشته‌ها و انجام عملیات بر روی آنها در دات نت از نوع system.string استفاده می‌کنیم:
string greeting = "Hello, C#";

که در این حالت مجموعه‌ای از کاراکترها را ایجاد خواهد کرد:

اتفاقاتی که در داخل کلاس string رخ می‌دهد بسیار ساده است و ما را از تعریف []char بی‌نیاز می‌کند تا مجبور نشویم خانه‌های  آرایه را به ترتیب پر کنیم. از معایب استفاده از آرایه char میتوان موارد زیر را برشمارد:
  1. خانه‌های آن یک ضرب پر نمیشوند بلکه به ترتیب، خانه به خانه پر می‌شوند.
  2. قبل از انتساب متن باید باید از طول متن مطمئن شویم تا بتوانیم تعداد خانه‌ها را بر اساس آن ایجاد کنیم.
  3. همه عملیات آرایه‌ها از پر کردن ابتدای کار گرفته تا هر عملی، نیاز است به صورت دستی صورت بگیرد و تعداد خطوط کد برای هر کاری هم بالا می‌رود.
البته استفاده از string هم راه حل نهایی برای کار با متون نیست. در انتهای این مطلب مورد دیگری را نیز بررسی خواهیم کرد. از ویژگی دیگر رشته‌ها این است که آن‌ها شباهت زیادی به آرایه‌ای از کاراکتر‌ها دارند؛ ولی اصلا شبیه آن‌ها نیستند و نمی‌توانید به صورت یک آرایه آن‌ها را مقداردهی کنید. البته کلاس string امکاناتی را با استفاده از indexer [] مهیا کرده است که میتوانید بر اساس اندیس‌ها به کاراکترها به صورت جداگانه دسترسی داشته باشید ولی نمی‌توانید آن‌ها را مقدار دهی کنید. این اندیس‌ها از 0 تا طول آن length-1 ادامه دارند.
string str = "abcde";
char ch = str[1]; // ch == 'b'
str[1] = 'a'; // Compilation error!
ch = str[50]; // IndexOutOfRangeException
همانطور که میدانیم برای مقداردهی رشته‌ها از علامت‌های نقل قول "" استفاده میکنیم که باعث میشود اگر بخواهیم علامت " را در رشته‌ها داشته باشیم نتوانیم. برای حل این مشکل از علامت \ استفاده میکنیم که البته باعث استفاده از بعضی کاراکترهای خاص دیگر هم می‌شود:
string a="Hello \"C#\"";
string b="Hello \r\n C#"; //مساوی با اینتر
string c="C:\\a.jpg"; //چاپ خود علامت  \ -مسیردهی
البته اگر از علامت @ در قبل از رشته استفاده شود علامت \ بی اثر خواهد شد.
string c=@"C:\a.jpg";// == "C:\\a.jpg"

مقداردهی رشته‌ها و پایدار (تغییر ناپذیر) بودن آنها Immutable
رشته‌ها ساختاری پایدار هستند؛ به این معنی که به صورت reference مقداردهی می‌شوند. موقعی که شما مقداری را به یک رشته انتساب می‌دهید، مقدار متغیر در  String pool یا لینک در Heap ذخیره می‌شوند و اگر همین متغیر را به یک متغیر دیگر انتساب دهیم، متغیر جدید مقدار آن را دیگر در حافظه پویا (داینامیک) Heap به عنوان مقدار جدید ذخیره نخواهد کرد؛ بلکه تنها یک pointer خواهد بود که به آدرس حافظه متغیر اولی اشاره می‌کند. به مثال زیر دقت کنید. متغیر source مقدار some source را ذخیره می‌کند و بعد همین متغیر، به متغیر assigned انتساب داده میشود؛ ولی مقداری جابجا نمی‌شود. بلکه متغیر assign به آدرسی در حافظه اشاره می‌کند که متغیر source اشاره می‌کند. هرگاه که در یکی از متغیرها، تغییری رخ دهد، همان متغیری که تغییر کرده است، به آدرس جدید با محتوای تغییر داده شده اشاره می‌کند.
string source = "Some source";
string assigned = source;

این ویژگی نوع reference فقط برای ساختارهای Immutable به معنی پایدار رخ می‌دهد و نه برای ساختار‌های ناپایدار (تغییر پذیر)  mutable؛ به این خاطر که آن‌ها مقادیرشان را مستقیما تغییر میدهند و اشاره‌ای در حافظه صورت نمی‌گیرد. 
string hel = "Hel";
string hello = "Hello";
string copy = hel + "lo";

string hello = "Hello";
string same = "Hello";

برای اطلاعات بیشتر در این زمینه این لینک را مطالعه نمایید.


مقایسه رشته‌ها
برای مقایسه دو رشته میتوان از علامت == یا از متد Equals استفاده نماییم. در این حالت به خاطر اینکه کد حروف کوچک و بزرگ متفاوت است، مقایسه حروف هم متفاوت خواهد بود. برای اینکه حروف کوچک و بزرگ تاثیری بر مقایسه ما نگذارند و #c را با #C برابر بدانند باید از متد Equals به شکل زیر استفاده کنیم:
Console.WriteLine(word1.Equals(word2,
    StringComparison.CurrentCultureIgnoreCase));
برای اینکه بزرگی و کوچکی اعداد را مشخص کنیم از علامت‌های < و > استفاده میکنیم ولی برای رشته‌ها از متد CompareTo بهره می‌بریم که چینش قرارگیری آن‌ها را بر اساس حروف الفبا مقایسه می‌کند و سه عدد، می‌تواند خروجی آن باشند. اگر 0 باشد یعنی برابر هستند، اگر -1 باشد رشته اولی قبل از رشته دومی است و اگر 1 باشد رشته دومی قبل از رشته اولی است.
string score = "sCore";
string scary = "scary";
 
Console.WriteLine(score.CompareTo(scary));
Console.WriteLine(scary.CompareTo(score));
Console.WriteLine(scary.CompareTo(scary));
 
// Console output:
// 1
// -1
// 0
 اینبار هم برای اینکه حروف کوچک و بزرگ، دخالتی در کار نداشته باشند، میتوانید از داده شمارشی StringComparison در متد ایستای (string.Compare(s1,s2,StringComparison استفاده نمایید؛ یا از نوع داده‌ای boolean برای تعیین نوع مقایسه استفاده کنید.
string alpha = "alpha";
string score1 = "sCorE";
string score2 = "score";
 
Console.WriteLine(string.Compare(alpha, score1, false));
Console.WriteLine(string.Compare(score1, score2, false));
Console.WriteLine(string.Compare(score1, score2, true));
Console.WriteLine(string.Compare(score1, score2,
    StringComparison.CurrentCultureIgnoreCase));
// Console output:
// -1
// 1
// 0
// 0
نکته : برای مقایسه برابری  دو رشته از متد Equals یا == استفاده کنید و فقط برای تعیین کوچک یا بزرگ بودن از compare‌ها استفاده نمایید. دلیل آن هم این است که برای مقایسه از فرهنگ culture فعلی سیستم استفاده میشود و نظم جدول یونیکد را رعایت نمی‌کنند و ممکن است بعضی رشته‌های نابرابر با یکدیگر برابر باشند. برای مثال در زبان آلمانی دو رشته "SS" و "ß " با یکدیگر برابر هستند.

عبارات با قاعده Regular Expression
این عبارات الگوهایی هستند که قرار است عبارات مشابه الگویی را در رشته‌ها پیدا کنند. برای مثال الگوی +[A-Z0-9] مشخص می‌کند که رشته مورد نظر نباید خالی باشد و حداقل با یکی از حروف بزرگ یا اعداد پرشده باشد. این الگوها میتوانند برای واکشی داده‌ها یا قالب‌های خاص در رشته‌ها به کار بروند. برای مثال شماره تماس‌ها ، پست الکترونیکی و ...
در اینجا میتواند نحوه‌ی الگوسازی را بیاموزید. کد زیر بر اساس یک الگو، شماره تماس‌های مورد نظر را یافته و البته با فیلتر گذاری آن‌ها را نمایش می‌دهد:
string doc = "Smith's number: 0898880022\nFranky can be " +
    "found at 0888445566.\nSteven's mobile number: 0887654321";
string replacedDoc = Regex.Replace(
    doc, "(08)[0-9]{8}", "$1********");
Console.WriteLine(replacedDoc);
// Console output:
// Smith's number: 08********
// Franky can be found at 08********.
// Steven' mobile number: 08********
سه شماره تماس در رشته‌ی بالا با الگوی ما همخوانی دارند که بعد با استفاده از متد replace در شی Regex عبارات دلخواه خودمان را جایگزین شماره تماس‌ها خواهیم کرد. الگوی بالا شماره تماس‌هایی را میابد که با 08 آغاز شده‌اند و بعد از آن 8 عدد دیگر از 0 تا 9 قرار گرفته‌اند. بعد از اینکه متن مطابق الگو یافت شد، ما آن را با الگوی ********1$ جایگزین می‌کنیم که علامت $ یک placeholder برای یک گروه است. هر عبارت () در عبارات با قاعده یک گروه حساب میشود و اولین پرانتر 1$ و دومین پرانتز یا گروه میشود 2$ که در عبارت بالا (08) میشود 1$ و به جای مابقی الگو، 8 علامت ستاره نمایش داده میشود.

اتصال رشته‌ها در Loop
برای اتصال رشته‌ها ما از علامت + یا متد ایستای string.concat استفاده می‌کنیم ولی استفاده‌ی از آن در داخل یک حلقه باعث کاهش کارآیی برنامه خواهد شد. برای همین بیایید ببینم در حین اتتقال رشته‌ها در حافظه چه اتفاقی رخ میدهد. ما در اینجا دو رشته str1 و str2 داریم که عبارات "super" و "star" را نگه داری می‌کنند و در واقع دو متغیر هستند که به حافظه‌ی پویای Heap اشاره می‌کنند. اگر این دو را با هم جمع کنیم و نتیجه را در متغیر result قرار دهیم، سه متغیر میشوند که هر کدام به حافظه‌ای جداگانه در heap اشاره می‌کنند. در واقع برای این اتصال، قسمت جدیدی از حافظه تخصصیص داده شده و مقدار جدید در آن نشسته‌است. در این حالت یک متغیر جدید ساخته شد که به آدرس آن اشاره می‌کند. کل این فرآیند یک فرآیند کاملا زمانبر است که با تکرار این عمل موجب از دست دادن کارآیی برنامه می‌شود؛ به خصوص اگر در یک حلقه این کار صورت بگیرد.
سیستم دات نت همانطور که میدانید شامل GC یا سیستم خودکار پاکسازی حافظه است که برنامه نویس را از dispose کردن بسیاری از اشیاء بی نیاز می‌کند. موقعی‌که متغیری به قسمتی از حافظه اشاره می‌کند که دیگر بلا استفاده است، سیستم GC به صورت خودکار آنها را پاکسازی می‌کند که این عمل زمان بر هم خودش موجب کاهش کارآیی می‌شود. همچنین انتقال رشته‌ها از یک مکان حافظه به مکانی دیگر، باز خودش یک فرآیند زمانبر است؛ به خصوص اگر رشته مورد نظر طولانی هم باشد.
مثال عملی: در تکه کد زیر قصد داریم اعداد 1 تا 20000 را در یک رشته الحاق کنیم:
 DateTime dt = DateTime.Now;
            string s = "";
        for (int index = 1; index <= 20000; index++)
        {
            s += index.ToString();
        }
            Console.WriteLine(s);
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
کد بالا تاز زمان نمایش کامل، بسته به قدرت سیستم ممکن است یکی دو ثانیه طول بکشد. حالا عدد را به 200000 تغییر دهید (یک صفر اضافه تر). برنامه را اجرا کنید و مجددا تست بزنید. در این حالت چند دقیقه ای بسته به قدرت سیستم زمان خواهد برد؛ مثلا دو دقیقه یا سه دقیقه یا کمتر و بیشتر.
عملیاتی که در حافظه صورت میگیرد این چند گام را طی میکند:
  • قسمتی از حافظه به طور موقت برای این دور جدید حلقه، گرفته میشود که به آن بافر میگوییم.
  • رشته قبلی به بافر انتقال میابد که بسته به مقدار آن زمان بر و کند است؛ 5 کیلو یا 5 مگابایت یا 50 مگابایت و ...
  • شماره تولید شده جدید به بافر چسبانده میشود.
  • بافر به یک رشته تبدیل میشود وجایی برای خود در حافظه Heap میگیرد.
  • حافظه رشته قدیمی و بافر دیگر بلا استفاده شده‌اند و توسط GC پاکسازی میشوند که ممکن است عملیاتی زمان بر باشد.

String Builder
این کلاس ناپایدار و تغییر پذیر است. به کد و شکل زیر دقت کنید:
string declared = "Intern pool";
string built = new StringBuilder("Intern pool").ToString();

این کلاس دیگر مشکل الحاق رشته‌ها یا دیگر عملیات پردازشی را ندارد. بیایید مثال قبل را برای این کلاس هم بررسی نماییم:
 StringBuilder sb = new StringBuilder();
      sb.Append("Numbers: ");

            DateTime dt = DateTime.Now;
        for (int index = 1; index <= 200000; index++)
        {
            sb.Append(index);
        }
            Console.WriteLine(sb.ToString());
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
اکنون همین عملیات چند دقیقه‌ای قبل، در زمانی کمتر، مثلا دو ثانیه انجام میشود.
حال این سوال پیش می‌آید مگر کلاس stringbuilder چه میکند که زمان پردازش آن قدر کوتاه است؟
همانطور که گفتیم این کلاس mutable یا تغییر پذیر است و برای انجام عملیات‌های ویرایشی نیازی به ایجاد شیء جدید در حافظه ندارد؛ در نتیجه باعث کاهش انتقال غیرضروری داده‌ها برای عملیات پایه‌ای چون الحاق رشته‌ها میگردد.
stringbuilder شامل یک بافر با ظرفیتی مشخص است (به طور پیش فرض 16 کاراکتر). این کلاس آرایه‌هایی از کاراکترها را پیاده سازی میکند که برای عملیات و پردازش‌هایش  از یک رابط کاربرپسند برای برنامه نویسان استفاده می‌کند. اگر تعداد کاراکترها کمتر از 16 باشد مثلا 5 ، فقط 5 خانه آرایه استفاده میشود و مابقی خانه‌ها خالی میماند و با اضافه شدن یک کاراکتر جدید، دیگر شیء جدیدی در حافظه درست نمی‌شود؛ بلکه در خانه ششم قرار می‌گیرد و اگر تعداد کاراکترهایی که اضافه می‌شوند باعث شود از 16 کاراکتر رد شود، مقدار خانه‌ها دو برابر میشوند؛ هر چند این عملیات دو برابر شدن resizing عملیاتی کند است ولی این اتفاق به ندرت رخ می‌دهد.
کد زیر یک آرایه 15 کاراکتری ایجاد می‌کند و عبارت #Hello C را در آن قرار می‌دهد.
StringBuilder sb = new StringBuilder(15);
sb.Append("Hello, C#!");

در شکل بالا خانه هایی خالی مانده است Unused و  جا برای کاراکترهای جدید به اندازه خانه‌های unused هست و اگر بیشتر شود همانطور که گفتیم تعداد خانه‌ها 2 برابر می‌شوند که در اینجا میشود 30.

استفاده از متد ایستای string.Format
از این متد برای نوشتن یک متن به صورت قالب و سپس جایگزینی مقادیر استفاده می‌شود:
DateTime date = DateTime.Now;
string name = "David Scott";
string task = "Introduction to C# book";
string location = "his office";
 
string formattedText = String.Format(
    "Today is {0:MM/dd/yyyy} and {1} is working on {2} in {3}.",
    date, name, task, location);
Console.WriteLine(formattedText);
در کد بالا ابتدا ساختار قرار گرفتن تاریخ را بر اساس الگو بین {} مشخص می‌کنیم و متغیر date در آن قرار می‌گیرد و سپس برای {1},{2},{3} به ترتیب قرار گیری آن‌ها متغیرهای name,last,location قرار میگیرند.
از ()ToString. هم می‌توان برای فرمت بندی خروجی استفاده کرد؛ مثل همین عبارت MM/dd/yyyy در خروجی نوع داده تاریخ و زمان.
نظرات مطالب
راهنمای انتخاب فناوری‌های مختلف
چرا اتفاقا، چون خود مایکروسافت نسخه‌ی Mac آن‌را منتشر کرده و پشتیبانی می‌کند. لینوکسی‌ها هم تحت پروژه Moonlight به آن دسترسی دارند. فقط نسبت به HTML خالص دسترسی موبایل آن شاید کمتر باشد.
البته چون سیلورلایت قابلیت اجرای خارج از مرورگر را هم دارد، در رده‌ی برنامه‌های معمولی ویندوز هم می‌تواند قرار گیرد.