مطالب
یک سرویس (میکروسرویس) چیست؟ و چگونه آن را مستند کنیم؟ (قسمت دوم)
در قسمت اول این مقاله ، مشخصات کلیدی یک سرویس مورد بررسی قرار گرفت و  API‌ها و وابستگی‌ها یا Dependencies هر سرویس نیز مورد بررسی قرار گرفتند. همانطور که مشخص است با زیاد شدن این سرویس‌ها و وابستگی هایی که به یکدیگر پیدا میکنند، سردرگمی‌ها نیز برای اعضای تیم‌های مختلف، زیاد میگردد. چرا که افراد هر تیم دائما باید از API‌های ارائه شده توسط تیم‌های دیگر مطلع باشند. به همین جهت زمانیکه شما یک سبک معماری مانند میکروسرویس را انتخاب می‌نمایید، باید یک روش مستند سازی مناسب را نیز انتخاب نمایید تا مانع از پیچیدگی و سردرگمی ناشی از نبود مستندات مناسب و سربار هماهنگی بین تیمی شوید.

در این مطلب، 2 روش زیر، جهت مستند سازی سرویس‌ها بررسی می‌شوند:
 روش اول - کارت طراحی میکروسرویس (microservice design card)
 روش دوم - بوم میکروسرویس ( microservice canvas)

لازم به ذکر است که دو روش فوق می‌توانند مکمل یکدیگر باشند؛ همچنین این اسناد (علاوه بر مفید بودن برای مصرف کنندگان سرویس) حتی جهت شناسایی سرویس‌ها و ارتباطات بین آنها در زمان تحلیل (در جلساتی مانند event storming) نیز می‌توانند کاربردی باشند.


روش اول - کارت طراحی میکروسرویس (microservice design card) 
روش کارت طراحی میکروسرویس بر اساس کارت‌های CRC که گاهی اوقات در Object-oriented design استفاده می‌شوند، مدل سازی شده‌است و می‌توان از آن جهت ثبت خدمات سرویس و همچنین تعاملات سرویس، با سایر سرویس ها، استفاده نمود (این اطلاعات نسبت به اطلاعات قابل درج در microservice canvas که در ادامه بررسی می‌شود، کمتر است).
می‌توانید این کارت‌ها را در ابعاد کوچک و به تعداد کافی پرینت و در هنگام تحلیل و طراحی سرویس(ها)، از آنها استفاده نمایید
در نهایت جهت کشیدن نقشه وابستگی سرویس‌ها، کارت‌ها روی یک برد نصب و با کشیدن خطوط بین سرویس‌ها، وابستگی‌های هر یک را مشخص نمایید. مزیت اصلی این روش در طراحی، همکاری بیشتر تیم‌ها با یکدیگر می‌باشد.

(نمونه ای از کارت طراحی ‌های مربوط سرویس‌های project و archive)



روش دوم  - بوم میکروسرویس (microservice canvas)
یکی دیگر از روش‌های مناسب برای مستند سازی یک سرویس و ساختار درونی آن، استفاده از روش بوم میکروسرویس می‌باشد. بوم میکروسرویس نیز تا حدودی شبیه به کارت‌های CRC و همچنین روش microservice design card که پیش‌تر بررسی گردید، می‌باشد؛ با این تفاوت که اطلاعات بیشتری را نگهداری می‌نماید.
روش طراحی روی بوم جهت مستند سازی، از گذشته در صنایع مختلفی از جمله صنعت نرم افزار رایج بوده است؛ ولی ظاهرا برای اولین بار در سال 2017 و در این مقاله  (از سایت DZone که توسط Matt McLarty و Irakli Nadareishvili منتشر شده‌است) از این روش به عنوان روشی برای مستند سازی سرویس‌ها (در معماری میکروسرویس) استفاده شده‌است . پس از آن و در طول زمان، نمونه‌های مختلف و نسبتا مشابهی از این بوم توسط افراد و شرکت‌های مختلف ارائه شد (که با جستجوی عبارت microservice canvas در بخش تصاویر سایت گوگل می‌توانید نمونه‌های متفاوت آن را بررسی نمایید). در ادامه این مقاله، بوم میکروسرویسی که آقای ریچاردسون در سال 2019 معرفی نمودند، بررسی می‌گردد.

تصویر فوق بوم مربوط به سرویس Order می‌باشد

با توجه به تصویر فوق و مفاهیم بررسی شده در قسمت قبلی این مقاله، به بررسی بخش‌های مختلف بوم میکروسرویس می‌پردازیم.

نمای بیرونی یک سرویس (A service’s external view) در بوم میکروسرویس توسط بخش‌های زیر معرفی می‌گردد
• Name – نام سرویس
• Description – ارائه یک توضیح مختصر در مورد سرویس
• Capabilities – معرفی بخش‌هایی از منطق کسب و کار که در این سرویس پیاده سازی شده‌است.
• Service APIs – معرفی عملیات یا operations (شامل commands  و queries) که در این سرویس پیاده سازی شده‌اند و همچنین معرفی وقایع یا همان domain event هایی که توسط سرویس منتشر می‌شوند.
• Quality attributes – معرفی non-functional requirements‌های سرویس
• Observability (قابلیت‌های مشاهده و بررسی  سرویس) – معرفی health check endpoints و key metrics و ... .

وابستگی‌های یک سرویس (A service’s dependencies)  که لزوما استفاده بیرونی ندارد و بیشتر منبعی برای خود اعضای تیم خواهد بود، در بخشی تحت عنوان dependencies مشخص می‌شوند که خود شامل دو قسمت به شرح زیر می‌باشد: 
• Invokes – عملیاتی که در سایر سرویس‌ها پیاده سازی شده‌اند و در این سرویس فراخوانی می‌گردند.
• Subscribes – اشتراک در کانال پیام‌هایی که شامل وقایع سایر سرویس‌ها می‌باشند.

موارد مربوط به پیاده سازی یک سرویس (A service’s implementation)
در بوم میکروسرویس علاوه بر تمام موارد فوق، شما میتوانید مدل پیاده سازی لایه دامنه را نیز معرفی نمایید. همچنین نام bounded context‌ها و aggregateهای پیاده سازی شده در این سرویس، در این بخش نوشته می‌شود (که در یک حالت ایده آل، تنها یک agg از یک bc در یک سرویس پیاده سازی خواهد شد).

تولید بوم میکروسرویس 
با توجه به دغدغه ناشی از به روز نگه داشتن بوم میکروسرویس (و به طور کلی مستندات پروژه) همراه با تغییرات پروژه، تکنیک‌ها و ابزارهایی جهت تولید خودکار فایل json بوم میکروسرویس (microservice-canvas-tools) و همچنین جهت به تصویر کشیدن فایل json تولید شده (microservices-design-canvas-editor ) وجود دارد. اما اگر ابزار مناسبی را با توجه به پلتفرم مورد نظر، نتواستید پیدا کنید و یا فرصت توسعه یک ابزار اختصاصی نبود، همواره می‌توان این فایل را به صورت دستی نیز ایجاد نمود و در مخزن کد مربوط به پروژه و در کنار سورس اصلی نگه داری کرد تا همراه با سایر مستندات پروژه، این سند نیز پس از هر تغییر به روز شود. نسخه‌ای از آن را نیز می‌توان در محلی مناسب با سایر تیم‌ها به اشتراک گذاشت.

در صورتیکه قصد تولید و توسعه دستی این سند را دارید، نسخه‌ای از آن را در قالب فایل ورد، می‌توانید در این مخزن در گیت هاب پیدا نمایید.
مطالب
روش‌هایی برای بهبود قابلیت دیباگ بسته‌های NuGet
عموما بسته‌های نیوگت تولید شده، قابلیت دیباگ ضعیفی را دارند. برای بالابردن بهبود تجربه‌ی کاربری آن‌ها می‌توان توزیع فایل‌های PDB و فعالسازی قابلیت Source Link را به آن‌ها اضافه کرد.


فعالسازی توزیع فایل‌های PDB به همراه بسته‌های NuGet

