اشتراکها
معرفی Nerd Fonts
مطابق توضیحات این صفحه ، اگر نیاز به پشتیبانی خاصی از تیم مربوطه داشته باشید، نیاز است هزینه کرده و مثلا در ایمیل خودتون Paypal transaction number را نیز ارسال کنید تا به شما پاسخ دهند.
فرض کنید قصد دارید خاصیت Id مدل مورد استفادهی در یک View را رمزنگاری کنید تا در سمت کلاینت به سادگی قابل تغییر نباشد. همچنین این Id زمانیکه به سمت سرور ارسال شد، به صورت خودکار رمزگشایی شود و بدون نیاز به تغییرات خاصی در کدهای متداول اکشن متدها، اطلاعات نهایی آن قابل استفاده باشند. برای این منظور در ASP.NET Core میتوان یک Action Result رمزنگاری کننده و یک Model binder رمزگشایی کننده را طراحی کرد.
نیاز به علامتگذاری خواصی که باید رمزنگاری شوند
میخواهیم خاصیت یا خاصیتهای مشخصی، از یک مدل را رمزنگاری شده به سمت کلاینت ارسال کنیم. به همین جهت ویژگی خالی زیر را به پروژه اضافه میکنیم تا از آن تنها جهت علامتگذاری این نوع خواص، استفاده کنیم:
رمزنگاری خودکار مدل خروجی از یک اکشن متد
در ادامه کدهای کامل یک ResultFilter را مشاهده میکنید که مدل ارسالی به سمت کلاینت را یافته و سپس خواصی از آنرا که با ویژگی EncryptedField مزین شدهاند، به صورت خودکار رمزنگاری میکند:
توضیحات:
- در اینجا برای رمزنگاری از IProtectionProviderService استفاده شدهاست که در بستهی DNTCommon.Web.Core تعریف شدهاست. این سرویس در پشت صحنه از سیستم Data Protection استفاده میکند.
- سپس رخداد OnResultExecuting، بازنویسی شدهاست تا بتوان به مدل ارسالی به سمت کلاینت، پیش از ارسال نهایی آن، دسترسی یافت.
- context.Result میتواند از نوع PageResult صفحات Razor باشد و یا از نوع ViewResult مدلهای متداول Viewهای پروژههای MVC و یا از نوع ObjectResult که مرتبط است به پروژههای Web Api بدون هیچ نوع View سمت سروری. هر کدام از این نوعها، دارای خاصیت مدل هستند که در اینجا قصد بررسی آنرا داریم.
- پس از مشخص شدن شیء Model، اکنون حلقهای را بر روی خواص آن تشکیل داده و خواصی را که دارای ویژگی EncryptedFieldAttribute هستند، یافته و آنها را رمزنگاری میکنیم.
روش اعمال این فیلتر باید به صورت سراسری باشد:
از این پس مدلهای تمام خروجیهای ارسالی به سمت کلاینت، بررسی شده و در صورت لزوم، خواص آنها رمزنگاری میشود.
رمزگشایی خودکار مدل دریافتی از سمت کلاینت
تا اینجا موفق شدیم خواص ویژهای از مدلها را رمزنگاری کنیم. مرحلهی بعد، رمزگشایی خودکار این اطلاعات در سمت سرور است. به همین جهت نیاز داریم تا در سیستم Model Binding پیشفرض ASP.NET Core مداخله کرده و منطق سفارشی خود را تزریق کنیم. بنابراین در ابتدا یک IModelBinderProvider سفارشی را تهیه میکنیم تا در صورتیکه خاصیت جاری در حال بررسی توسط سیستم Model Binding دارای ویژگی EncryptedFieldAttribute بود، از EncryptedFieldModelBinder برای پردازش آن استفاده کند:
که این EncryptedFieldModelBinder به صورت زیر تعریف میشود:
در اینجا مقدار ارسالی به سمت سرور به صورت یک رشته دریافت شده و سپس رمزگشایی میشود و بجای مقدار فعلی خاصیت، مورد استفاده قرار میگیرد. به این ترتیب دیگر نیازی به تغییر کدهای اکشن متدها برای رمزگشایی اطلاعات نیست.
پس از این تعاریف نیاز است EncryptedFieldModelBinderProvider را به صورت زیر به سیستم معرفی کرد:
یک مثال
فرض کنید مدلهای زیر تعریف شدهاند:
که بعضی از خواص آنها با ویژگی EncryptedField مزین شدهاند.
اکنون کنترلر زیر زمانیکه رندر شود، View متناظر با اکشن متد Index آن، یکسری لینک را به اکشن متد Details، جهت مشاهدهی جزئیات محصول، تولید میکند. همچنین اکشن متد Products آن هم فقط یک خروجی JSON را به همراه دارد:
کدهای View اکشن متد Index به صورت زیر است:
در ادامه اگر برنامه را اجرا کنیم، میتوان مشاهده کرد که تمام asp-route-idها که به خاصیت ویژهی Id اشاره میکنند، به صورت خودکار رمزنگاری شدهاند:
و اگر یکی از لینکها را درخواست کنیم، خروجی model.Id، به صورت معمولی و رمزگشایی شدهای مشاهده میشود (این خروجی یک رشتهاست که هیچ ویژگی خاصی به آن اعمال نشدهاست. به همین جهت، اینبار این خروجی معمولی مشاهده میشود). هدف از اکشن متد Details، نمایش رمزگشایی خودکار اطلاعات است.
و یا اگر اکشن متدی که همانند اکشن متدهای Web API، فقط یک شیء JSON را باز میگرداند، فراخوانی کنیم نیز میتوان به خروجی رمزنگاری شدهی زیر رسید:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EncryptedModelBinder.zip
نیاز به علامتگذاری خواصی که باید رمزنگاری شوند
میخواهیم خاصیت یا خاصیتهای مشخصی، از یک مدل را رمزنگاری شده به سمت کلاینت ارسال کنیم. به همین جهت ویژگی خالی زیر را به پروژه اضافه میکنیم تا از آن تنها جهت علامتگذاری این نوع خواص، استفاده کنیم:
using System; namespace EncryptedModelBinder.Utils { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class EncryptedFieldAttribute : Attribute { } }
رمزنگاری خودکار مدل خروجی از یک اکشن متد
در ادامه کدهای کامل یک ResultFilter را مشاهده میکنید که مدل ارسالی به سمت کلاینت را یافته و سپس خواصی از آنرا که با ویژگی EncryptedField مزین شدهاند، به صورت خودکار رمزنگاری میکند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldResultFilter : ResultFilterAttribute { private readonly IProtectionProviderService _protectionProviderService; private readonly ILogger<EncryptedFieldResultFilter> _logger; private readonly ConcurrentDictionary<Type, bool> _modelsWithEncryptedFieldAttributes = new ConcurrentDictionary<Type, bool>(); public EncryptedFieldResultFilter( IProtectionProviderService protectionProviderService, ILogger<EncryptedFieldResultFilter> logger) { _protectionProviderService = protectionProviderService; _logger = logger; } public override void OnResultExecuting(ResultExecutingContext context) { var model = context.Result switch { PageResult pageResult => pageResult.Model, // For Razor pages ViewResult viewResult => viewResult.Model, // For MVC Views ObjectResult objectResult => objectResult.Value, // For Web API results _ => null }; if (model is null) { return; } if (typeof(IEnumerable).IsAssignableFrom(model.GetType())) { foreach (var item in model as IEnumerable) { encryptProperties(item); } } else { encryptProperties(model); } } private void encryptProperties(object model) { var modelType = model.GetType(); if (_modelsWithEncryptedFieldAttributes.TryGetValue(modelType, out var hasEncryptedFieldAttribute) && !hasEncryptedFieldAttribute) { return; } foreach (var property in modelType.GetProperties()) { var attribute = property.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { continue; } hasEncryptedFieldAttribute = true; var value = property.GetValue(model); if (value is null) { continue; } if (value.GetType() != typeof(string)) { _logger.LogWarning($"[EncryptedField] should be applied to `string` proprties, But type of `{property.DeclaringType}.{property.Name}` is `{property.PropertyType}`."); continue; } var encryptedData = _protectionProviderService.Encrypt(value.ToString()); property.SetValue(model, encryptedData); } _modelsWithEncryptedFieldAttributes.TryAdd(modelType, hasEncryptedFieldAttribute); } } }
- در اینجا برای رمزنگاری از IProtectionProviderService استفاده شدهاست که در بستهی DNTCommon.Web.Core تعریف شدهاست. این سرویس در پشت صحنه از سیستم Data Protection استفاده میکند.
- سپس رخداد OnResultExecuting، بازنویسی شدهاست تا بتوان به مدل ارسالی به سمت کلاینت، پیش از ارسال نهایی آن، دسترسی یافت.
- context.Result میتواند از نوع PageResult صفحات Razor باشد و یا از نوع ViewResult مدلهای متداول Viewهای پروژههای MVC و یا از نوع ObjectResult که مرتبط است به پروژههای Web Api بدون هیچ نوع View سمت سروری. هر کدام از این نوعها، دارای خاصیت مدل هستند که در اینجا قصد بررسی آنرا داریم.
- پس از مشخص شدن شیء Model، اکنون حلقهای را بر روی خواص آن تشکیل داده و خواصی را که دارای ویژگی EncryptedFieldAttribute هستند، یافته و آنها را رمزنگاری میکنیم.
روش اعمال این فیلتر باید به صورت سراسری باشد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
رمزگشایی خودکار مدل دریافتی از سمت کلاینت
تا اینجا موفق شدیم خواص ویژهای از مدلها را رمزنگاری کنیم. مرحلهی بعد، رمزگشایی خودکار این اطلاعات در سمت سرور است. به همین جهت نیاز داریم تا در سیستم Model Binding پیشفرض ASP.NET Core مداخله کرده و منطق سفارشی خود را تزریق کنیم. بنابراین در ابتدا یک IModelBinderProvider سفارشی را تهیه میکنیم تا در صورتیکه خاصیت جاری در حال بررسی توسط سیستم Model Binding دارای ویژگی EncryptedFieldAttribute بود، از EncryptedFieldModelBinder برای پردازش آن استفاده کند:
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Metadata.IsComplexType) { return null; } var propName = context.Metadata.PropertyName; if (string.IsNullOrWhiteSpace(propName)) { return null; } var propInfo = context.Metadata.ContainerType.GetProperty(propName); if (propInfo == null) { return null; } var attribute = propInfo.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault(); if (attribute == null) { return null; } return new BinderTypeModelBinder(typeof(EncryptedFieldModelBinder)); } } }
namespace EncryptedModelBinder.Utils { public class EncryptedFieldModelBinder : IModelBinder { private readonly IProtectionProviderService _protectionProviderService; public EncryptedFieldModelBinder(IProtectionProviderService protectionProviderService) { _protectionProviderService = protectionProviderService; } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var logger = bindingContext.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>(); var fallbackBinder = new SimpleTypeModelBinder(bindingContext.ModelType, logger); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == ValueProviderResult.None) { return fallbackBinder.BindModelAsync(bindingContext); } bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); var valueAsString = valueProviderResult.FirstValue; if (string.IsNullOrWhiteSpace(valueAsString)) { return fallbackBinder.BindModelAsync(bindingContext); } var decryptedResult = _protectionProviderService.Decrypt(valueAsString); bindingContext.Result = ModelBindingResult.Success(decryptedResult); return Task.CompletedTask; } } }
پس از این تعاریف نیاز است EncryptedFieldModelBinderProvider را به صورت زیر به سیستم معرفی کرد:
namespace EncryptedModelBinder { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDNTCommonWeb(); services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new EncryptedFieldModelBinderProvider()); options.Filters.Add(typeof(EncryptedFieldResultFilter)); }); }
یک مثال
فرض کنید مدلهای زیر تعریف شدهاند:
namespace EncryptedModelBinder.Models { public class ProductInputModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } } namespace EncryptedModelBinder.Models { public class ProductViewModel { [EncryptedField] public string Id { get; set; } [EncryptedField] public int Price { get; set; } public string Name { get; set; } } }
اکنون کنترلر زیر زمانیکه رندر شود، View متناظر با اکشن متد Index آن، یکسری لینک را به اکشن متد Details، جهت مشاهدهی جزئیات محصول، تولید میکند. همچنین اکشن متد Products آن هم فقط یک خروجی JSON را به همراه دارد:
namespace EncryptedModelBinder.Controllers { public class HomeController : Controller { public IActionResult Index() { var model = getProducts(); return View(model); } public ActionResult<string> Details(ProductInputModel model) { return model.Id; } public ActionResult<List<ProductViewModel>> Products() { return getProducts(); } private static List<ProductViewModel> getProducts() { return new List<ProductViewModel> { new ProductViewModel { Id = "1", Name = "Product 1"}, new ProductViewModel { Id = "2", Name = "Product 2"}, new ProductViewModel { Id = "3", Name = "Product 3"} }; } } }
@model List<ProductViewModel> <h3>Home</h3> <ul> @foreach (var item in Model) { <li><a asp-action="Details" asp-route-id="@item.Id">@item.Name</a></li> } </ul>
و اگر یکی از لینکها را درخواست کنیم، خروجی model.Id، به صورت معمولی و رمزگشایی شدهای مشاهده میشود (این خروجی یک رشتهاست که هیچ ویژگی خاصی به آن اعمال نشدهاست. به همین جهت، اینبار این خروجی معمولی مشاهده میشود). هدف از اکشن متد Details، نمایش رمزگشایی خودکار اطلاعات است.
و یا اگر اکشن متدی که همانند اکشن متدهای Web API، فقط یک شیء JSON را باز میگرداند، فراخوانی کنیم نیز میتوان به خروجی رمزنگاری شدهی زیر رسید:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EncryptedModelBinder.zip
VSCode یکی از بهترین ادیتورهایی است که از آن میتوان برای توسعهی برنامههای Angular استفاده کرد و در این بین افزونههای ویژهای جهت کار با Angular برای آن تدارک دیده شدهاند که در ادامه تعدادی از مهمترینهای آنها را بررسی میکنیم.
Angular Essentials
این افزونه گروهی از مهمترین افزونههای موجود را به صورت بسته بندی شده ارائه میدهد و با نصب آن، تعدادی از افزونههایی را که در ادامه نامبرده خواهند شد، به صورت یکجا و خودکار دریافت خواهید کرد.
Angular Language Service
نگارشهای اخیر Angular به همراه یک سرویس زبان نیز میباشند که به ادیتورهای مختلف این امکان را میدهد تا توسط این ویژگی بتوانند قابلیتهای ویرایشی بهتری را جهت برنامههای Angular ارائه کنند. برای مثال ویرایش مطلوب قالبهای کامپوننتهای Angular و استفادهی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمیشود. اکنون به کمک سرویس زبان Angular و افزونهی ویژهی آن برای VSCode که توسط تیم اصلی Angular توسعه یافتهاست، امکان ویرایش غنی قالبهای HTML ایی آن فراهم شدهاست. این افزونه یک چنین قابلیتهایی را فراهم میکند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:
ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شدهی در قالب.
د) Quick info که با نزدیک کردن اشارهگر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش میدهد.
angular2-inline
علاوه بر افزونهی سرویس زبانهای Angular، این افزونه نیز قابلیت درک قالبهای inline کامپوننتها را داشته و به همراه syntax highlighting و همچنین Intellisense است.
Auto Import
حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونهی Auto Import با بررسی ماژولهای موجود و فراهم آوردن Intellisense ایی بر اساس آنها، اینکار را سادهتر میکند:
بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.
TSLint
این افزونه ابزار TSLint را با VSCode یکپارچه میکند. بنابراین نیاز است پیش از نصب این افزونه، وابستگیهای ذیل را نیز به صورت سراسری نصب کرد:
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونههایی مانند ریشارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه میدهد.
تعدادی از امکانات آنرا پس از نصب، با فشردن دکمهی F1 میتوان مشاهده کرد:
برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شوندهی توسط این افزونه را فراهم میکند و اگر برنامهی خود را توسط Angular CLI ایجاد کردهاید، این فایل هم اکنون در ریشهی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامهی جدید» بیشتر توضیح داده شدهاست و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچهای مشاهده خواهید کرد.
Angular v4 TypeScript Snippets
سیستم کار VSCode مبتنی بر ایجاد فایلهای خالی است و مفهوم قالبهای از پیش آمادهی فایلها در آن وجود ندارد. اما با کمک Code Snippets میتوان این خلاء را پر کرد. افزونهی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شدهاست و زمانیکه حروف -a یا -rx را در صفحه تایپ میکنید، منویی ظاهر خواهد شد که توسط آن میتوان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژههای Angular را تهیه کرد.
Path Intellisense
این افزونه مسیر فایلهای موجود را به صورت یک Intellisense ارائه میکند و به این صورت به سادگی میتوان مسیرهای اسکریپتها و یا شیوهنامهها را در ادیتور انتخاب و وارد کرد.
Angular Essentials
این افزونه گروهی از مهمترین افزونههای موجود را به صورت بسته بندی شده ارائه میدهد و با نصب آن، تعدادی از افزونههایی را که در ادامه نامبرده خواهند شد، به صورت یکجا و خودکار دریافت خواهید کرد.
Angular Language Service
نگارشهای اخیر Angular به همراه یک سرویس زبان نیز میباشند که به ادیتورهای مختلف این امکان را میدهد تا توسط این ویژگی بتوانند قابلیتهای ویرایشی بهتری را جهت برنامههای Angular ارائه کنند. برای مثال ویرایش مطلوب قالبهای کامپوننتهای Angular و استفادهی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمیشود. اکنون به کمک سرویس زبان Angular و افزونهی ویژهی آن برای VSCode که توسط تیم اصلی Angular توسعه یافتهاست، امکان ویرایش غنی قالبهای HTML ایی آن فراهم شدهاست. این افزونه یک چنین قابلیتهایی را فراهم میکند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:
ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شدهی در قالب.
د) Quick info که با نزدیک کردن اشارهگر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش میدهد.
angular2-inline
علاوه بر افزونهی سرویس زبانهای Angular، این افزونه نیز قابلیت درک قالبهای inline کامپوننتها را داشته و به همراه syntax highlighting و همچنین Intellisense است.
Auto Import
حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونهی Auto Import با بررسی ماژولهای موجود و فراهم آوردن Intellisense ایی بر اساس آنها، اینکار را سادهتر میکند:
بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.
TSLint
این افزونه ابزار TSLint را با VSCode یکپارچه میکند. بنابراین نیاز است پیش از نصب این افزونه، وابستگیهای ذیل را نیز به صورت سراسری نصب کرد:
> npm install -g tslint typescript
تعدادی از امکانات آنرا پس از نصب، با فشردن دکمهی F1 میتوان مشاهده کرد:
برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شوندهی توسط این افزونه را فراهم میکند و اگر برنامهی خود را توسط Angular CLI ایجاد کردهاید، این فایل هم اکنون در ریشهی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامهی جدید» بیشتر توضیح داده شدهاست و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچهای مشاهده خواهید کرد.
Angular v4 TypeScript Snippets
سیستم کار VSCode مبتنی بر ایجاد فایلهای خالی است و مفهوم قالبهای از پیش آمادهی فایلها در آن وجود ندارد. اما با کمک Code Snippets میتوان این خلاء را پر کرد. افزونهی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شدهاست و زمانیکه حروف -a یا -rx را در صفحه تایپ میکنید، منویی ظاهر خواهد شد که توسط آن میتوان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژههای Angular را تهیه کرد.
Path Intellisense
این افزونه مسیر فایلهای موجود را به صورت یک Intellisense ارائه میکند و به این صورت به سادگی میتوان مسیرهای اسکریپتها و یا شیوهنامهها را در ادیتور انتخاب و وارد کرد.
مطالب
Roslyn #5
بررسی Semantic Models
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
این ساختار که بسیار شبیه است به ساختار قدیمی <Nullable<T، منحصر به Value types نیست و Reference types را نیز شامل میشود و بیانگر این است که آیا یک Reference type، واقعا مقدار دهی شدهاست یا خیر؟
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
اگر به کدهای ادامهی بحث دقت کنید، قصد داریم مقادیر ثابت آرگومانهای Console.WriteLine را استخراج کنیم. به همین جهت در این SyntaxWalker، نوع Console و متد WriteLine آن مورد بررسی قرار گرفتهاند. اگر این نود دارای یک تک آرگومان بود، آین آرگومان استخراج شده و به لیست آرگومانهای خروجی این کلاس اضافه میشود.
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
در ادامه با استفاده از CSharpCompilation و متد GetSemanticModel آن به SemanticModel جاری دسترسی خواهیم یافت. اکنون SyntaxWalker را وارد به حرکت بر روی ریشهی syntax tree سورس کد آنالیز شده میکنیم. به این ترتیب لیست آرگومانهای متدهای Console.WriteLine بدست میآیند. سپس با فراخوانی متد model.GetConstantValue بر روی هر آرگومان دریافتی، مقادیر آنها با فرمت <Optional<T استخراج میشوند.
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
یکی از کاربردهای مهم Symbols API دریافت اطلاعات Symbols نقطهای خاص از کدها میباشد. برای مثال در محل اشارهگر ادیتور، چه Symbols ایی تعریف شدهاند و از آنها در مباحث ساخت افزونههای آنالیز کدها زیاد استفاده میشود. نمونهای از آنرا در قطعه کد فوق ملاحظه میکنید. در اینجا با استفاده از متد GetEnclosingSymbol، سعی در یافتن Symbols قرار گرفتهی در ناحیهی insideBar# کدهای فوق داریم؛ با خروجی ذیل که نام demo.exe آن از نام CSharpCompilation آن گرفته شدهاست:
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
با این خروجی:
در مثال فوق، با استفاده از Syntax Walker طراحی شده در ابتدای بحث که کار استخراج آرگومانهای متدهای Console.WriteLine را انجام میدهد، قصد داریم بررسی کنیم، هر آرگومان به چه Symbol ایی بایند شدهاست و نوعش چیست؟ برای مثال Console.WriteLine اول که از پارامتر x استفاده میکند، نوع x مورد استفادهاش چیست؟ آیا فیلد است، متغیر محلی است یا یک پارامتر؟ این اطلاعات را با استفاده از متد model.GetSymbolInfo میتوان استخراج کرد.
همانطور که از قسمت قبل بهخاطر دارید، برای دسترسی به اطلاعات semantics، نیاز به یک context مناسب که همان Compilation API است، میباشد. این context دارای اطلاعاتی مانند دسترسی به تمام نوعهای تعریف شدهی توسط کاربر و متادیتاهای ارجاعی، مانند کلاسهای پایهی دات نت فریمورک است. بنابراین پس از ایجاد وهلهای از Compilation API، کار با فراخوانی متد GetSemanticModel آن ادامه مییابد. در ادامه با مثالهایی، کاربرد این متد را بررسی خواهیم کرد.
ساختار جدید Optional
خروجیهای تعدادی از متدهای Roslyn با ساختار جدیدی به نام Optional ارائه میشوند:
public struct Optional<T> { public bool HasValue { get; } public T Value { get; } }
دریافت مقادیر ثابت Literals
فرض کنید میخواهیم مقدار ثابت ; int x = 42 را دریافت کنیم. برای اینکار ابتدا باید syntax tree آن تشکیل شود و سپس نیاز به یک سری حلقه و if و else و همچنین بررسی نال بودن بسیاری از موارد است تا به نود مقدار ثابت 42 برسیم. سپس متد GetConstantValue مربوط به GetSemanticModel را بر روی آن فراخوانی میکنیم تا به مقدار واقعی آن که ممکن است در اثر محاسبات جاری تغییر کرده باشد، برسیم.
اما روش بهتر و توصیه شده، استفاده از CSharpSyntaxWalker است که در انتهای قسمت سوم معرفی شد:
class ConsoleWriteLineWalker : CSharpSyntaxWalker { public ConsoleWriteLineWalker() { Arguments = new List<ExpressionSyntax>(); } public List<ExpressionSyntax> Arguments { get; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { var member = node.Expression as MemberAccessExpressionSyntax; var type = member?.Expression as IdentifierNameSyntax; if (type != null && type.Identifier.Text == "Console" && member.Name.Identifier.Text == "WriteLine") { if (node.ArgumentList.Arguments.Count == 1) { var arg = node.ArgumentList.Arguments.Single().Expression; Arguments.Add(arg); return; } } base.VisitInvocationExpression(node); } }
در ادامه نحوهی استفادهی از این SyntaxWalker را ملاحظه میکنید. در اینجا ابتدا سورس کدی حاوی یک سری Console.WriteLine که دارای تک آرگومانهای ثابتی هستند، تبدیل به syntax tree میشود. سپس از روی آن CSharpCompilation تولید میگردد تا بتوان به اطلاعات semantics دسترسی یافت:
static void getConstantValue() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { Console.WriteLine(3.14); Console.WriteLine(""qux""); Console.WriteLine('c'); Console.WriteLine(null); Console.WriteLine(x * 2 + 1); } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Analyze the constant argument (if any). foreach (var arg in walker.Arguments) { var val = model.GetConstantValue(arg); if (val.HasValue) { Console.WriteLine(arg + " has constant value " + (val.Value ?? "null") + " of type " + (val.Value?.GetType() ?? typeof(object))); } else { Console.WriteLine(arg + " has no constant value"); } } }
خروجی نمایش داده شدهی توسط برنامه به صورت ذیل است:
3.14 has constant value 3.14 of type System.Double "qux" has constant value qux of type System.String 'c' has constant value c of type System.Char null has constant value null of type System.Object x * 2 + 1 has no constant value
درک مفهوم Symbols
اینترفیس ISymbol در Roslyn، ریشهی تمام Symbolهای مختلف مدل سازی شدهی در آن است که تعدادی از آنها را در تصویر ذیل مشاهده میکنید:
API کار با Symbols بسیار شبیه به API کار با Reflection است با این تفاوت که در زمان آنالیز کدها رخ میدهد و نه در زمان اجرای برنامه. همچنین در Symbols API امکان دسترسی به اطلاعاتی مانند locals, labels و امثال آن نیز وجود دارد که با استفاده از Reflection زمان اجرای برنامه قابل دسترسی نیستند. برای مثال فضاهای نام در Reflection صرفا به صورت رشتهای، با دات جدا شده از نوعهای آنالیز شدهی توسط آن است؛ اما در اینجا مطابق تصویر فوق، یک اینترفیس مجزای خاص خود را دارد. جهت سهولت کار کردن با Symbols، الگوی Visitor با معرفی کلاس پایهی SymbolVisitor نیز پیش بینی شدهاست.
static void workingWithSymbols() { // Get the syntax tree. var code = @" using System; class Foo { void Bar(int x) { // #insideBar } } class Qux { protected int Baz { get; set; } } "; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse enclosing symbol hierarchy. var cursor = code.IndexOf("#insideBar"); var barSymbol = model.GetEnclosingSymbol(cursor); for (var symbol = barSymbol; symbol != null; symbol = symbol.ContainingSymbol) { Console.WriteLine(symbol); } // Analyze accessibility of Baz inside Bar. var bazProp = ((CompilationUnitSyntax)root) .Members.OfType<ClassDeclarationSyntax>() .Single(m => m.Identifier.Text == "Qux") .Members.OfType<PropertyDeclarationSyntax>() .Single(); var bazSymbol = model.GetDeclaredSymbol(bazProp); var canAccess = model.IsAccessible(cursor, bazSymbol); }
Foo.Bar(int) Foo <global namespace> Demo.exe Demo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
همچنین در ادامهی کد، توسط متد IsAccessible قصد داریم بررسی کنیم آیا Symbol قرار گرفته در محل کرسر، دسترسی به خاصیت protected کلاس Qux را دارد یا خیر؟ که پاسخ آن خیر است.
آشنایی با Binding symbols
یکی از مراحل کامپایل کد، binding نام دارد و در این مرحله است که اطلاعات Symbolic هر نود از Syntax tree دریافت میشود. برای مثال در اینجا مشخص میشود که این x، آیا یک متغیر محلی است، یا یک فیلد و یا یک خاصیت؟
مثال ذیل بسیار شبیه است به مثال getConstantValue ابتدای بحث، با این تفاوت که در حلقهی آخر کار از متد GetSymbolInfo استفاده شدهاست:
static void bindingSymbols() { // Get the syntax tree. var code = @" using System; class Foo { private int y; void Bar(int x) { Console.WriteLine(x); Console.WriteLine(y); int z = 42; Console.WriteLine(z); Console.WriteLine(a); } }"; var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); // Get the semantic model from the compilation. var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib); var model = comp.GetSemanticModel(tree); // Traverse the tree. var walker = new ConsoleWriteLineWalker(); walker.Visit(root); // Bind the arguments. foreach (var arg in walker.Arguments) { var symbol = model.GetSymbolInfo(arg); if (symbol.Symbol != null) { Console.WriteLine(arg + " is bound to " + symbol.Symbol + " of type " + symbol.Symbol.Kind); } else { Console.WriteLine(arg + " could not be bound"); } } }
x is bound to int of type Parameter y is bound to Foo.y of type Field z is bound to z of type Local a could not be bound
یک نکتهی تکمیلی: روش نگاشت خواص سایهای توسط AutoMapper
public static class ShadowProperty { public static readonly string CreatedAt = nameof(CreatedAt); } public class Student { public int Id { get; set; } public string Name { get; set; } } public class StudentDto { public int Id { get; set; } public string Name { get; set; } public DateTimeOffset Created { get; set; } } public class StudentMappingProfile : Profile { public StudentMappingProfile() { CreateMap<Student, StudentDto>() .ForMember( dest => dest.Created, opt => opt.MapFrom(src => EF.Property<DateTimeOffset>(src, ShadowProperty.CreatedAt)) ); } }
var students = context.Students.ProjectTo<StudentDto>(configuration: mapper.ConfigurationProvider).ToList();
اشتراکها