FirstName
firstName
strFirstName lblFirstName
<Company>.<Technology|Produt|Project>[.<Feature>][.<SubNamespace>]
Microsoft.Reporting.WebForms Kara.Support.Manager.Enums Kara.CSS.HQ.WebUI.Configuration
PackageManager , PacakgeConfigGenerator Circle , Utility , Package
CreateConfig , classdata CManager , ClsPackage , Config_Creator , Config1389
CenterCollection , PackageCollection
public delegate void Logger (string log); public delegate void LoggingCallback (object sender, string reason);
AddEventArgs , EditEventArgs , DeleteEventArgs
Adding , Add , Added
public delegate void <EventName>EventHandler (object sender, <EventName>EventArgs e);
public event EventHandler <AddEventArgs> Adding;
DisplayNameAttribute , MessageTypeAttribute
IComponent (اسم) IConnectionProvider (موصوف) ICloneable (صفت)
public enum FileMode { Append, Read, … }
[Flag] public enum KeyModifiers { Alt = 1, Control = 2, Shift = 4 }
public enum OperationState { DoneState, FaultState, RollbackState }
AddDays , Save , DeleteRow , BindData , Close , Open
Radius , ReportType , DataSource , Mode , CurrentCenterId
public CenterCollection Centers { get; set; }
if (list.Contains(item)) if (regularExpression.Matches(text)) if (stream.CanSeek) if (context.Created) if (form.Enabled)
if (list.IsContains(item)) if (regularExpression.Match(text)) if (stream.Seekable) if (context.IsCreated) if (form.IsEnabled)
public Color Color { get; set; }
firstName , e , id , packageId , centerName , name
public static MyType operator +(MyType left, MyType right) public static bool operator ==(MyType left, MyType right)
public static MyType operator ++(MyType value)
public static MyType operator /(MyType dividend, MyType divisor)
public static MyType operator -(MyType d1, MyType d2) // incorrect!
Area , DataBinder , PublicCacheName
_centersList _firstName _currentCenter
parameterType , packageOperationTypeId
lblName (Label) txtHeader (TextBox) btnSave (Button)
ArgumentNullException , InvalidOperaionException
System.Data
<Company>.<Component>.dll <Company>.<Project|Product|Technology>.<Component>.dll
Microsoft.CSharp.dll , Kara.CSS.Manager.dll
public int IComparer<T> {…} public delegate bool Predicate<T> (T item)
public int ISessionChannel<TSession> {…} public delegate TOutput Converter<TInput, TOutput> (TInput from) public class Nullable<T> {…} public class List<T> {…}
ArgumentExceptionIllegalCharacters ArgumentExceptionInvalidName ArgumentExceptionFileNotFound
Pascal Casing |
camel Casing |
Wrong |
Callback |
callback |
CallBack |
BitFlag |
bitFlag |
Bitflag / bitflag |
Canceled |
canceled |
Cancelled |
DoNot |
doNot |
Donot / Don’t |
|
|
|
Endpoint |
endpoint |
EndPoint / endPoint |
FileName |
fileName |
Filename / filename |
Gridline |
gridline |
GridLine / gridLine |
Hashtable |
hashtable |
HashTable / hashTable |
Id |
id |
ID |
Indexes |
indexes |
Indices |
LogOff |
logOff |
Logoff / LogOut ! |
LogOn |
logOn |
Logon / LogIn ! |
SignOut |
signOut |
Signout / SignOff |
SignIn |
signIn |
Signin / SignOn |
Metadata |
metadata |
MetaData / metaData |
Multipanel |
multipanel |
MultiPanel / multiPanel |
Multiview |
multiview |
MultiView / multiView |
Namespace |
namespace |
NameSpace / nameSpace |
Ok |
ok |
OK |
Pi |
pi |
PI |
Placeholder |
placeholder |
PlaceHolder / placeHolder |
UserName |
username |
Username / username |
WhiteSpace |
whiteSpace |
Whitespace / whitespace |
Writable |
writable |
Writeable / writeable |
public class Color { … public static Color FromArgb(…) { … } }
ASP.NET MVC #5
بررسی نحوه انتقال اطلاعات از یک کنترلر به Viewهای مرتبط با آن
در ASP.NET Web forms در فایل code behind یک فرم مثلا میتوان نوشت Label1.Text و سپس مقداری را به آن انتساب داد. اما اینجا به چه ترتیبی میتوان شبیه به این نوع عملیات را انجام داد؟ با توجه به اینکه در کنترلرها هیچ نوع ارجاع مستقیمی به اشیاء رابط کاربری وجود ندارد و این دو از هم مجزا شدهاند.
در پاسخ به این سؤال، همان مثال ساده قسمت قبل را ادامه میدهیم. یک پروژه جدید خالی ایجاد شده است به همراه HomeController ایی که به آن اضافه کردهایم. همچنین مطابق روشی که ذکر شد، View ایی به نام Index را نیز به آن اضافه کردهایم. سپس برای ارسال اطلاعات از یک کنترلر به View از یکی از روشهای زیر میتوان استفاده کرد:
الف) استفاده از اشیاء پویا
ViewBag یک شیء dynamic است که در دات نت 4 امکان تعریف آن میسر شده است. به این معنا که هر نوع خاصیت دلخواهی را میتوان به این شیء انتساب داد و سپس این اطلاعات در View نیز قابل دسترسی و استخراج خواهد بود. مثلا اگر در اینجا به شیء ViewBag، خاصیت دلخواه Country را اضافه کنیم و سپس مقداری را نیز به آن انتساب دهیم:
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
return View();
}
}
}
این اطلاعات در View مرتبط با اکشنی به نام Index به نحو زیر قابل بازیابی خواهد بود (نحوه اضافه کردن View متناظر با یک اکشن یا متد را هم در قسمت قبل با تصویر مرور کردیم):
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Country : @ViewBag.Country
</p>
در این مثال، @ در View engine جاری که Razor نام دارد، به این معنا میباشد که این مقداری است که میخواهم دریافت کنی (ViewBag.Country) و سپس آنرا در حین پردازش صفحه نمایش دهی.
ب) انتقال اطلاعات یک شیء کامل و غیر پویا به View
هر پروژه جدید MVC به همراه پوشهای به نام Models است که در آن میتوان تعاریف اشیاء تجاری برنامه را قرار داد. در پروژه جاری، یک کلاس ساده را به نام Employee به این پوشه اضافه میکنیم:
namespace MvcApplication1.Models
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
اکنون برای نمونه یک وهله از این شیء را در متد Index ایجاد کرده و سپس به view متناظر با آن ارسال میکنیم (در قسمت return View کد زیر مشخص است). بدیهی است این وهله سازی در عمل میتواند از طریق دسترسی به یک بانک اطلاعاتی یا یک وب سرویس و غیره باشد.
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
var employee = new Employee
{
Email = "name@site.com",
FirstName = "Vahid",
LastName = "N."
};
return View(employee);
}
}
}
امضاهای متفاوت (overloads) متد کمکی View هم به شرح زیر هستند:
ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)
اکنون برای دسترسی به اطلاعات این شیء employee در View متناظر با این متد، چندین روش وجود دارد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country <br />
FirstName: @Model.FirstName
</div>
میتوان از طریق شیء استاندارد دیگری به نام Model (که این هم یک شیء dynamic است مانند ViewBag قسمت قبل)، به خواص شیء یا مدل ارسالی به View جاری دسترسی پیدا کرد که یک نمونه از آنرا در اینجا ملاحظه میکنید.
روش دوم، بر اساس تعریف صریح نوع مدل است به نحو زیر:
@model MvcApplication1.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country
<br />
FirstName: @Model.FirstName
</div>
در اینجا در مقایسه با قبل، تنها یک سطر به اول فایل View اضافه شده است که در آن نوع شیء Model تعیین میگردد (کلمه model هم در اینجا با حروف کوچک شروع شده است). به این ترتیب اینبار اگر سعی کنیم به خواص این شیء دسترسی پیدا کنیم، Intellisense ویژوال استودیو ظاهر میشود. به این معنا که شیء Model بکارگرفته شده اینبار دیگر dynamic نیست و دقیقا میداند که چه خواصی را باید پیش از اجرای برنامه در اختیار استفاده کننده قرار دهد.
به این روش، روش Strongly typed view هم گفته میشود؛ چون View دقیقا میداند که چون نوعی را باید انتظار داشته باشد؛ تحت نظر کامپایلر قرار گرفته و همچنین Intellisense نیز برای آن مهیا خواهد بود.
به همین جهت این روش Strongly typed view، در بین تمام روشهای مهیا، به عنوان روش توصیه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views یک مزیت دیگر را هم به همراه دارد: فعال شدن یک code generator توکار در VS.NET به نام scaffolding. یک مثال ساده:
تا اینجا ما اطلاعات یک کارمند را نمایش دادیم. اگر بخواهیم یک لیست از کارمندها را نمایش دهیم چه باید کرد؟
روش کار با قبل تفاوتی نمیکند. اینبار در return View ما، یک شیء لیستی ارائه خواهد شد. در سمت View هم با یک حلقه foreach کار نمایش این اطلاعات صورت خواهد گرفت. راه سادهتری هم هست. اجازه دهیم تا خود VS.NET، کدهای مرتبط را برای ما تولید کند.
یک کلاس دیگر به پوشه مدلهای برنامه اضافه کنید به نام Employees با محتوای زیر:
using System.Collections.Generic;
namespace MvcApplication1.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
};
}
}
}
سپس متد جدید زیر را به کنترلر Home اضافه کنید.
public ActionResult List()
{
var employeesList = new Employees().CreateEmployees();
return View(employeesList);
}
برای اضافه کردن View متناظر با آن، روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه ظاهر شده:
تیک مربوط به Create a strongly typed view را قرار دهید. سپس در قسمت Model class، کلاس Employee را انتخاب کنید (نه Employees جدید را، چون از آن میخواهیم به عنوان منبع داده لیست تولیدی استفاده کنیم). اگر این کلاس را مشاهده نمیکنید، به این معنا است که هنوز برنامه را یکبار کامپایل نکردهاید تا VS.NET بتواند با اعمال Reflection بر روی اسمبلی برنامه آنرا پیدا کند. سپس در قسمت Scaffold template گزینه List را انتخاب کنید تا Code generator توکار VS.NET فعال شود. اکنون بر روی دکمه Add کلیک نمائید تا View نهایی تولید شود. برای مشاهده نتیجه نهایی مسیر http://localhost/Home/List باید بررسی گردد.
ج) استفاده از ViewDataDictionary
ViewDataDictionary از نوع IDictionary با کلیدی رشتهای و مقداری از نوع object است. توسط آن شیءایی به نام ViewData در ASP.NET MVC به نحو زیر تعریف شده است:
public ViewDataDictionary ViewData { get; set; }
این روش در نگارشهای اولیه ASP.NET MVC بیشتر مرسوم بود. برای مثال:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["DateTime"] = "<br/>" + DateTime.Now;
return View();
}
}
}
و سپس جهت استفاده از این ViewData تعریف شده با کلید دلخواهی به نام DateTime در View متناظر با اکشن Index خواهیم داشت:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
DateTime: @ViewData["DateTime"]
</div>
یک نکته امنیتی:
اگر به مقدار انتساب داده شده به شیء ViewDataDictionary دقت کنید، یک تگ br هم به آن اضافه شده است. برنامه را یکبار اجرا کنید. مشاهده خواهید کرد که این تگ به همین نحو نمایش داده میشود و نه به صورت یک سطر جدید HTML . چرا؟ چون Razor به صورت پیش فرض اطلاعات را encode شده (فراخوانی متد Html.Encode در پشت صحنه به صورت خودکار) در صفحه نمایش میدهد و این مساله از لحاظ امنیتی بسیار عالی است؛ زیرا جلوی بسیاری از حملات cross site scripting یا XSS را خواهد گرفت.
احتمالا الان این سؤال پیش خواهد آمد که اگر «عالمانه» بخواهیم این رفتار نیکوی پیش فرض را غیرفعال کنیم چه باید کرد؟
برای این منظور میتوان نوشت:
@Html.Raw(myString)
و یا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>
به این ترتیب خروجی Razor دیگر encode شده نخواهد بود.
د) استفاده از TempData
TempData نیز یک dictionary دیگر برای ذخیره سازی اطلاعات است و به نحو زیر در فریم ورک تعریف شده است:
public TempDataDictionary TempData { get; set; }
TempData در پشت صحنه از سشنهای ASP.NET جهت ذخیره سازی اطلاعات استفاده میکند. بنابراین اطلاعات آن در سایر کنترلرها و View ها نیز در دسترس خواهد بود. البته TempData یک سری تفاوت هم با سشن معمولی ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پایان درخواست از بین خواهد رفت.
هر دو مورد هم به جهت بالابردن کارآیی برنامههای ASP.NET MVC و مصرف کمتر حافظه سرور درنظر گرفته شدهاند.
البته کسانی که برای بار اول هست با ASP.NET مواجه میشوند، شاید سؤال بپرسند این مسایل چه اهمیتی دارد؟ پروتکل HTTP، ذاتا یک پروتکل «بدون حالت» است یا Stateless هم به آن گفته میشود. به این معنا که پس از ارائه یک صفحه وب توسط سرور، تمام اشیاء مرتبط با آن در سمت سرور تخریب خواهند شد. این مورد متفاوت است با برنامههای معمولی دسکتاپ که طول عمر یک شیء معمولی تعریف شده در سطح فرم به صورت یک فیلد، تا زمان باز بودن آن فرم، تعیین میگردد و به صورت خودکار از حافظه حذف نمیشود. این مساله دقیقا مشکل تمام تازه واردها به دنیای وب است که چرا اشیاء ما نیست و نابود شدند. در اینجا وب سرور قرار است به هزاران درخواست رسیده پاسخ دهد. اگر قرار باشد تمام این اشیاء را در سمت سرور نگهداری کند، خیلی زود با اتمام منابع مواجه میگردد. اما واقعیت این است که نیاز است یک سری از اطلاعات را در حافظه نگه داشت. به همین منظور یکی از چندین روش مدیریت حالت در ASP.NET استفاده از سشنها است که در اینجا به نحو بسیار مطلوبی، با سربار حداقل توسط TempData مدیریت شده است.
یک مثال کاربردی در این زمینه:
فرض کنید در متد جاری کنترلر، ابتدا بررسی میکنیم که آیا ورودی دریافتی معتبر است یا خیر. در غیراینصورت، کاربر را به یک View دیگر از طریق کنترلری دیگر جهت نمایش خطاها هدایت خواهیم کرد.
همین «هدایت مرورگر به یک View دیگر» یعنی پاک شدن و تخریب اطلاعات کنترلر قبلی به صورت خودکار. بنابراین نیاز است این اطلاعات را در TempData قرار دهیم تا در کنترلری دیگر قابل استفاده باشد:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult InsertData(string name)
{
// Check for input errors.
if (string.IsNullOrWhiteSpace(name))
{
TempData["error"] = "name is required.";
return RedirectToAction("ShowError");
}
// No errors
// ...
return View();
}
public ActionResult ShowError()
{
var error = TempData["error"] as string;
if (!string.IsNullOrWhiteSpace(error))
{
ViewBag.Error = error;
}
return View();
}
}
}
در همان HomeController دو متد جدید به نامهای InsertData و ShowError اضافه شدهاند. در متد InsertData ابتدا بررسی میشود که آیا نامی وارد شده است یا خیر. اگر خیر توسط متد RedirectToAction، کاربر به اکشن یا متد ShowError هدایت خواهد شد.
برای انتقال اطلاعات خطایی که میخواهیم در حین این Redirect نمایش دهیم نیز از TempData استفاده شده است.
بدیهی است برای اجرا این مثال نیاز است دو View جدید برای متدهای InsertData و ShowError ایجاد شوند (کلیک راست روی نام متد و انتخاب گزینه Add view برای اضافه کردن View مرتبط با آن اکشن).
محتوای View مرتبط با متد افزودن اطلاعات فعلا مهم نیست، ولی View نمایش خطاها در سادهترین حالت مثلا میتواند به صورت زیر باشد:
@{
ViewBag.Title = "ShowError";
}
<h2>Error</h2>
@ViewBag.Error
برای آزمایش برنامه هم مطابق مسیریابی پیش فرض و با توجه به قرار داشتن در کنترلری به نام Home، مسیر http://localhost/Home/InsertData ابتدا باید بررسی شود. چون آرگومانی وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودکار هدایت خواهد شد.
نکتهای تکمیلی در مورد Strongly typed viewها:
عنوان شد که Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زیرا اطلاعات اشیاء و خواص تعریف شده در یک View تحت نظر کامپایلر قرار میگیرند که بسیار عالی است. یعنی اگر در View بنویسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجی ندارد، برنامه نباید کامپایل شود. یکبار این را بررسی کنید. برنامه بدون مشکل کامپایل میشود! اما تنها در زمان اجرا است که صفحه زرد رنگ معروف خطاهای ASP.NET ظاهر میشود که چنین خاصیتی وجود ندارد (این حالت پیش فرض است؛ یعنی کامپایل یک View در زمان اجرا). البته این باز هم خیلی بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد کنیم، در زمان اجرا تنها چیزی نمایش داده نخواهد شد؛ اما با روش Strongly typed view، حتما خطای Compilation Error به همراه نمایش محل مشکل نهایی، در صفحه ظاهر خواهد شد.
سؤال: آیا میشود پیش از اجرای برنامه هم این بررسی را انجام داد؟
پاسخ: بله. باید فایل پروژه را اندکی ویرایش کرده و مقدار MvcBuildViews را که به صورت پیش فرض false هست، true نمود. یا خارج از ویژوال استودیو با یک ادیتور متنی ساده مثلا فایل csproj را گشوده و این تغییر را انجام دهید. یا داخل ویژوال استودیو، بر روی نام پروژه کلیک راست کرده و سپس گزینه Unload Project را انتخاب کنید. مجددا بر روی این پروژه Unload شده کلیک راست نموده و گزینه edit را انتخاب نمائید. در صفحه باز شده، MvcBuildViews را یافته و آنرا true کنید. سپس پروژه را Reload کنید.
اکنون اگر پروژه را کامپایل کنید، پیغام خطای زیر پیش از اجرای برنامه قابل مشاهده خواهد بود:
'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1'
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1
البته بدیهی است این تغییر، زمان Build پروژه را مقداری افزایش خواهد داد؛ اما امنترین حالت ممکن برای جلوگیری از این نوع خطاهای تایپی است.
یا حداقل بهتر است یکبار پیش از ارائه نهایی برنامه این مورد فعال و بررسی شود.
و یک خبر خوب!
مجوز سورس کد ASP.NET MVC از MS-PL به Apache تغییر کرده و همچنین Razor و یک سری موارد دیگر هم سورس باز شدهاند. این تغییرات به این معنا خواهند بود که پروژه از حالت فقط خواندنی MS-PL به حالت متداول یک پروژه سورس باز که شامل دریافت تغییرات و وصلهها از جامعه برنامه نویسها است، تغییر کرده است (^ و ^).
اوراکل به عنوان دومین شرکت بزرگ تولیدکننده نرمافزار اعلام کرد تا دو ماه آینده اولین محصول زنجیرهبلوکی خود را ارائه میدهد
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);
مکانیزم Eventing
PM> Install-Package DNTFrameworkCore
public class TaskEditingBusinessEventHandler : BusinessEventHandler<EditingBusinessEvent<TaskModel, int>> { private readonly ILogger<TaskEditingBusinessEventHandler> _logger; public TaskEditingBusinessEventHandler(ILogger<TaskEditingBusinessEventHandler> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override Task<Result> Handle(EditingBusinessEvent<TaskModel, int> @event) { foreach (var model in @event.Models) { _logger.LogInformation($"Title changed from: {model.OriginalValue.Title} to: {model.NewValue.Title}"); } return Task.FromResult(Ok()); } }
کار با پیادهسازی واسط جنریک IBusinessEventHandler یا ارثبری از کلاس جنریک BusinessEventHandler آغاز میشود؛ سپس نیاز است Type Parameter متناظر را نیز مشخص کنیم. برای این منظور در تکه کد بالا از رخداد جنریک EditingBusinessEvent استفاده شده است. همچنین همانطور که ملاحظه میکنید، نیاز است نوع Model مورد نظر نیز مشخص شده باشد؛ در اینجا از TaskModel به عنوان Model/DTO عملیات CUD موجودیت Task استفاده شده است.
رخدادهای Creating/Created/Deleting/Deleted دارای خصوصیتی بنام Models هستند که نوع آن IEnumerable<TModel> میباشد. ولی این خصوصیت در رخدادهای Editing/Edited از نوع IEnumerable<ModifiedModel<TModel>> میباشد؛ در این صورت به مقادیر موجود در بانک اطلاعاتی و همچنین مقادیری که توسط استفاده کننده از سرویس جاری به عنوان آرگومان به متد ویرایش ارسال شده است، دسترسی خواهیم داشت.
public class ModifiedModel<TValue> { public TValue NewValue { get; set; } public TValue OriginalValue { get; set; } }
استفاده از سرویسهای موجودیتها
OOP : Everything is an object CRUD-based thinking : Everything is CRUD
public class ItemCategoryCreatedBusinessEventHandler : IBusinessEventHandler<CreatedBusinessEvent<ItemCategoryModel, int>> { private readonly ISaleMethodService _saleMethodService; public TaskEditingBusinessEventHandler(ISaleMethodService saleMethodService) { _saleMethodService = saleMethodService ?? throw new ArgumentNullException(nameof(saleMethodService)); } public override Task<Result> Handle(CreatedBusinessEvent<ItemCategoryModel, int> @event) { var methods = _saleMethodService.FindAsnc(); foreach (var method in methods) { foreach (var model in @event.Models) { method.ItemCategories.Add(new SaleMethodItemCategoryModel { ItemCategoryId = model.Id, TrackingState = TrackingState.Added; }); } } return _saleMethodService.EditAsync(methods); }
ربطش به برنامه نویسی
در کل به درجهی روانی به اشتراک گذاری اطلاعات رسیدن، کار سختی است. به همین جهت باز هم تکرار میکنم این دور و اطراف بگردید، ماهی 5 نفر رو شاید پیدا کنید که حاضر باشند مطلب فنی مهمی رو به رایگان منتشر کنند و قید همه چیز آنرا بزنند. زمانیکه هم که روز به روز تعدادشون کمتر بشه، انگیزه رو از بقیه خواهند گرفت.
سوال: چگونه این فایل را در Jcenter آپلود کنیم؟
فرآیندی که در این نوشتار قصد داریم دنبال شود شامل مراحل زیر است:
ابتدا کتابخانهی خودمان را روی جی سنتر قرار داده و در صورتیکه علاقه داشته باشیم، آن را به mavenCentral هم انتقال میدهیم.
ابتدا نیاز است در سایت bintray ثبت نام کنید و با حساب جدید وارد شوید و گزینهی maven را انتخاب کنید.
سپس روی گزینهی Add New Package کلیک کنید تا یک پکیج جدید را ایجاد کنیم.
در صفحهای که باز میشود، اطلاعات مربوط به این پکیج را وارد کنید که عموما شامل نام پکیج، مجوز آن، کلمات کلیدی، لینک گزارش باگ و .. میشود. در انتخاب نام پکیج، قانون اجباری یا خاصی وجود ندارد؛ ولی توصیه میشود که از حروف کوچک و - استفاده گردد. بعد از پرکردن فیلدهای الزامی، وارد صفحهی جزئیات پکیج میشوید که در آن فیلدهای اضافهتری نیز وجود دارند که میتوانید در صورت تمایل آنها را پر کنید. همچنین در بالای صفحه لینک به صفحهی اختصاصی این پکیج نیز وجود دارد که در زیر عبارت Edit Package قرار گرفته است.
پی نوشت : اگر قصد آپلود کتابخانهی خود را در این سایت ندارید، میتوانید این سوال و مرحلهی امضای خودکار را از مراحل کاری خود حذف کنید.
سوال: چگونه این فایل را در SonaType آپلود کنیم؟
گام اول: ابتدا باید در سایت ثبت نام کنید. پس به این صفحه رفته و ثبت نام کنید. سپس در یک مرحلهی غیرمنطقی باید یک issue توسط سیستم JIRA ایجاد کنید. برای همین گزینهی Creare را در بالای صفحه بزنید. اطلاعات زیر را به ترتیب پر کنید:
Project: Community Support - Open Source Project Repository Hosting Issue Type: New Project Summary: مثلا نام پروژه خودتان را بنویسید یک نام پکیج که سعی کنید کتابخانههای هم خانواده این اشتراک را داشته باشند که در یک گروه قرار بگیرند Group Id: AndroidBreadCrumb.Plus آدرس جایی که پروژه قرار دارد Project URL: https://github.com/yeganehaym/AndroidBreadCrumb //آدرس سیستم کنترل نسخه SCM url: https://github.com/yeganehaym/AndroidBreadCrumb
فعال سازی امضای خودکار در Bintray
همانطور که در ابتدای مقاله گفتیم، میخواهیم کتابخانهی خود را از طریق jcenter به maven ارسال کنیم. برای همین نیاز داریم که ابتدا کتابخانهی خود را امضا کنیم. برای اینکار باید از طریق GPG یک کلید بسازیم. ساخت کلید به این شیوه، قبلا در مقالهی «ساخت کلیدهای امنیتی با GnuPG» توضیح داده شد و از تکرار آن خودداری میکنیم. تنها به ذکر این نکته بسنده میکنیم که شما باید یک کلید ساخته و آن را به سرور کلیدها ارسال کنید و سپس کلید متنی عمومی و خصوصی آن را در پروفایل bintray برگهی GPG Signing درج کنید.
این تنظیم از این پس بر روی تمامی کتابخانهها اعمال میشود.
سوال : چگونه پروژهی اندرویدی خودم را کامپایل کنم؟
فایل build.gradle پروژه را باز کنید و پلاگین bintray را به آن معرفی کنید:
dependencies { classpath 'com.android.tools.build:gradle:1.2.2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' classpath 'com.github.dcendents:android-maven-plugin:1.2' }
bintray.user=YOUR_BINTRAY_USERNAME bintray.apikey=YOUR_BINTRAY_API_KEY bintray.gpg.password=YOUR_GPG_PASSWORD
در مرحلهی بعدی خطوط زیر را بعد از 'Apply Plugin 'com.android.library اضافه کنید و اطلاعاتی که در bintray وارد کردهاید را در اینجا وارد کنید:
apply plugin: 'com.android.library' ext { bintrayRepo = 'maven' bintrayName = 'AndroidBreadCrumb' publishedGroupId = 'com.plus' libraryName = 'AndroidBreadCrumb' artifact = 'AndroidBreadCrumb' libraryDescription = 'create breadcrumb on android to show a path to user and let user to jump on them' siteUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb' gitUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb' libraryVersion = '1.0' developerId = 'yeganehaym' developerName = 'ali yeganeh.m' developerEmail = 'yeganehaym@gmail.com' licenseName = 'The Apache Software License, Version 2.0' licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' allLicenses = ["Apache-2.0"] }
apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
compile 'com.plus:AndroidBreadCrumb:1.0'
آپلود فایلها به مخزن
برای آپلود فایلهای ماژول به مخزن، ابتدا ترمینال اندروید استودیو را باز کنید و گامهای زیر را به ترتیب انجام بدهید:
گام اول: با ارسال دستور زیر از صحت کدها و منابع مطمئن میشویم:
gradlew install
BUILD SUCCESSFUL
gradlew bintrayUpload
SUCCESSFUL
حال صفحهی اختصاصی پکیجتان را چک کنید. میبینید که قسمتهایی از آن تغییر کردهاست و قسمت نسخه، به روز شده است:
و قسمت فایلها هم دیگر خالی نیست:
با اینکه کتابخانهی ما روی maven قرار گرفت، ولی هنوز نمیتوان آن را توسط jcenter استفاده کرد و باید bintray maven را با jcenter هماهنگ نماییم. در حال حاضر استفاده از این کتابخانه بدون سینک به شکل زیر است:
گریدل پروژه maven{ url 'https://dl.bintray.com/yeganehaym/maven' } گریدل ماژول dependencies { compile 'com.plus:AndroidbreadCrumb:1.0' }
برای افزودن کتابخانهی خود به سیستم jcenter با کلیک بر روی گزینهی Add to jcenter میتوانید به تیم jcenter درخواست دهید که آن را تایید کنند که بعد از درخواست حدود سه ساعت طول میکشد تا پاسخ شما را بدهند.
به این ترتیب دیگر نیازی به تعریف یک url به maven نخواهد بود.
برای دیدن این کتابخانه در صفحه jcenter به ترتیب شناسههای Group_ID.Artifact.version را دنبال کنید، یعنی برای ما میشود:
com/plus/androidbreadcrumb/1.0
نکته دوم: در صورتی که پکیج خودتان را حذف کنید، چیزی از روی jcenter حذف نمیشود. فقط به یاد داشته باشید که برای حذف آن باید ابتدا نسخههای مختلف آپلود شده را حذف کنید تا پکیج از جی سنتر هم حذف شود.
در این مرحله قصد داریم که این کتابخانه را بر روی mavenCentral هم داشته باشیم. اگر قصدش را ندارید از اینجا به بعد را نیازی نیست انجام بدهید و برای اینکار لازم است همهی مراحل بالا انجام گرفته باشد.
قبل از اینکه این عمل ارسال انجام گیرد، باید دو عمل زیر از قبل صورت گرفته باشند:
- پکیج شما در jcenter تایید شده باشد.
- با مخزن شما در sonatype موافقت شده باشد.
در صورتیکه دو مرحلهی بالا صورت گرفته باشند، در صفحهی پکیج اختصاصی، بر روی گزینهی mavenCentral کلیک کنید:
پس از آن باید نام کاربری و کلمهی عبورتان را در SonaType، وارد کنید و گزینهی sync را بفشارید:
در صورتیکه پیام موفقیت در سینک را بدهد، پکیج شما منتقل شدهاست. در غیر این صورت خطای آن را اعلام میکند و باید برای رفع آن تلاش کنید تا خطاها از بین بروند. برای اینکه بتوانید این پکیج را در لیست mavenCentral ببینید، مثل همان چیزی که در بالاتر گفته شد، شناسهی گریدل را دنبال کنید.