وجود فایل‌های PDB، برای اجرای برنامه‌ها ضرورتی ندارند؛ اما اگر ارائه شوند، به کمک آن‌ها می‌توان گزارش‌های استثناءهای بسیار کاملتری را به همراه نام فایل و شماره سطرهای مرتبط موجود در Exception.StackTrace، مشاهده کرد.
پیشتر توسعه دهندگان بسته‌های NuGet، فایل‌های PDB را خودشان با تعریف یک سری include در فایل مشخصات بسته، به فایل نهایی تولیدی اضافه می‌کردند. اما این روزها با ارائه‌ی «NuGet.org Symbol Server»، دیگر افزودن فایل‌های PDB به بسته‌های nupkg توصیه نمی‌شود. چون حجم نهایی و زمان بازیابی بسته‌ها را بیش از اندازه افزایش می‌دهند؛ خصوصا اگر مصرف کننده‌ای قصد دیباگ آن‌ها را نداشته باشد.
راه حل جدید توصیه شده، ارائه‌ی فایل‌های ویژه‌ی snupkg. در کنار بسته‌های nupkg. معمولی است که حاوی فایل‌های PDB متناظر با بسته‌ی اصلی NuGet هستند.

برای فعالسازی آن‌ها در پروژه‌های NET Core. بسته‌های نیوگت خود تنها کافی است دو تنظیم زیر را به فایل csproj اضافه کنید:
<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
در این حالت پس از ساخت بسته‌ی نیوگت توسط دستور «dotnet pack -c release»، دو فایل با پسوندهای snupkg و nupkg تولید می‌شوند که باید هر دو را به سایت NuGet ارسال کرد.

در سمت مصرف کننده، IDE شما باید برای کار با این Symbol Server تنظیم شده باشد.


فعالسازی تولید Source Link

وجود PDBها جهت دیباگ بسته‌های ارائه شده بسیار مفیدند؛ اما اگر بر روی کدهای نهایی بهینه سازی صورت گرفته باشد، ممکن است اطلاعات دیباگ آن‌ها با کد اصلی تطابق پیدا نکنند. برای بهبود این وضعیت و ارتقاء آن به یک سطح بالاتر، مفهوم source link ارائه شده‌است که مستقل است از نوع زبان و همچنین سورس کنترل.
کار سورس‌لینک، افزودن متادیتای سورس کنترل انتخابی، به بسته‌ی نهایی تولید شده‌است. به این صورت می‌توان سورس کامل و متناظر با قطعه کد بسته و کتابخانه‌ی ثالث در حال دیباگ را در IDE خود داشت و با آن به نحو متداولی کار کرد. یعنی سورس لینک، قابلیت «Step Into" the source code"» را مهیا می‌کند. متادیتای اضافه شده، دقیقا مشخص می‌کند که بسته‌ی تولیدی نهایی، متناظر با کدام commit سورس کنترل است. به این ترتیب دقیقا می‌توان به کدهای همان commit ای که بسته بر اساس آن کامپایل شده‌است، در IDE خود دسترسی یافت.
این قابلیت از Visual Studio 15.3 به بعد در اختیار کاربران آن است (گزینه‌ی Enable Source Server Support، در قسمت Debug/General آن باید فعال شود). همچنین Rider و VSCode نیز از آن پشتیبانی می‌کنند. برای Rider باید در قسمت Tools/External symbols، گزینه‌های Use sources from symbol files when available و Allow downloading symbols from remote locations را فعال کنید.
همچنین برای فعالسازی آن در فایل csproj بسته‌ی نیوگت خود می‌توانید تنظیمات زیر را اضافه کنید:
<PropertyGroup>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
البته در اینجا پروایدر مخصوص GitHub را مشاهده می‌کنید (اگر مخزن کد بسته‌ی شما بر روی آن قرار دارد) و همچنین سایر پروایدرهای مخصوص سورس کنترل‌های دیگر مانند Azure DevOps/VSTS نیز برای آن تهیه شده‌اند.


روش فعالسازی Source Link در پروژه‌ی VSCode

اگر از VSCode استفاده می‌کنید، نیاز است تنظیمات زیر را به قسمت configurations فایل launch.json خود اضافه کنید تا قابلیت «Step Into" the source code"» بسته‌های نیوگتی که از SourceLink پشتیبانی می‌کنند، با فشردن دکمه‌ی F11 در حین دیباگ، فعال شود:
"justMyCode": false,
"symbolOptions": {
   "searchMicrosoftSymbolServer": true
},
"suppressJITOptimizations": true,
"env": {
   "COMPlus_ZapDisable": "1"
}
مطالب
آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT
برای مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» و پروژه‌ی آن، یک چنین رابط کاربری آزمایشی تهیه شده‌است:


اکنون در ادامه قصد داریم این موارد را تبدیل به چندین درخواست به هم مرتبط postman کرده و در نهایت آن‌ها را به صورت یک collection قابل آزمایش مجدد، ذخیره کنیم.

مرحله 1: خاموش کردن بررسی مجوز SSL برنامه

چون مجوز SSL برنامه‌های ASP.NET Core که در حالت local اجرا می‌شوند از نوع self-signed certificate است، توسط postman پردازش نخواهند شد. به همین جهت نیاز است به منوی File -> Settings آن مراجعه کرده و این بررسی را خاموش کنیم:



مرحله 2: ایجاد درخواست login و دریافت توکن‌ها


در اینجا این مراحل طی شده‌اند:
- ابتدا آدرس درخواست به https://localhost:5001/api/account/login تنظیم شده‌است.
- سپس نوع درخواست نیز به Post تغییر کرده‌است.
- چون اکنون نوع درخواست، Post است، می‌توان بدنه‌ی آن‌را مقدار دهی کرد و چون نوع آن JSON است، گزینه‌ی raw و سپس contentType صحیح انتخاب شده‌اند. در ادامه مقدار زیر تنظیم شده‌است:
{
    "username": "Vahid",
    "password": "12345"
}
این مقداری‌است که اکشن متد login می‌پذیرد. البته اگر برنامه را اجرا کنید، کلمه‌ی عبور پیش‌فرض آن 1234 است.
- پس از این تنظیمات اگر بر روی دکمه‌ی send کلیک کنیم، توکن‌های دریافتی را در قسمت response می‌توان مشاهده کرد.


مرحله‌ی 3: ذخیره سازی توکن‌های دریافتی در متغیرهای سراسری

برای دسترسی به منابع محافظت شده‌ی سمت سرور، نیاز است access_token را به همراه هر درخواست، به سمت سرور ارسال کرد. بنابراین نیاز است در همینجا این دو توکن را در متغیرهایی برای دسترسی بعدی، ذخیره کنیم:

var jsonData = pm.response.json();
pm.globals.set("access_token", jsonData.access_token);
pm.globals.set("refresh_token", jsonData.refresh_token);
محل تعریف این کدها نیز در قسمت Tests درخواست جاری است تا پس از پایان کار درخواست، اطلاعات آن استخراج شده و ذخیره شوند.


مرحله‌ی 3: ذخیره سازی مراحل انجام شده

برای این منظور، بر روی دکمه‌ی Save کنار Send کلیک کرده، نام Login را وارد و سپس یک Collection جدید را با نام دلخواه JWT Sample ایجاد می‌کنیم:


سپس این درخواست را در این مجموعه ذخیره خواهیم کرد.


مرحله‌ی 4: دسترسی به منابع محافظت شده‌ی سمت سرور

برای این منظور بر روی دکمه‌ی + موجود در کنار اولین برگه‌ای که مشغول به تکمیل آن هستیم، کلیک می‌کنیم تا یک برگه‌ی جدید ایجاد شود. سپس مشخصات آن را به صورت زیر تکمیل خواهیم کرد:


ابتدا آدرس درخواست از نوع Get را به https://localhost:5001/api/MyProtectedApi تنظیم خواهیم کرد.
سپس گزینه‌ی هدرهای این درخواست را انتخاب کرده و key/value جدیدی با مقادیر Authorization و Bearer {{access_token}} ایجاد می‌کنیم. در اینجا {{access_token}} همان متغیر سراسری است که پس از لاگین، تنظیم می‌شود. اکنون از این متغیر جهت تنظیم هدر Authorization استفاده کرده‌ایم.
در آخر اگر بر روی دکمه‌ی Send این درخواست کلیک کنیم، response فوق را می‌توان مشاهده کرد.

فراخوانی مسیر api/MyProtectedAdminApi نیز دقیقا به همین نحو است.

