- کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
EF Code First #1
منظور شما اینه که باید با Nuget رفرنس Entity FrameWork رو روی هر سه پروژه ( Domain Classes و وDataLayer و پروژه اصلی) نصب کنم ؟
من وب اپلیکیشن تازه داره کار میکنم و تا الان همش وب سایت کار میکردم، آیا بصورت پیش فرض EntityFrameWork توی پروژهها وجود نداره و حتما باید با Nuget رفرنس اونو به پروژه اضافه کنیم ؟
(یعنی این dll با نصب ویژوال استودیو نصب نمیشه ؟! و باید از نوگت دانولدش کنیم ؟)
ایجاد یک پروژه با استفاده Razor
در ادامه با هم یک مثال را با استفاده از Razor ایجاد میکنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد میکنیم.
مراحل:
1- ابتدا در کلاس startup قابلیت MVC را فعال میکنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
services.AddMvc();
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); });
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); } } }
ایجاد 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 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
@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ها استفاده میکنیم. اگر بخواهیم برای 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>
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>
اعمال Layout
برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آنرا مقدار دهی و سپس Htmlهای اضافی موجود در آنرا مانند المنتهای head و Body حذف کنید؛ همانند کدهای زیر:
@model Product @{ Layout = "_BasicLayout"; ViewBag.Title = "Product"; }
در اینجا عبارت 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 = "_BasicLayout"; }
@model Product @{ ViewBag.Title = "Product"; }
شما همچنین میتوانید چندین فایل 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>
پردازش دادهها در مقابل فرمت
تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت دادهها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کردهام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء دادهای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی میکند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.
اضافه نمودن مقدار داده ای
سادهترین کاری را که میتوانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایجترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
<p>Product Name: @Model.Name</p>
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>
نکته: ویژگیهای دادهها که نام آنها *-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 را لغو کنید،میتوانید از :@ استفاده کنید تا عین آن عبارت درج شود.
گرفتن خروجی XML از جداول در SQL Server 2012
دیتابیس
Gulp #2
ساخت پروژه گالپ
npm init
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
نصب گالپ
npm install gulp --save-dev
ایجاد فایل gulpfile.js
touch gulpfile.js
نوشتن اولین تسک گالپ
var gulp = require('gulp');
gulp.task('task-name', function() { // Stuff here });
gulp.task('hello', function() { console.log('Hello Gulp !'); });
البته که تسکهایی که برای گالپ مینویسیم، کاراتر از این است؛ برای مثال:
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 })
مخزن پروژه در گیت هاب : https://github.com/mmdsharifi/gulp-rtlBootstrap-fontawesome
Roslyn #4
Compilation API، یک abstraction سطح بالا از فعالیتهای کامپایل Roslyn است. برای مثال در اینجا میتوان یک اسمبلی را از Syntax tree موجود، تولید کرد و یا جایگزینهایی را برای APIهای قدیمی CodeDOM و Reflection Emit ارائه داد. به علاوه این API امکان دسترسی به گزارشات خطاهای کامپایل را میسر میکند؛ به همراه دسترسی به اطلاعات Semantic analysis. در مورد تفاوت Syntax tree و Semantics در قسمت قبل بیشتر بحث شد.
با ارائهی Roslyn، اینبار کامپایلرهای خط فرمان تولید شده مانند csc.exe، صرفا یک پوسته بر فراز Compilation API آن هستند. بنابراین دیگر نیازی به فراخوانی Process.Start بر روی فایل اجرایی csc.exe مانند یک سری کتابخانههای قدیمی نیست. در اینجا با کدنویسی، به تمام اجزاء و تنظیمات کامپایلر، دسترسی وجود دارد.
کامپایل پویای کد توسط Roslyn
برای کار با API کامپایل، سورس کد، به صورت یک رشته در اختیار کامپایلر قرار میگیرد؛ به همراه تنظیمات ارجاعاتی به اسمبلیهایی که نیاز دارد. سپس کار کامپایلر شروع خواهد شد و شامل مواردی است مانند تبدیل متن دریافتی به Syntax tree و همچنین تبدیل مواردی که اصطلاحا به آنها Syntax sugars گفته میشود مانند خواص get و set دار به معادلهای اصلی آنها. در اینجا کار Semantic analysis هم انجام میشود و شامل تشخیص حوزهی دید متغیرها، تشخیص overloadها و بررسی نوعهای بکار رفتهاست. در نهایت کار تولید فایل باینری اسمبلی، از اطلاعات آنالیز شده صورت میگیرد. البته خروجی کامپایلر میتواند اسمبلیهای exe یا dll، فایل XML مستندات اسمبلی و یا فایلهای .netmudule و .winmdobj مخصوص WinRT هم باشد.
در ادامه، اولین مثال کار با Compilation API را مشاهده میکنید. پیشنیاز اجرای آن همان مواردی هستند که در قسمت قبل بحث شدند. یک برنامهی کنسول سادهی .NET 4.6 را آغاز کرده و سپس بستهی نیوگت Microsoft.CodeAnalysis را در آن نصب کنید. در ادامه کدهای ذیل را به پروژهی آماده شده اضافه کنید:
static void firstCompilation() { var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }"); var mscorlibReference = MetadataReference.CreateFromFile(typeof (object).Assembly.Location); var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var comp = CSharpCompilation.Create("Demo") .AddSyntaxTrees(tree) .AddReferences(mscorlibReference) .WithOptions(compilationOptions); var res = comp.Emit("Demo.dll"); if (!res.Success) { foreach (var diagnostic in res.Diagnostics) { Console.WriteLine(diagnostic.GetMessage()); } } }
متدهای تعریف شده توسط Compilation API به یک s جمع، ختم میشوند؛ به این معنا که در اینجا در صورت نیاز، چندین Syntax tree یا ارجاع را میتوان تعریف کرد.
پس از وهله سازی Compilation API و تنظیم آن، اکنون با فراخوانی متد Emit، کار تولید فایل اسمبلی نهایی صورت میگیرد. در اینجا اگر خطایی وجود داشته باشد، استثنایی را دریافت نخواهید کرد. بلکه باید خاصیت Success نتیجهی آنرا بررسی کرده و درصورت موفقیت آمیز نبودن عملیات، خطاهای دریافتی را از مجموعهی Diagnostics آن دریافت کرد. کلاس Diagnostic، شامل اطلاعاتی مانند محل سطر و ستون وقوع مشکل و یا پیام متناظر با آن است.
معرفی مقدمات Semantic analysis
Compilation API به اطلاعات Semantics نیز دسترسی دارد. برای مثال آیا Type A قابل تبدیل به Type B هست یا اصلا نیازی به تبدیل ندارد و به صورت مستقیم قابل انتساب هستند؟ برای درک بهتر این مفهوم نیاز است یک مثال را بررسی کنیم:
static void semanticQuestions() { var tree = CSharpSyntaxTree.ParseText(@" using System; class Foo { public static explicit operator DateTime(Foo f) { throw new NotImplementedException(); } void Bar(int x) { } }"); var mscorlib = MetadataReference.CreateFromFile(typeof (object).Assembly.Location); var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib).WithOptions(options); // var res = comp.Emit("Demo.dll"); // boxing var conv1 = comp.ClassifyConversion( comp.GetSpecialType(SpecialType.System_Int32), comp.GetSpecialType(SpecialType.System_Object) ); // unboxing var conv2 = comp.ClassifyConversion( comp.GetSpecialType(SpecialType.System_Object), comp.GetSpecialType(SpecialType.System_Int32) ); // explicit reference conversion var conv3 = comp.ClassifyConversion( comp.GetSpecialType(SpecialType.System_Object), comp.GetTypeByMetadataName("Foo") ); // explicit user-supplied conversion var conv4 = comp.ClassifyConversion( comp.GetTypeByMetadataName("Foo"), comp.GetSpecialType(SpecialType.System_DateTime) ); }
برای مثال نتیجهی بررسی آخرین تبدیل انجام شده در تصویر فوق مشخص است. با توجه به تعریف public static explicit operator DateTime در سورس کد مورد آنالیز، این تبدیل explicit بوده و همچنین user defined. به علاوه متدی هم که این تبدیل را انجام میدهد، مشخص کردهاست.
Roslyn #3
زمانیکه صحبت از Syntax میشود، منظور نمایش متنی سورس کدها است. برای بررسی و آنالیز آن، نیاز است این نمایش متنی، به ساختار دادهای ویژهای به نام Syntax tree تبدیل شود و این Syntax tree مجموعهای است از tokenها. Tokenها بیانگر المانهای مختلف یک زبان، شامل کلمات کلیدی، عملگرها و غیره هستند.
در تصویر فوق، مراحل تبدیل یک قطعه کد #C را به مجموعهای از tokenهای معادل آن مشاهده میکنید. علاوه بر اینها، Roslyn syntax tree شامل موارد ویژهای به نام Trivia نیز هست. برای مثال در حین نوشتن کدها، در ابتدای سطرها تعدادی space یا tab وجود دارند و یا در این بین ممکن است کامنتی نوشته شود. هرچند این موارد از دیدگاه یک کامپایلر بیمعنا هستند، اما ابزارهای Refactoring ایی که به Trivia دقت نداشته باشند، خروجی کد به هم ریختهای را تولید خواهند کرد و سبب سردرگمی استفاده کنندگان میشوند.
در تصویر فوق، اشارهگر ادیتور پس از تایپ semicolon قرار گرفتهاست. در این حالت میتوانید دو نوع trivia مخصوص فضای خالی و کامنتها را در syntax visualizer، مشاهده کنید.
به علاوه پس از هر token بازهای از اعداد را مشاهده میکنید که بیانگر محل قرارگیری آنها در سورس کد هستند. این محلها جهت ارائهی خطاهای دقیق مرتبط با آن نقاط، بسیار مفید هستند.
یک Syntax tree از مجموعهای از syntax nodes تشکیل میشود و هر node شامل مواردی مانند تعاریف، عبارات و امثال آن است. در افزونهی Syntax visualizer نودهایی که رنگ قرمز متمایل به قهوهای دارند، بیانگر نودهای Trivia، نودهای آبی، Syntax nodes و نودهای سبز، Syntax token هستند.
مفاهیم این رنگها را با کلیک بر روی دکمهی Legend هم میتوان مشاهده کرد.
تفاوت Syntax با Semantics
در Roslyn امکان کار با Syntax و Semantics کدها وجود دارد.
یک Syntax، از گرامر زبان خاصی پیروی میکند. در Syntax اطلاعات بسیار زیادی وجود دارند که معنای برنامه را تغییر نمیدهند؛ مانند کامنتها، فضاهای خالی و فرمت ویژهی کدها. البته فضاهای خالی در زبانهایی مانند پایتون دارای معنا هستند؛ اما در سیشارپ خیر. همچنین در Syntax، توافق نامهای وجود دارد که بیانگر تعدادی واژهی از پیش رزرو شده، مانند کلمات کلیدی هستند.
اما Semantics در نقطهی مقابل Syntax قرار میگیرد و بیانگر معنای سورس کد است. برای مثال در اینجا تقدم و تاخر عملگرها مفهوم پیدا میکنند و یا اینکه Type system چیست و چه نوعهایی را میتوان به دیگری نسبت داد و تبدیل کرد. عملیات Binding در این مرحله رخ میدهد و مفهوم identifierها را مشخص میکند. برای مثال x در این قسمت از سورس کد، به چه معنایی است و به کجا اشاره میکند؟
خواص ویژهی Syntax tree در Roslyn
- تمام اجزای کد را شامل عناصر سازندهی زبان و همچنین Trivia، به همراه دارد.
- API آن توسط کتابخانههای ثالث قابل دسترسی است.
- Immutable طراحی شدهاست. به این معنا که زمانیکه syntax tree توسط Roslyn ایجاد شد، دیگر تغییر نمیکند. به این ترتیب امکان دسترسی همزمان و موازی به آن بدون نیاز به انواع قفلهای مسایل همزمانی وجود دارد. اگر کتابخانهی ثالثی به Syntax tree ارائه شده دسترسی پیدا میکند، میتواند کاملا مطمئن باشد که این اطلاعات دیگر تغییری نمیکنند و نیازی به قفل کردن آنها نیست. همچنین این مساله امکان استفادهی مجدد از sub treeها را در حین ویرایش کدها میسر میکند. به آنها mutating trees نیز گفته میشود.
- مقاوم است در برابر خطاها. اگر از قسمت اول به خاطر داشته باشید، Roslyn میبایستی جایگزین کامپایلر دومی به نام کامپایلر پس زمینهی ویژوال استودیو که خطوط قرمزی را ذیل سطرهای مشکل دار ترسیم میکند، نیز میشد. فلسفهی طراحی این کامپایلر، مقاوم بودن در برابر خطاهای تایپی و هماهنگی آن با تایپ کدها توسط برنامه نویس بود. Syntax tree در Roslyn نیز چنین خاصیتی را دارد و اگر مشغول به تایپ شوید، باز هم کار کرده و اینبار خطاهای موجود را نمایش میدهد که میتواند توسط ابزارهای نمایش دهندهی ویژوال استودیو یا سایر ابزارهای ثالث استفاده شود.
برای نمونه در تصویر فوق، تایپ semicolon فراموش شدهاست؛ اما همچنان Syntax tree در دسترس است و به علاوه گزارش میدهد که semicolon مفقود است و تایپ نشدهاست.
Parse سورس کد توسط Roslyn
ابتدا یک پروژهی کنسول سادهی دات نت 4.6 را در VS 2015 آغاز کنید. سپس از طریق خط فرمان نیوگت، دستور ذیل را صادر نمائید:
PM> Install-Package Microsoft.CodeAnalysis
سپس کدهای ذیل را به آن اضافه کنید:
using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Roslyn01 { class Program { static void Main(string[] args) { parseText(); } static void parseText() { var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }"); Console.WriteLine(tree.ToString()); Console.WriteLine(tree.GetRoot().NormalizeWhitespace().ToString()); var res = SyntaxFactory.ClassDeclaration("Foo") .WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(new[] { SyntaxFactory.MethodDeclaration( SyntaxFactory.PredefinedType( SyntaxFactory.Token(SyntaxKind.VoidKeyword) ), "Bar" ) .WithBody(SyntaxFactory.Block()) })) .NormalizeWhitespace(); Console.WriteLine(res); } } }
کار Parse سورس کد دریافتی، بر اساس سرویسهای زبان متناظر با آنها آغاز میشود. برای مثال سرویسهایی مانند VisualBasicSyntaxTree و یا CSharpSyntaxTree مثال فوق که سورس کد مورد آنالیز آن، از نوع سیشارپ است.
این کلاسهای Factory، دارای دو متد Create و ParseText هستند. کار متد ParseText آن مشخص است؛ یک قطعهی متنی از کد را آنالیز کرده و معادل Syntax Tree آنرا تولید میکند. متد Create آن، اشیایی مانند نودهای Syntax visualizer را دریافت کرده و بر اساس آنها یک Syntax tree را تولید میکند.
کار با متد Create آنچنان ساده نیست. به همین جهت یکی از اعضای تیم Roslyn برنامهای را به نام Roslyn Quoter ایجاد کردهاست که نسخهی آنلاین آنرا در اینجا و سورس کد آنرا در اینجا میتوانید بررسی کنید.
جهت آزمایش، همان قطعهی متنی سورس کد مثال فوق را در نسخهی آنلاین آن جهت آنالیز و تولید ورودی متد Create، وارد کنید. خروجی آنرا میتوان مستقیما در متد Create بکار برد.
فرمت کردن خودکار کدها به کمک Roslyn
اگر بر روی tree حاصل، متد ToString را فراخوانی کنیم، خروجی آن مجددا سورس کد مورد آنالیز است. اگر علاقمند بودید که Roslyn به صورت خودکار کدهای ورودی را فرمت کند و تمام آنها را در یک سطر نمایش ندهد، متد NormalizeWhitespace را بر روی ریشهی Syntax tree فراخوانی کنید:
tree.GetRoot().NormalizeWhitespace().ToString()
class Foo { void Bar(int x) { } }
کوئری گرفتن از سورس کد توسط Roslyn
در ادامه قصد داریم با سه روش مختلف کوئری گرفتن از Syntax tree، آشنا شویم. برای این منظور متد ذیل را به پروژهای که در ابتدای برنامه آغاز کردیم، اضافه کنید:
static void querySyntaxTree() { var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar() {} }"); var node = (CompilationUnitSyntax)tree.GetRoot(); // Using the object model foreach (var member in node.Members) { if (member.Kind() == SyntaxKind.ClassDeclaration) { var @class = (ClassDeclarationSyntax)member; foreach (var member2 in @class.Members) { if (member2.Kind() == SyntaxKind.MethodDeclaration) { var method = (MethodDeclarationSyntax)member2; // do stuff } } } } // Using LINQ query methods var bars = from member in node.Members.OfType<ClassDeclarationSyntax>() from member2 in member.Members.OfType<MethodDeclarationSyntax>() where member2.Identifier.Text == "Bar" select member2; var res = bars.ToList(); // Using visitors new MyVisitor().Visit(node); }
روش اول کوئری گرفتن از Syntax tree، استفاده از object model آن است. در اینجا هربار، نوع و Kind هر نود را بررسی کرده و در نهایت به اجزای مدنظر خواهیم رسید. شروع کار هم با دریافت ریشهی syntax tree توسط متد GetRoot و تبدیل نوع آن نود به CompilationUnitSyntax میباشد.
روش دوم استفاده از روش LINQ است؛ با توجه به اینکه ساختار یک Syntax tree بسیار شبیه است به LINQ to XML. در اینجا یک سری نود، ریشه و فرزندان آنها را داریم که با روش LINQ بسیار سازگار هستند. برای نمونه در مثال فوق، در ریشهی Parse شده، در تمام کلاسهای آن، به دنبال متد یا متدهایی هستیم که نام آنها Bar است.
و در نهایت روش مرسوم و متداول کار با Syntax trees، استفاده از الگوی Visitors است. همانطور که در کدهای دو روش قبل مشاهده میکنید، باید تعداد زیادی حلقه و if و else نوشت تا به جزء و المان مدنظر رسید. راه سادهتری نیز برای مدیریت این پیچیدگی وجود دارد و آن استفاده از الگوی Visitor است. کار این الگو ارائهی متدهایی قابل override شدن است و فراخوانی آنها، در طی حلقههایی پشت صحنه که این Visitor را اجرا میکنند، صورت میگیرد. بنابراین در اینجا دیگر برای رسیدن به یک متد، حلقه نخواهید نوشت. تنها کاری که باید صورت گیرد، override کردن متد Visit المانی خاص در Syntax tree است.
هر نود در syntax tree دارای متدی است به نام Accept که یک Visitor را دریافت میکند. همچنین Visitorهای نوشته شده نیز دارای متد Visit یک نود هستند.
نمونهای از این Visitors را در کلاس ذیل مشاهده میکنید:
class MyVisitor : CSharpSyntaxWalker { public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (node.Identifier.Text == "Bar") { // do stuff } base.VisitMethodDeclaration(node); } }
کلاس پایهی CSharpSyntaxWalker از کلاس CSharpSyntaxVisitor مشتق شدهاست و به تمام امکانات آن دسترسی دارد. علاوه بر آنها، کلاس CSharpSyntaxWalker به Tokens و Trivia نیز دسترسی دارد.
نحوهی استفاده از Visitor سفارشی نوشته شده نیز به صورت ذیل است:
new MyVisitor().Visit(node);
Strong Name
System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
برای استفاده از این تکنولوژی ابتدا نیاز است تا یک جفتکلید عمومی/خصوصی (توسط ادمین، منبع گواهینامهها، یک بانک یا یک ابزار خاص) فراهم شود تا از آن برای اینکریپشن استفاده شود. سپس دادههای موردنظر (هر داده کلی که قصد ارسال و توزیع آن را داریم مثل یک اسمبلی) با استفاده از یک الگوریتم هشکردن (مثل MD5، SHA یا ترکیبی از آنها، هرچند MD5 توصیه نمیشود) پردازش شده و یک هشکد مخصوص تولید میشود. این هشکد با استفاده از کلید خصوصی دردسترس اینکریپت میشود و به عنوان یک امضای دیجیتال به همراه داده موردنظر ارسال یا توزیع میشود. در سمت مصرف کننده که با استفاده از یک روش خاص و امن به کلید عمومی دسترسی پیدا کرده است عملیات دیکریپت کردن این امضای دیجیتال با استفاده از کلید عمومی انجام شده و هشکد مربوطه بدست میآید. همچنین عملیات تولید هشکد با استفاده از دادهها در سمت مصرف کننده انجام شده و هشکد دادهها نیز دوباره با استفاده از همان الگوریتم استفاده شده در سمت توزیعکننده تولید میشود. سپس این دو مقدار محاسبه شده در سمت مصرفکننده با یکدیگر مقایسه شده و درصورت برابر بودن میتوان اطمینان حاصل کرد همان دادهای که توزیع کننده در اصل ارسال کرده بدون تغییر به دست مصرف کننده رسیده است. درواقع ویژگی اینکریپت/دیکریپت کردن دادهها توسط جفتکلید این است که بهصورت یکطرفه بوده و دادههای اینکریپت شده با استفاده از یک کلید خصوصی را تنها با استفاده از کلید عمومی همان کلید خصوصی میتوان بدرستی دیکریپت کرد.
1. تولید و مدیریت جفتکلیدهای قوی- نامگذاریشده (Strongly Named Key Pairs)
همانطور که در قسمت قبل اشاره شد برای نامگذاری قوی یک اسمبلی به یک کلید عمومی (public key) و یک کلید خصوصی (private key) که در مجموع به آن یک جفت کلید (key pair) میگویند، نیاز است.برای اینکار میتوان با استفاده از برنامه sn.exe (عنوان کامل آن Microsoft .Net Framework Strong Name Utility است) یک جفت کلید تولید کرده و آن را در یک فایل و یا در CSP (یا همان cryptographic service provider) ذخیره کرد. همچنین اینکار را میتوان توسط ویژوال استودیو نیز انجام داد. امکان موردنظر در فرم پراپرتی یک پروژه و در تب Signing آن وجود دارد.
نکته: یک CSP عنصری از API کریپتوگرافی ویندوز (Win32 CryptoAPI) است که سرویسهایی چون اینکریپشن، دیکریپشن، و تولید امضای دیجیتال را فراهم میکند. این پرووایدرها همچنین تسهیلاتی برای مخازن کلیدها فراهم میکنند که از اینکریپشنهای قوی و ساختار امنیتی سیستم عامل (سیستم امنیتی و دسترسی کاربران ویندوز) برای محافظت از تمام کلیدهای کریپتوگرافی ذخیره شده در مخزن استفاده میکند. بهطور خلاصه و مفید میشود اشاره کرد که میتوان کلیدهای کریپتوگرافی را درون یک مخزن کلید CSP ذخیره کرد و تقریبا مطمئن بود که تا زمانیکه هیچکس کلمه عبور سیستم عامل را نداند، این کلیدها امن خواهند ماند. برای کسب اطلاعات بیشتر به دادههای CryptoAPI در اسناد SDK سیستم عامل خود مراجعه کنید.
برنامه sn به همراه SDKهای ویندوز نصب میشود. البته با نصب ویژوال استودیو تمام SDKهای موردنیاز مطابق با نسخههای موجود، نصب خواهد شد. مسیر نسخه 4 و 32 بیتی این برنامه در سیستم عامل Windows 7 بهصورت زیر است:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sn.exe
با استفاده از آرگومان k همانند دستور زیر یک جفتکلید جدید تولید شده و در فایل MyKeys.snk در ریشه درایو d: ذخیره میشود:
sn –k d:\MyKeys.snk
نکته: به بزرگی و کوچکی حروف سوییچهای دستورات برنامه sn دقت کنید!
این کار یک جفت کلید کریپتوگرافی 1024 بیتی بهصورت تصادفی تولید میکند. این دستور را باید در خط فرمانی (Command Prompt) اجرا نمود که مسیر فایل sn.exe را بداند. برای راحتی کار میتوان از خط فرمان ویژوال استودیو (Visual Studio Command Prompt) استفاده کرد.
نکته: اجرای عملیات فوق در یک شرکت یا قسمت توسعه یک شرکت، تنها یک بار نیاز است زیرا تمام اسمبلیهای تولیدی تا زمانیکه عناوین ساده متمایزی دارند میتوانند از یک جفت کلید مشترک استفاده کنند.
نکته: هرچند که میتوان از پسوندهای دیگری نیز برای نام فایل حاوی جفت کلید استفاده کرد، اما توصیه میشود از همین پسوند snk. استفاده شود.
فایل تولید شده حاوی هر دو کلید «عمومی» و «خصوصی» است. میتوان با استفاده از دستور زیر کلید عمومی موجود در فایل mykeys.snk را استخراج کرده و در فایل mypublickey.snk ذخیره کرد:
sn –p d:\mykeys.snk d:\mypublickey.snk
sn -tp MyPublicKey.snk
sn -i MyKeys.snk MyStrongNameKeys
sn –m n
sn –m y
sn -d MyStrongNameKeys
نکته: برای استفاده از این ویژگی در ویژوال استودیو، باید در تب Signing در تنظیمات پروژه گزینه Sign the Assembly را انتخاب کرد. سپس میتوان فایل حاوی جفت کلیدهای تولیدشده را انتخاب یا فایل جدیدی تولید کرد. البته ویژوال استودیو تا نسخه 2010 امکانی جهت استفاده از مخازن CSP را ندارد.
[assembly:AssemblyKeyFileAttribute("MyKeys.snk")]
sn –vf MyAsm.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. Failed to verify assembly -- Strong name validation failed for assembly MyAsm.exe'.
sn –p d:\MyKeys.snk d:\MyPublicKey.snk sn –pc MyKeysContainer d:\MyPublicKey.snk
csc.exe /delaysign /keyfile:d:\MyPublicKey.snk /out:d:\MyAsm.exe d:\Class1.cs
al /out:<assembly name> <module name> /keyfile:<file name>
sn –Vr d:\MyAsm.exe
sn –R d:\MyAsm.exe MyKeys.snk sn –R d:\MyAsm.exe MyKeysContainer
sn –D assembly1 assembly2
sn –Vu d:\MyAsm.exe
sn –Vx
sn –Vl
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
gacutil /i c:\MyAsm.dll
gacutil /u MyAsm
gacutil /u MyAsm,Version=1.3.0.5
gacutil /l
gacutil /l MyAsm