یک نکته: روش دومی نیز برای تنظیم هدر Authorization در postman وجود دارد. برای این منظور، گزینه‌ی Authorization یک درخواست را انتخاب کنید (تصویر زیر). سپس نوع آن‌را به Bearer token تغییر دهید و مقدار آن‌را به همان متغیر سراسری {{access_token}} تنظیم کنید. به این صورت هدر مخصوص JWT را به صورت خودکار ایجاد و ارسال می‌کند و در این حالت دیگر نیازی به تنظیم دستی برگه‌ی هدرها نیست.



مرحله‌ی 5: ارسال Refresh token و دریافت یک سری توکن جدید

این مرحله، نیاز به ارسال anti-forgery token را هم دارد و گرنه از طرف سرور و برنامه، برگشت خواهد خورد. اگر به کوکی‌های تنظیم شده‌ی توسط برگه‌ی لاگین دقت کنیم:


کوکی با نام XSRF-TOKEN نیز تنظیم شده‌است. بنابراین آن‌را توسط متد pm.cookies.get، در قسمت Tests برگه‌ی لاگین خوانده و در یک متغیر سراسری تنظیم می‌کنیم:
var jsonData = pm.response.json();
pm.globals.set("access_token", jsonData.access_token);
pm.globals.set("refresh_token", jsonData.refresh_token);
pm.globals.set('XSRF-TOKEN', pm.cookies.get('XSRF-TOKEN'));
سپس برگه‌ی جدید ایجاد درخواست refresh token به صورت زیر تنظیم می‌شود:
ابتدا در قسمت بدنه‌ی درخواست از نوع post به آدرس https://localhost:5001/api/account/RefreshToken، در قسمت raw آن، با انتخاب نوع json، این refresh token را که در قسمت لاگین خوانده و ذخیره کرده بودیم، به سمت سرور ارسال خواهیم کرد:


همچنین دو هدر زیر را نیز باید ارسال کنیم:


یکی هدر مخصوص Authorization است که در مورد آن بحث شد و دیگر هدر X-XSRF-TOKEN که در سمت سرور بجای anti-forgery token پردازش می‌شود. مقدار آن‌را نیز در برگه‌ی login با خواندن کوکی مرتبطی، پیشتر تنظیم کرده‌ایم که در اینجا از متغیر سراسری آن استفاده شده‌است.

اکنون اگر بر روی دکمه‌ی send این برگه کلیک کنید، دو توکن جدید را دریافت خواهیم کرد:


که نیاز است مجددا در برگه‌ی Tests آن، متغیرهای سراسری پیشین را بازنویسی کنند. به همین جهت دقیقا همان 4 سطری که اکنون در برگه‌ی Tests درخواست لاگین وجود دارند، باید در اینجا نیز تکرار شوند تا عملیات refresh token واقعا به تمام برگه‌های موجود، اعمال شود.


مرحله‌ی آخر: پیاده سازی logout

در اینجا نیاز است refreshToken را به صورت یک کوئری استرینگ به سمت سرور ارسال کرد که به کمک متغیر {{refresh_token}}، در برگه‌ی params تعریف می‌شود:


همچنین هدر Authorization را نیز باید درج کرد:


پس از آن اگر درخواست را رسال کنیم، یک true را باید بتوان در response مشاهده کرد:



ذخیره سازی مجموعه‌ی جاری به صورت یک فایل JSON

برای گرفتن خروجی از این مجموعه و به اشتراک گذاشتن آن‌، اگر اشاره‌گر ماوس را بر روی نام یک مجموعه حرکت دهیم، یک دکمه‌ی جدید با برچسب ... ظاهر می‌شود. با کلیک بر روی آن، یکی از گزینه‌های آن، export نام دارد که جزئیات تمام درخواست‌های یک مجموعه را به صورت یک فایل JSON ذخیره می‌کند. برای نمونه فایل JSON خروجی این قسمت را می‌توانید از اینجا دریافت کنید: JWT-Sample.postman_collection.json
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت هفتم - سفارشی سازی ظاهر مستندات API
امکان کنترل کامل و سفارشی سازی ظاهر نهایی Swagger-UI وجود دارد که جزئیات آن‌را در این قسمت بررسی خواهیم کرد.


بهبود ظاهر کامنت‌ها با بکارگیری Markdown

Markdown زبان سبکی است برای تعیین شیوه‌نامه‌ی نمایش متون ساده. اگر پیشتر با سیستم ارسال نظرات Github و یا Stackoverflow کار کرده باشید، قطعا با آن آشنایی دارید. توضیحات کامل و جزئیات آن‌را می‌توانید در آدرس markdownguide.org مطالعه کنید. خوشبختانه امکان استفاده‌ی از Markdown در OpenAPI spec نیز وجود دارد که سبب بهبود ظاهر مستندات نهایی حاصل از آن خواهد شد.
در قسمت سوم، سعی کردیم مثالی را توسط remarks، به قسمت Patch اضافه کنیم که ظاهر آن، آنچنان مطلوب به نظر نمی‌رسد و بهتر است آن‌را به صورت یک قطعه کد نمایش داد:


برای بهبود این ظاهر می‌توان از Markdown استفاده کرد. بنابراین ابتدا تمام backslash‌های اضافه شده را که جهت نمایش خطوط جدید اضافه شده بودند، حذف می‌کنیم. در Markdown خطوط جدید با درج حداقل 2 فاصله (space) و یک سطر جدید مشخص می‌شوند. همچنین استفاده‌ی از ** سبب ضخیم شدن نمایش عبارت‌ها می‌شود. برای اینکه قطعه کد نوشته شده را در Markdown به صورت کدی با پس زمینه‌ای مشخص نمایش دهیم، پیش از شروع هر سطر آن نیاز است یک tab و یا 4 فاصله (space) درج شوند:
/// <remarks>
/// Sample request (this request updates the author's **first name**)
///
///     PATCH /authors/id
///     [
///         {
///           "op": "replace",
///           "path": "/firstname",
///           "value": "new first name"
///           }
///     ]
/// </remarks>
پس از این تغییرات خواهیم داشت:

که نسبت به حالت قبلی بسیار بهتر به نظر می‌رسد.
در نگارش فعلی، استفاده‌ی از Markdown برای توضیحات remarks، پارامترها و response codes پشتیبانی می‌شود؛ اما نه برای قسمت summary که برای نگارش بعدی درنظر گرفته شده‌است. همچنین از قابلیت‌های پیشترفته‌ی Markdown هم استفاده نکنید (در کل نیاز به مقداری سعی و خطا دارد تا مشخص شود چه قابلیت‌هایی را پشتیبانی می‌کند).


سفارشی سازی مقدماتی UI به کمک تنظیمات API آن

جائیکه تنظیمات میان‌افزار Swashbuckle.AspNetCore در کلاس Starup تعریف می‌شوند، می‌توان تغییراتی را نیز در UI آن اعمال کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
    // ...
            app.UseSwaggerUI(setupAction =>
            {
                setupAction.SwaggerEndpoint(
                    url: "/swagger/LibraryOpenAPISpecification/swagger.json",
                    name: "Library API");
                setupAction.RoutePrefix = "";

                setupAction.DefaultModelExpandDepth(2);
                setupAction.DefaultModelRendering(Swashbuckle.AspNetCore.SwaggerUI.ModelRendering.Model);
                setupAction.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
                setupAction.EnableDeepLinking();
                setupAction.DisplayOperationId();
            });
    // ...
        }
    }
}
با این خروجی که به علت تنظیم DocExpansion آن به None، اینبار لیست قابلیت‌ها را به صورت باز شده نمایش نمی‌دهد:


همچنین چون DefaultModelRendering به Model تنظیم شده‌است، اینبار بجای مثال، مشخصات مدل را به صورت پیش‌فرض نمایش می‌دهد:


کار DisplayOperationId نمایش Id هر Operation است؛ مانند get_api_authors. در اینجا Operation همان مداخل API ما هستند و به عنوان هر قسمت، Tag گفته می‌شود؛ مانند Authors و یا Books:


با فعالسازی EnableDeepLinking، آدرس‌هایی مانند tagName/# و یا tagName/OperationId/# قابلیت مرور و بازشدن خودکار را پیدا می‌کنند (tagName همان نام کنترلر است و OperationId همان اکشن متدی که عمومی شده‌است). برای مثال اگر آدرس https://localhost:5001/index.html#/Books/get_api_authors__authorId__books را در یک برگه‌ی جدید مرورگر باز کنیم، بلافاصله گروه Books، باز شده و سپس به اکشن متد یا مدخلی که Id آن ذکر شده‌است، هدایت می‌شویم:



اعمال تغییرات پیشرفته در UI

Swagger-UI در اصل از یک سری فایل html، css، js و فونت تشکیل شده‌است که آن‌ها را می‌توانید در آدرس src/Swashbuckle.AspNetCore.SwaggerUI مشاهده کنید. برای مثال فایل index.html آن‌را در اینجا می‌توانید مشاهده کنید. اصل آن در div ای با id مساوی swagger-ui رخ می‌دهد و در این قسمت است که رابط کاربری آن به صورت پویا تولید شده و رندر خواهد شد. بررسی فایل‌های js و css آن در این مخزن کد مشکل است؛ چون نگارش فشرده شده‌ی آن هستند. به همین جهت می‌توان به مخزن کد اصلی Swagger-UI که نگارش جایگذاری شده‌ی آن (embedded) توسط Swashbuckle.AspNetCore ارائه می‌شود، رجوع کرد. برای مثال در پوشه‌ی src/styles آن، اصل فایل‌های css آن برای سفارشی سازی وجود دارند.

پس از اعمال تغییرات خود، می‌توانید css و یا js سفارشی خود را به نحو زیر به تنظیمات app.UseSwaggerUI سیستم معرفی کنید:
setupAction.InjectStylesheet("/Assets/custom-ui.css");
setupAction.InjectJavaScript("/Assets/custom-js.js");
باید دقت داشت که این فایل‌ها باید در پوشه‌ی wwwroot قرار گرفته و قابل دسترسی باشند.
برای اعمال تغییرات اساسی و تزریق صفحه‌ی index.html جدیدی، می‌توان به صورت زیر عمل کرد:
setupAction.IndexStream = () => 
    GetType().Assembly.GetManifestResourceStream(
  "OpenAPISwaggerDoc.Web.EmbeddedAssets.index.html");
نکته‌ی مهم: اینبار این فایل باید به صورت embedded ارائه شود. به همین جهت در مثال فوق، عبارت OpenAPISwaggerDoc.Web به فضای نام اصلی اسمبلی جاری اشاره می‌کند. سپس EmbeddedAssets، نام پوشه‌ای است که فایل index.html سفارشی سازی شده، در آن قرار خواهد گرفت. اکنون برای اینکه این فایل را به صورت EmbeddedResource معرفی کنیم، نیاز است فایل csproj را به نحو زیر ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <None Remove="EmbeddedAssets\index.html" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="EmbeddedAssets\index.html" />
  </ItemGroup>
</Project>

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-07.zip
مطالب
کار با Razor در ASP.NET Core 2.0
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 می‌باشد.


ایجاد یک پروژه با استفاده Razor

در ادامه با هم یک مثال را با استفاده از Razor ایجاد می‌کنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد می‌کنیم.

مراحل:

1- ابتدا در کلاس startup قابلیت MVC را فعال می‌کنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
 services.AddMvc();
و بعد کد زیر را که مربوط به اجرای پروژه‌ی hello Word است ، از متد Configure حذف می‌کنیم:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
در نهایت محتویات  فایل StartUp به صورت زیر می‌باشد:

namespace Razor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}


ایجاد یک Model
 یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام داده‌ایم. درخواست‌هایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال می‌کند. حالا ما یک پوشه جدید را به نام Controllers ایجاد می‌کنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}
در این کلاس یک Action Method را به نام index ایجاد می‌کنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آن‌را به View ارسال می‌کنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان می‌باشد.

 
ایجاد View
 برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد می‌کنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View می‌باشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
 
@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

تا اینجا ما یک پروژه ساده را ایجاد نموده‌ایم که قابلیت استفاده‌ی از Razor را هم دارد. در ادامه نحوه‌ی استفاده از امکانات Razor شرح داده میشوند.


استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد می‌کند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده می‌کنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده می‌کنیم. به کد زیر دقت کنید:

@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل می‌باشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو می‌باشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.

نتیجه خروجی بالا مانند زیر می‌باشد:

 



معرفی View Imports

زمانیکه بخواهیم به یک کلاس در View دسترسی داشته باشیم، باید فضای نام آن کلاس را مانند کد زیر در بالای View اضافه کنیم. حالا اگر بخواهیم به چند کلاس دسترسی داشته باشیم، باید این کار را به ازای هر کلاس در هر View انجام دهیم که سبب ایجاد کدهای اضافی در View‌ها میشود. برای بهبود این وضعیت می‌توانید یک کلاس View Import را در پوشه‌ی Views ایجاد کنید و تمام فضاهای نام را در آن قرار دهید. با اینکار تمام فضاهای نامی که در این کلاس View Import قرار گرفته‌اند، در تمام Viewهای موجود در پوشه Views قابل دسترسی خواهند بود.

در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب می‌کنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن  Razor View Imports است) انتخاب می‌کنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.


نکته: استاندارد نام گذاری این View این می‌باشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
 
در کلاس تعریف شده با استفاده از عبارت using@ فضای نام‌های خود را قرار میدهیم؛ مانند زیر:
 @using Razor.Models
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
@model Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>


Layout ها

یکی دیگر از عبارت‌های مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{
    Layout = null;
}
شما می‌توانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار داده‌ایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمی‌کند.

از Layout برای طراحی الگوی Viewها استفاده می‌کنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آن‌ها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد می‌کنیم و از آن در View ها استفاده می‌کنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
 
Layout
طرحبندی  Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشه‌ی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب می‌کنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
 


هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند: 
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
طرحبندی‌ها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان می‌شود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شده‌است.
ViewBag ویژگی مفیدی است که اجازه می‌دهد تا مقادیر و داده‌ها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال می‌کنیم، این مورد چگونه کار می‌کند.

عناصر HTML در یک Layout به هر View که از آن استفاده می‌کند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه می‌شود؛ مانند کدهای زیر. من برخی از نشانه گذاری‌های ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>
در اینجا یک عنصر عنوان و همچنین بعضی از CSS‌ها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کرده‌ام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت می‌آید و چه چیزی از View.
 

اعمال Layout

برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آن‌را مقدار دهی و سپس Htmlهای اضافی موجود در آن‌را مانند المنت‌های head و Body حذف کنید؛ همانند کدهای زیر:
@model Product
@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product";
}
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو می‌کند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی می‌کنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه ساده‌ای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه می‌دهد و داده‌ها را به کاربر منتقل می‌کند. هنگامیکه MVC فایل Index.cshtmal را پردازش می‌کند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه می‌شود؛ مانند عکس زیر:
 


 
View Start

بعضی موارد هنوز در برنامه وجود دارند که می‌توان کنترل بیشتری بر روی آن‌ها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده می‌کنند، پیدا کنید و نام Layout استفاده شده در آن‌ها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start می‌توانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آن‌را در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا می‌کنیم، MVC به دنبال فایل View Start می‌گردد و اگر اطلاعاتی داشته باشد، آن را اجرا می‌کند و الویت این فایل از تمام فایل‌های دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.

برای ایجاد یک فایل شروع مشاهده، روی پوشه‌ی Views کلیک راست کرده و گزینه add->New Items را انتخاب می‌کنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب می‌کنیم؛ مانند تصویر زیر:


ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینه‌ی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر می‌باشد:
@{
    Layout = "_Layout";
}
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر: 
@{
    Layout = "_BasicLayout";
}
از آنجا که فایل View Start دارای مقداری برای Layout می‌باشد، می‌توانیم عبارت‌های مربوطه را در Index.cshtml‌ها حذف کنیم:
@model Product
@{
    ViewBag.Title = "Product";
}
در اینجا لازم نیست مشخص کنیم که من می‌خواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده می‌کند. البته باید دقت داشت که مقادیر تعریف شده‌ی در فایل View اولویت دارند و باعث میشوند با معادل‌های فایل View Start جایگزین شوند.

شما همچنین می‌توانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمت‌های مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیک‌ترین فایل View start، پردازش می‌شود. به این معنا که شما می‌توانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.

نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمی‌خواهید از آن استفاده کنید، بنابراین مقدار Layout آن‌را صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض می‌کند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین می‌کند.
 

استفاده از عبارت‌های شرطی در Razor
 
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آن‌ها را پشتیبانی می‌کند و نحوه استفاده‌ی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقش‌هایی که توسط View و Action متدها انجام می‌شود، جدایی روشنی وجود دارد. در اینجا قوانین ساده‌ای وجود دارند که در جدول زیر مشخص شده‌اند:

کامپوننت 
انجام میشود 
انجام نمیشود 
  Action Method    یک شیء ViewModel را به View ارسال می‌کند.
  یک فرمت داده را به View ارسال می‌کند.
  View    از شیء ViewModel برای ارائه محتوا به کاربر استفاده می‌کند.
  هر جنبه‌ای از شیء View Model مشخصات را تغییر می‌دهد.
 
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمت‌های مختلف برنامه را دارید. همانطور که می‌بینید، می‌توانید کاملا با Razor کار کنید و این نوع فایل‌ها شامل دستورالعمل‌های سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@
@model Product
@{

    ViewBag.Title = "Product";
}
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
می‌توان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار می‌کند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف می‌کند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش می‌دهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام می‌دهید مطلع باشید.
 

پردازش داده‌ها در مقابل فرمت

تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت داده‌ها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کرده‌ام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء داده‌‌ای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی می‌کند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.


اضافه نمودن مقدار داده ای

ساده‌ترین کاری را که می‌توانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایج‌ترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
 <p>Product Name: @Model.Name</p>
شما همچنین می‌توانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کرده‌ایم:
using Microsoft.AspNetCore.Mvc;
using Razor.Models;


namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

خصوصیت ViewBag یک شیء پویا را باز می‌گرداند که می‌تواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شده‌است: 
 <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
نتیجه آن‌را در زیر می‌توانید مشاهده کنید:



تنظیم مقادیر مشخص

شما همچنین می‌توانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product
@{

    ViewBag.Title = "Product";
}
p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> 
<p>Stock Level: @ViewBag.StockLevel</p>
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">    
<p>Product Name: @Model.Name</p>    
<p>Product Price: @($"{Model.Price:C2}")</p>   
 <p>Stock Level: @ViewBag.StockLevel</p> 
</div>
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگی‌های داده در عنصر div استفاده کرده‌ام.

نکته: ویژگی‌های داده‌ها که نام آنها *-data است، روشی برای ایجاد ویژگی‌های سفارشی برای سال‌ها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آن‌ها برای یافتن اطلاعات استفاده می‌کنند.

اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2">    <p>Product Name: Kayak</p>    <p>Product Price: £275.00</p>    <p>Stock Level: 2</p> </div>


استفاده از عبارت‌های شرطی

Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شده‌اند می‌بینید:

@model Product
@{ ViewBag.Title = "Product Name"; }
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">  
  <p>Product Name: @Model.Name</p>   
 <p>Product Price: @($"{Model.Price:C2}")</p> 
   <p>Stock Level:       
 @switch (ViewBag.StockLevel)
{
    case 0:@:Out of Stock                break;           
    case 1:          
    case 2:        
    case 3:            
    <b>Low Stock (@ViewBag.StockLevel)</b>         
       break;      
    default:            
    @: @ViewBag.StockLevel in Stock          
      break;      
  }    
</p>
</div>


برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار می‌دهیم. درون قطعه کد Razor، می‌توانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
 <b>Low Stock (@ViewBag.StockLevel)</b>
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر می‌خواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،‌می‌توانید از :@ استفاده کنید تا عین آن عبارت درج شود.
مطالب
آشنایی با CLR: قسمت بیست و یکم

آغاز فصل سوم:

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

مشکلی که در توزیع اسمبلی‌های عمومی وجود دارد این است که شما باید این اطمینان را کسب کنید که اسمبلی شما، همیشه همان اسمبلی خواهد بود و تغییری در آن رخ نخواهد داد. فرض کنید که شما از یک اسمبلی که توسط شرکت تلریک تهیه شده است استفاده کرده‌اید و برنامه‌ی شما به خوبی با آن کار می‌کند. ولی چه اتفاقی می‌افتد که اگر برنامه‌ی دیگری بعد از شما نصب شود و از همان اسمبلی، منتها از نسخه‌ی دیگر آن استفاده می‌کند؟ بله برنامه‌ی شما احتمال زیادی دارد که در این حالت به مشکل بر بخورد یا اینکه شخص دیگری یک اسمبلی دیگری همنام اسمبلی و هم نسخه‌ی اسمبلی شما تولید می‌کند. برای رفع این مشکلات مایکروسافت تمهیداتی را اندیشیده است که ما به آن می‌گوییم «اسمبلی با نام قوی Strong Name Assembly».

اسمبلی‌ها به دو دسته تقسیم می‌شوند: اسمبلی‌های با نام قوی و اسمبلی هایی با نام ضعیف ( این مورد در مستندات مایکروسافت نیست و توسط نویسنده‌ی کتاب، این اصطلاح ایجاد شده است).

در قسمت دوم گفتیم که اسمبلی‌ها از قسمت‌هایی چون جداول مانیفست، هدرها، متادیتاها و ... تشکیل می‌شوند. اسمبلی‌های نام قوی هم به همین شکل هستند. فقط توسط جفت کلید عمومی و خصوصی محافظت و امضا می‌شوند که برای ناشر، یک کلید منحصر به فرد را ایجاد می‌کنند و به ناشر این اطمینان را می‌دهند که اگر جفت کلیدی را که در دست شما است، به کسی ندهید، هیچ کس دیگری نمی‌تواند اسمبلی را با مشخصات اسمبلی شما امضاء کند.

حال یک اسمبلی نام قوی، دارای چهار خصوصیت است: نام اسمبلی بدون پسوند، نگارش، فرهنگ (Culture) و کلید عمومی.

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

"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture="en­US", PublicKeyToken=b77a5c561934e089" 
"MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

استفاده از فضای نام

System.Reflection.AssemblyName

به شما اجازه‌ی ساخت و دریافت اطلاعاتی را از اسمبلی‌ها می‌دهد؛ هر نوع اطلاعاتی را که شامل 4 خصوصیت بالا می‌شود، به شما می‌دهد.

از آنجا که مطالب مربوطه به  امضاء کردن اسمبلی، در سایت جاری موجود می‌باشند، این مباحث را می‌توانید از طریق این مقاله "نام قوی" دنبال کنید تا در این باره گزافه گویی نکرده باشیم .

مکانیزم اعمال این امضاء بدین شکل است که بعد از ساخت فایل PE که شامل مانیفست است، کل محتویات آن ( به جز امضاهای شناسایی و هدر (Checksum) و اطلاعات مربوط به نام قوی) هش می‌شوند و سپس این مقدا هش شده توسط کلید خصوصی امضاء می‌شود و در همان PE، در بخش هش نشده‌ای به نام Reserved Section ذخیره می‌گردند و در نهایت CLR Header برای ارتباط با این بخش آپدیت می‌شود. 

تصویر زیر توضیح بند بالا را نشان می‌دهد:

 

مختصری در مورد GAC

اگر مقاله‌ی بالا را خوانده باشید، الان باید بدانید که GAC، وظیفه‌ی رجیستر و توزیع اسمبلی نام قوی را بر عهده دارد؛ ولی چرا باید اینکار را به GAC، واگذار کنیم؟ فرض کنید دو اسمبلی با نام‌های dotnettips.dll وجود دارند. اگر قرار باشد هر دو داخل یک دایرکتوری قرار بگیرند، مسلما اسمبلی دوم بر روی اسمبلی اولی overwrite خواهد شد. درنتیجه کاری که GAC انجام می‌دهد، جلوگیری از این اتفاق است و GAC مدیریت می‌کند که در داخل مسیر
%SystemRoot%\Microsoft.NET\Assembly
زیرشاخه هایی ایجاد شوند و اسمبلی‌ها در آن‌ها قرار بگیرند.

هر اسمبلی را که توزیع می‌کنید، حتما حداقل به یک اسمبلی نام قوی ارجاع خواهد داشت؛ دلیل این گفته هم وجود فضای نام system.object در اسمبلی mscorlib است. برای همین، موقعیکه شما با csc، کامپایل می‌کنید، از سوئیچ reference استفاده می‌شود تا ارجاعی را به نام اسمبلی مورد نظر داشته باشد. اگر نام اسمبلی به طور کامل به همراه مسیر ذکر شود که مستقیما از همانجا فراخوانی می‌شود؛ در غیر این صورت مسیرهای زیر مورد بررسی قرار می‌گیرند:
  1. مسیر پوشه‌ی کاری برنامه
  2. مسیر کامپایلر CSC
  3. هر مسیری که با سوئیچ lib به کامپایلر معرفی کرده باشید.
  4. هر مسیری که با متغیرهای Lib Environment مشخص شده باشند.
بنابراین اگر شما مثلا از یک اسمبلی مانند system.drawing استفاده کرده باشید، کمپایلر به طور خودکار دستور زیر را صادر می‌کند:
/reference:System.Drawing.dll
و همانطور که گفته شد، کامپایلر شروع به اسکن دایرکتوری‌ها می‌کند و در نهایت آن را در مسیر خود کامپایلر، یعنی مورد 2 پیدا می‌کند. برنامه کمپایل می‌شود، ولی این اسمبلی همان اسمبلی نیست که برنامه در زمان اجرا، مورد استفاده قرار می‌دهد؛ چون این اسمبلی یک اسمبلی نام قوی است و انتظار می‌رود تا در مسیر مورد تایید GAC یافت شود. به همین دلیل است که موقع نصب دات نت فریم ورک، از هر اسمبلی، دو نسخه بر روی سیستم کپی می‌شود. یکی در مسیر کامپایل و دیگری برای GAC.
در ضمن اسمبلی‌های موجود در مسیر کامپایلر به هیچ عنوان اهمیتی به نوع ماشین نمی‌دهند؛ چون متادیتاهای اسمبلی آن‌ها اهمیت دارند نه کد IL آن‌ها. کد IL فقط در زمان اجرا، برای ما اهمیت دارد و در زمان کامپایل، همان متادیتا کفایت می‌کند و در نهایت برنامه موقع اجرا، با توجه به نوع ماشین x86,x36,ARM، اسمبلی مورد نیاز خود را از طریق GAC فراهم می‌کند که هر یک از اسمبلی‌های مخصوص هر ماشین، توسط GAC، در داخل زیر شاخه‌های مخصوص خود قرار گرفته‌اند.
مطالب
طراحی جدول فایل‌های پیوستی پایگاه داده
سناریو‌ی زیر را در نظر بگیرید:
می‌خواهید پروژه‌ای را انجام دهید که شامل جداول زیر است:
مقالات، اخبار، گالری تصاویر، گالری ویدیو، اسلایدشو، تبلیغات و ... و تمامی این جداول حداقل شامل یک فایل پیوست (عکس، فیلم، ...) می‌باشند. به طور مثال جدول مقالات دارای یک عکس نیز می‌باشد. قصد داریم تمام فایل‌ها را بر روی هاست ذخیره کرده و فقط آدرس و نام فایل را در دیتابیس ذخیره نمایم.

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

مثال:
    public class Article
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string RegisterDate { get; set; }
        public string FileName { get; set; }
    }
معایب:
این روش فقط در صورتی پاسخگو می‌باشد که هر رکورد فقط شامل یک فایل باشد. به طور مثال ممکن است برای یک مقاله، چندین عکس و فایل را ضمیمه‌ی آن کنیم. در این حالت این روش پاسخ گو نمی‌باشد؛ ولی می‌توانیم به صورت زیر نیز عمل کنیم:
ایجاد جدولی برای نگهداری فایل‌های هر رکورد از مقاله :


public class ArticleFiles
{
        public int Id { get; set; }
        public string FielName { get; set; }
        public string FileExtension { get; set; } 
        public Article Article { get; set; }
        public int FileSize { get; set; }
}
 
روش دوم : ایجاد جدولی پایه برای نگهدارای تمام فایل‌های آپلود شده

می‌توانیم جدولی را به نام Attachment ایجاد کرده و هر فایلی را که آپلود می‌کنیم، مشخصات آن را در این جدول ذخیره کنیم و هر جدول هم که نیازی به فایل داشت، رابطه‌ای با این جدول برقرار کند. در این حالت خواهیم داشت:


public class Attachment
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string FileName { get; set; }
        public string Extension { get; set; }
        public DateTime RegisterDate { get; set; }
        public int Size { get; set; }
        public ICollection<Article> ArticleFiles { get; set; }
        public ICollection<News> NewsFiles { get; set; }
        public int Viewed { get; set; }
    }
در این حالت باید بین تمام جداولی که نیاز به فایل دارند، رابطه ای با جدول Attachment داشته باشد. به طور مثال بین جدول مقالات و جدول Attachment یک رابطه‌ی یک به چند برای لیست فایل‌ها وجود خواهد داشت.


روش سوم : جدولی برای نگه داری اسم فایل‌ها، بدون رابطه

جدول Attachment در این روش، همانند روش دوم می‌باشد؛ با دو تفاوت:
1- با هیچ جدولی رابطه‌ای ندارد.
2- دو فیلد به عنوان نام جدول و Id رکورد به آن اضافه شده است.
تفاوت نسبت به روش دوم:
در روش دوم، ثبت یک رکورد، وابسته‌ی به ثبت رکورد در جدول Attachment بود و ابتدا می‌بایستی فایل در Attachment ذخیره می‌شد و بعد از بدست آوردن Id آن، رکورد مورد نظر (مقاله) را درج می‌کردیم. ولی در این روش ابتدا مقاله درج شده و بعد از آن فایل را با اسم جدول و ID رکورد مورد نظر ذخیره می‌کنیم:
public class Attachment
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string FileName { get; set; }
        public string Extension { get; set; }
        public DateTime RegisterDate { get; set; }
        public int Size { get; set; }
        public string TableName { get; set; }
        public int RowId { get; set; }
        public int Viewed { get; set; }        
    }

حالت پنجم :

ایجاد یک کلاس پایه و ارث بری سایر کلاس‌ها از کلاس پایه و ایجاد رابطه‌ای بین کلاس پایه و کلاس‌های مشتق شده.

نظراتی پیرامون حالت‌های مختلف:

1- داشتن یک جدول الحاقات برای هر جدول
  • اضافه کردن یک فیلد: بعضی‌ها این روش را ترجیح می‌دهند. به این دلیل که هر جدول، یک جدول attachment مختص به خود دارد؛ با توجه به فیلدهایی که لازم است. به طور مثال ممکن است بعد از گذشت مدتی، نیاز باشد تا دو فیلد برای فایل‌های هر مقاله اضافه شوند که در این حالت فقط به جدول attachment مقاله اضافه خواهند شد.

2- داشتن یک جدول پایه که کل فایل‌ها در آن ذخیره شوند (روش‌های دوم و سوم)

  • متمرکز شدن کل فایل‌ها در یک جدول: بیشتر پروژه‌ها و یا برنامه نویسان (طبق تجربه‌ی بنده) یک جدول پایه را برای این منظور دوست دارند. به دلیل اینکه تمام اطلاعات یکجا باشد.
  • عدم آپلود چندین باره‌ی یک فایل: در این حالت می‌توان از یک فایل چندین بار در چند جای مختلف استفاده نمود و در فضای هاست صرفه جویی می‌شود. این روش مدیریت سختی دارد و نیازمند کوئری‌های بیشتری می‌باشد.
  • وجود فیلد‌های زیاد null در جدول: در این حالت ممکن است ردیف‌هایی با ستون‌های مقدار null در جدول زیاد شوند. فرض کنید دو فیلد در جدول attachment وجود دارند که فقط توسط جدول مقالات مورد استفاده قرار می‌گیرند و در بقیه‌ی جداول بدون استفاده می‌باشند.


از کدام روش استفاده کنیم؟

نمی توان پیشنهاد کرد که الزاما از کدامیک از روش‌های بالا باید استفاده کنیم؛ چون نیازمندهای‌های هر پروژه با هم متفات است و نمی‌توان نسخه‌ای خاص را برای همه تجویز کرد.

مطالب
سفارشی کردن Controller factory توکار و فرصتی برای تزریق وابستگی‌ها به کنترلر
در مقاله‌ی قبلی ( + ) به این لحاظ که بهترین راه نشان دادن نحوه‌ی کارکرد Controller Factory ایجاد یک نمونه‌ی سفارشی بود، آن رابررسی کردیم و برای اکثریت برنامه‌ها و سناریوها، کلاس توکار Controller Factory به نام DefaultControllerFactory کفایت می‌کند.
پس از وصول یک درخواست از طریق سیستم مسیریابی، factory پیش فرض (DefaultControllerFactory) به بررسی rout data پرداخته تا خاصیت Controller آن را بیابد و سعی در پیدا کردن کلاسی در برنامه خواهد داشت که مشخصات ذیل را دارا باشد:
  1. دارای سطح دسترسی public باشد.
  2. Abstract نباشد.
  3. حاوی پارامتر generic نباشد.
  4. نام کلاس دارای پسوند Controller باشد.
  5. پیاده سازی کننده اینترفیس IContoller باشد.
کلاس DefaultControllerFactory در صورت یافتن کلاسی مطابق قواعد فوق و مناسب درخواست رسیده، وهله‌ای از آن را به کمک Controller Activator ایجاد می‌کند. می‌بینید که با برپایی چند قاعده‌ی ساده،  factory پیش فرض، نیاز به ثبت کنترلرها را به منظور معرفی و داشتن لیستی برای بررسی از طرف برنامه نویس (مثلا درج نام کلاس‌های کنترلر در یک فایل پیکربندی)، مرتفع ساخته است.
اگر بخواهید به فضاهای نام خاصی برای یافتن آنها توسط factory پیش فرض، برتری قائل شوید، باید در متد Application_Start فایل global.asax.cs مانند ذیل عمل نمایید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using ControllerExtensibility.Infrastructure;
namespace ControllerExtensibility
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace");
            ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*");
        }
    }
}

سفارشی کردن وهله سازی کنترلرها توسط DefaultControllerFactory

مهمترین دلیلی که نیاز داریم factory پیش فرض را سفارشی کنیم، استفاده از تزریق وابستگی‌ها (DI) به کنترلرهاست. راه‌های متعددی برای این کار وجود دارند که انتخاب بهترین روش بسته به چگونگی بکارگیری DI در برنامه شماست:
الف) تزریق وابستگی به کنترلر با ایجاد یک controller activator سفارشی

کدهای اینترفیس  IControllerActivator  مطابق ذیل است:
namespace System.Web.Mvc
{
    using System.Web.Routing;
    public interface IControllerActivator
    {
        IController Create(RequestContext requestContext, Type controllerType);
    }
}
این اینترفیس حاوی متدی به نام Create است که شیء RequestContext به آن پاس داده می‌شود و یک Type که مشخص می‌کند کدام کنترلر باید وهله سازی شود. در کدهای ذیل در قسمت (return (IController)ObjectFactory.GetInstance(controllerType  فرض بر این است که در پروژه برای تزریق وابستگی، StructureMapFactory  را به کار گرفته‌ایم و سیم کشی‌های لازم قبلا صورت گرفته است. چنانچه با StructureMap  آشنایی ندارید به این مقاله سایت (استفاده از StructureMap به عنوان یک IoC Container) مراجعه نمایید.
using ControllerExtensibility.Controllers;
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace ControllerExtensibility.Infrastructure
{
    public class StructureMapControllerActivator : IControllerActivator
    {
        public IController Create(RequestContext requestContext,
        Type controllerType)
        {
            return (IController)ObjectFactory.GetInstance(controllerType);
        }
    }
}

در شکل فوق منظور از CustomControllerActivator یک پیاده سازی از اینترفیس IControllerActivator مانند کلاس StructureMapControllerActivator است.
برای استفاده از این activator سفارشی نیاز داریم وهله‌ای از آن را به عنوان پارامتر به سازنده‌ی کلاس DefaultControllerFactory ارسال کنیم و نتیجه را در متد Application_Start فایل global.asax.cs ثبت کنیم.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using ControllerExtensibility.Infrastructure;
namespace ControllerExtensibility
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ControllerBuilder.Current.SetControllerFactory(new
            DefaultControllerFactory(new StructureMapControllerActivator()));
        }
    }
}

ب) تحریف و بازنویسی متدهای کلاس DefaultControllerFactory
 می‌توان متدهای کلاس مشتق شده‌ی از DefaultControllerFactory را override کرد و برای اهدافی نظیر DI از آن بهره جست. جدول ذیل سه متدی که می‌توان با تحریف آنها به مقصود رسید، توصیف شده‌اند:

 متد  نوع بازگشتی توضیحات
  CreateController   IController  پیاده سازی کننده‌ی متد Createontroller از اینترفیس IControllerFactory است و به صورت پیش فرض متد GetControllerType را جهت تعیین نوعی که باید وهله سازی شود، صدا می‌زند و سپس کنترلر وهله سازی شده را به متد GetControllerInstance ارسال می‌کند.
  GetControllerType   Type  وظیفه‌ی نگاشت درخواست رسیده را به Controller type عهده دار است.
GetControllerInstance IController وظیفه ایجاد وهله‌ای از نوع مشخص شده را عهده دار است.

شیوه‌ی تحریف متد GetControllerInstance 
public class StructureMapControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return ObjectFactory.GetInstance(controllerType) as Controller;
        }
    }
شیوه‌ی ثبت در فایل global.asax.cs و در متد Application_start :
 ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());

نمونه‌ای عملی آن‌را در مقاله‌ی (EF Code First #12) و یا دوره‌ی «بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن» می‌توانید بررسی کنید.
مطالب
Gulp #2
در قسمت قبلی بحث کردیم که گالپ چیست و چه کاربردی دارد و در نهایت آن را بر روی سیستم خود نصب کردیم. در این مقاله و مقالات بعد می‌خواهیم کار خود را با راه اندازی یک workflow برای بوت استرپ، روند شخصی سازی آن را بسیار آسان و لذت بخش‌تر کنیم. امیدوارم که برای ادامه‌ی این بحث هیجان انگیز آماده باشید!

ساخت پروژه گالپ

ابتدا یک پوشه‌ی دلخواه به نام project را درست کنید.سپس خط فرمان خود را به این مسیر تغییر دهید و در نهایت دستور زیر را وارد کنید:
 npm init
این دستور برایمان یک فایل package.json می‌سازد تا هم مشخصات پروژه را مثل نام، ورژن، نام توسعه دهنده، مخزن و ... مشخص کنیم و هم وابستگی‌های آن را، تا توسعه دهندگان دیگر، هنگام استفاده از پروژه، با مشکل مواجه نشوند و فقط با اجرای دستور npm install تمام وابستگی‌های پروژه را نصب کنند.
حتما مشاهده کرده‌اید که این دستور چند سوالی را از شما می‌پرسد. برای نمونه من آنها به این صورت پاسخ می‌دهم و در نهایت از من یک تایید می‌گیرد که yes را می‌زنم.
name: (Gulp-RTLbootstrap-fontawesome) 
version: (1.0.0) 
description: An Awesome workflow
entry point: (index.js) index.html
test command: test
git repository: https://github.com/mmdsharifi/gulp-rtlBootstrap-fontawesome.git
keywords: gulp,rtlbootstrap,persian bootstrap
author: Mohammad Sharifi
license: (ISC) MIT
الان فایل package.json درست شده و چون ما در این پروژه می‌خواهیم از گالپ استفاده کنیم، یکی از وابستگی‌های پروژه‌ی ما گالپ خواهد بود. بدین معنی که اگر بخواهیم پروژه را توسعه دهیم و گالپ نصب نشده باشد، با مشکل مواجه می‌شویم.

نصب گالپ

در خط فرمان دستور زیر را وارد کنید تا گالپ در این پروژه نصب شود.
npm install gulp --save-dev
تفاوتی آن با دستوری که در مقاله‌ی قبلی اجرا کردیم، این است که این دستور فقط گالپ را در مسیر جاری در فولدر node_modules نصب می‌کند و save-dev --      آن را به وابستگی‌های توسعه‌ی پروژه در فایل package.json اضافه می‌کند.
گام بعدی، ساخت فایلی است که گالپ به آن نیاز دارد تا با استفاده از آن، تسک‌ها و کارهایی را که برایش نوشته‌ایم، از آن بخواند و اجرا کند.

ایجاد فایل gulpfile.js

این فایل را یا به صورت دستی ایجاد کنید یا با خط فرمان با دستور ذیل:
touch gulpfile.js
و حالا آن را در ویراشگر مورد علاقه‌ی خود باز کنید. من از ویرایشگر Atom استفاده می‌کنم. اما notepad هم کفایت می‌کند.

نوشتن اولین تسک گالپ

در ویراشگر خط زیر را می‌نویسیم:
var gulp = require('gulp');
 require  به Node می‌گوید که به فولدر node_modules برای پکیج gulp نگاه کند. زمانیکه آن را پیدا کرد، آن را به متغیر gulp انتساب می‌دهیم تا از تابع‌های گالپ بتوانیم استفاده کنیم. حال می‌خواهیم اولین تسک خود را بنویسیم:
gulp.task('task-name', function() {
  // Stuff here
});
گالپ دارای ۴ تابع task,src,dest,watch است که با آنها آشنا خواهیم شد. task یک کار را برای گالپ تعریف می‌کند و سه پارامتر دارد. اولی نام تسک، دومی (اختیاری) وابستگی این تسک (بدین معنا که اول باید این وظیفه اجرا شود، سپس تسک جاری) و در نهایت تابع درون تسک‌ها را می‌نویسیم. برای مثال:
gulp.task('hello', function() {
  console.log('Hello Gulp !');
});
فایل را ذخیره کنید. می‌خواهیم به گالپ بگوییم که این وظیفه را انجام دهد. کافی است gulp hello  را در خط فرمان وارد کنیم. نتیجه به صورت زیر خواهد بود:


 البته که تسک‌هایی که برای گالپ می‌نویسیم، کاراتر از این است؛ برای مثال:
gulp.task('task-name', function () {
  return gulp.src('source-files') // Get source files with gulp.src
    .pipe(aGulpPlugin()) // Sends it through a gulp plugin
    .pipe(gulp.dest('destination')) // Outputs the file in the destination folder
})
با استفاده از متد src به گالپ می‌گوییم که مسیر مبداء فایل‌ها، برای انجام تسک کجا است و dest هم بعد از انجام تسک، فایل‌های خروجی را به مقصد مشخص می‌برد. متد pipe یک تابع ند جی اس است که مطابق مستندات خودش متدی است که تمام جریان‌های قابل خواندن را واکشی می‌کند و به مسیری [که به صورت آرگومان به عنوان مقصد] داده شده، هدایت می‌کند.
شاید در ابتدا نوشتن تسک برایتان کمی پیچیده باشد، اما بعد از ساخت اولین پروژه با گالپ، خواهید دانست که تسک نوشتن برای گالپ کاری بسیار آسان و شیرین است!

مخزن پروژه در گیت هاب : https://github.com/mmdsharifi/gulp-rtlBootstrap-fontawesome 
نام کامیت این قسمت: Init commit

در مقاله بعدی gulp را در کنار bower بکار خواهیم برد. بهتر است مطالعه‌ای در مورد bower نیز انجام دهید. (پیشنهاد: + و + )
مطالب
آشنایی با CLR: قسمت چهاردهم
در ادامه قسمت قبلی روش‌های زیادی جهت اضافه شدن یک ماژول به یک اسمبلی وجود دارند. اگر شما از کامپایلر سی‌شارپ برای ساخت یک فایل PE با جدول مانیفست استفاده می‌کنید، می‌توانید از سوئیچ AddModule/ استفاده کنید. برای اینکه بدانیم چگونه می‌توان یک اسمبلی چند فایله ساخت بیاید فرض کنیم که دو فایل سورس کد با مشخصات زیر داریم:
RUT.cs: این سورس شامل کدهایی است که به ندرت در برنامه استفاده می‌شود.
FUT.cs: این سورس شامل کدهایی است که به طور مکرر مورد استفاده قرار می‌گیرد.

ابتدا به صورت زیر کد سورسی را که به ندرت استفاده می‌شود، به عنوان یک ماژول جداگانه کامپایل می‌کنم:
csc /t:module RUT.cs
اجرای این خط سبب ایجاد یک فایل به نام RUT.netmodule می‌گردد که یک DLL استاندارد است؛ ولی CLR به تنهایی توانایی بارگیری آن را ندارد. دفعه‌ی بعد سورس کدی را که مکرر استفاده می‌شود، به صورت یک ماژول کامپایل می‌کنیم و از آنجائیکه این ماژول استفاده‌ی زیادی دارد، آن را نگهدارنده‌ی جدول مانیفست معرفی می‌کنیم و به این دلیل که این ماژول نماینده‌ی کل اسمبلی است، نام خروجی آن را به جای FUT.dll به MultiFileLibrary.dll تغییر می‌دهیم:
csc /out:MultiFileLibrary.dll /t:library /addmodule:RUT.netmodule FUT.cs
خط بالا به علت سوئیچ t:library\ فایل MultiFileLibrary.dll را ایجاد می‌کند. این فایل شامل جدول متادیتای مانیفست می‌شود و سوئیچ به آن می‌گوید که باید ماژول RUT.netmodule را جزئی از اسمبلی بداند. این سوئیچ به کامپایلر اعلام می‌کند که ارجاع این فایل در جدول FileDef  و ExportedTypesDef ثبت شود.
بعد از اتمام عملیات کامپایل، مطابق شکل زیر دو فایل ایجاد می‌شود که فایل سمت راست شامل جدول مانیفست است. فایل RUT.netmodule شامل کد IL و جداول متادیتاهای مربوط به خواص و رویدادها و مواردی از این قبیل است که در این ماژول یافت می‌شود. فایل بعدی MultiFileLibrary.dll هست که شامل کد IL کد FUT.CS می‌شوذ بعلاوه جداول متادیتا مثل ماژول قبلی و جدول متادیتای مانیفست که باعث می‌شود به عنوان یک اسمبلی شناخته شود.



البته توجه داشته باشید که جدول مانیفست ارجاعی به نوع‌های عمومی استخراج شده داخل فایل خودش ندارد، زیرا که در جداول اختصاصی خودش موجود است و در ذخیره سازی صرفه جویی می‌گردد.
بعد از اینکه MultiFileLibrary.dll ساخته شد، به منظور آزمایش کردن جداول متادیتا می‌توانید از ابزار ILDasm.exe استفاده کنید تا ارجاع به فایل RUT.netmodule به شما ثابت شود. آنچه در زیر می‌بینید نمایی از جداول FileDef و ExportedTypesDef است:
File #1 (26000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x26000001
Name : RUT.netmodule
HashValue Blob : e6 e6 df 62 2c a1 2c 59 97 65 0f 21 44 10 15 96 f2 7e db c2
Flags : [ContainsMetaData] (00000000)


ExportedType #1 (27000001)
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Token: 0x27000001
Name: ARarelyUsedType
Implementation token: 0x26000001
TypeDef token: 0x02000002
Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass]
[BeforeFieldInit](00100101)
 
همانطور که در بالا می‌بینید فایل RUT.netmodule با شناسه‌ی (توکن) 0x26000001 به عنوان بخشی از اسمبلی شناخته می‌شود و به نوع کد IL آن اشاره می‌کند.

قابل توجه افراد کنجکاو: توکن‌های جداول متا، مقادیر 4 بایتی است که بایت پر ارزش آن اشاره می‌کند که برای یافتن آن باید به چه جدولی ارجاع کرد. مقادیر زیر این نکته را روشن می‌کند که هر کد ابتدایی به چه جدولی اشاره می‌کند:
 0x01
 TypeRef
 0x02
 TypeDef
 0x23
 AssemblyRef
 0x26
 File
file definition

 0x27
 ExportedType
برای دیدن لیست کاملی از این کدها فایل Corhdr.h را که به همراه فریم ورک دات نت نصب می‌شود، مطالعه فرمایید. سه بایت باقیمانده هم بر اساس جدولی که به آن ارجاع شده است مشخص می‌گردد؛ مثلا در مثال بالا کد 0x26000001  به اولین سطر جدول File اشاره می‌کند. برای اکثر جدول‌ها شماره گذاری سطرها از عدد 1 آغاز می‌شود نه صفر یا برای برای جداول TypeDef عموما از عدد 2 آغاز می‌شود. 

برای اجرای اسمبلی، کامپایلر نیاز دارد که همه‌ی فایل‌های اسمبلی، نصب شده و قابل دسترس باشند و در صورتیکه شما فایل RUT.netmodule را حذف کنید کامپایلر سی شارپ خطای زیر را صادر می‌کند:
fatal error CS0009: Metadata file 'C:\ MultiFileLibrary.dll' could not be opened—'Error importing module 'RUT.netmodule' of assembly 'C:\ MultiFileLibrary.dll'—The system cannot find the file specified'

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