نظرات مطالب
پشتیبانی توکار از انجام کارهای پس‌زمینه در ASP.NET Core 2x
ارتقاء به NET Core 3.0.: پشتیبانی از ایجاد سرویس‌های پس‌زمینه

یکی از تغییرات مهم قالب ایجاد پروژه‌های ASP.NET Core 3.0، تغییر فایل program.cs آن است که در آن از یک Generic Host بجای روش قبلی Web Host، استفاده شده‌است. علت آن فراهم آوردن امکان استفاده‌ی از قابلیت‌هایی مانند تزریق وابستگی‌ها، logging، تنظیمات برنامه و غیره، در برنامه‌های غیر وب نیز می‌باشد. یکی از این انواع برنامه‌ها، سرویس‌های پس‌زمینه‌ی غیر HTTP هستند. به این ترتیب می‌توان برنامه‌ای شبیه به یک برنامه‌ی وب ASP.NET Core را ایجاد کرد که تنها کارش اجرای سرویس‌های غیر وبی است؛ اما به تمام امکانات و زیر ساخت‌های ASP.NET Core دسترسی دارد.
برای ایجاد این نوع برنامه‌ها در NET Core 3x. می‌توانید دستور زیر را در پوشه‌ی خالی که ایجاد کرده‌اید، اجرا کنید:
dotnet new worker
ساختار برنامه‌ای که توسط این دستور تولید می‌شود به صورت زیر است که بسیار شبیه به ساختار یک برنامه‌ی ASP.NET Core است:
appsettings.Development.json
appsettings.json
MyWorkerServiceApp.csproj
Program.cs
Worker.cs

- فایل csproj آن دارای این محتوا است:
<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UserSecretsId>dotnet-MyWorkerServiceApp-B76DB08E-FFBB-4AD1-89B5-93BF483D1BD0</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0-preview8.19405.4" />
  </ItemGroup>
</Project>
در آن ویژگی Sdk به Microsoft.NET.Sdk.Worker اشاره می‌کند و همچنین از بسته‌ی Microsoft.Extensions.Hosting استفاده شده‌است.

- محتوای فایل Program.cs آن بسیار آشنا است و دقیقا کپی همان فایلی است که در برنامه‌های ASP.NET Core 3x حضور دارد:
namespace MyWorkerServiceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}
در اینجا یک Generic host را بجای Web host قالب‌های پیشین فایل Program.cs ملاحظه می‌کنید که هدف اصلی آن، عمومی کردن این قالب، برای استفاده‌ی از آن در برنامه‌های غیر وبی نیز می‌باشد.
در متد ConfigureServices، انواع اقسام سرویس‌ها را منجمله یک HostedService که در مطلب جاری به آن پرداخته شده، می‌توان افزود. سرویس Worker ای که در اینجا به آن ارجاعی وجود دارد، به صورت زیر تعریف شده‌است:
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
با ساختار این کلاس نیز آشنا هستید و موضوع اصلی مطلب جاری است.


یک نکته‌ی تکمیلی: روش تبدیل کردن یک BackgroundService به یک Windows Service

اگر برنامه‌ی NET Core. شما در ویندوز اجرا می‌شود، می‌توانید این برنامه‌ی BackgroundService را به یک سرویس ویندوز NT نیز تبدیل کنید. برای اینکار ابتدا بسته‌ی نیوگت Microsoft.Extensions.Hosting.WindowsServices را به پروژه اضافه کنید. سپس جائیکه CreateHostBuilder صورت می‌گیرد، متد UseWindowsService را فراخوانی کنید:
public static IHostBuilder CreateHostBuilder(string[] args) => 
            Host.CreateDefaultBuilder(args) 
                .UseWindowsService() 
                .ConfigureServices((hostContext, services) => 
                { 
                   //services.AddHttpClient(); 
                   services.AddHostedService<Worker>(); 
                });
تا اینجا هنوز هم برنامه، شبیه به یک برنامه‌ی کنسول دات نت Core قابل اجرا و دیباگ است. اما اگر خواستید آن‌را به صورت یک سرویس ویندوز نیز نصب کنید، تنها کافی است از دستور زیر استفاده کنید:
 cs create WorkerServiceDemo binPath=C:\Path\To\WorkerServiceDemo.exe

البته برای لینوکس نیز می‌توان از UseSystemd استفاده کرد که نیاز به نصب بسته‌ی Microsoft.Extensions.Hosting.Systemd را دارد:
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSystemd()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
        });
مطالب
Strong Name
نام قوی (Strong Name یا به‌صورت مخفف SN) تکنولوژی‌ای است که با ورود دانت نت معرفی شده و امکانات متنوعی را در زمینه حفاظت از هویت اسمبلی فراهم کرده است. اما بسیاری از برنامه‌نویسان به اشتباه آن را به‌عنوان ابزاری برای فعال‌سازی امنیت می‌پندارند، درصورتی‌که «نام قوی» درواقع یک تکنولوژی تعیین «هویتِ منحصربه‌فرد» اسمبلی‌ها است. یک نام قوی حاوی مجموعه‌ای از مشخصات یک اسمبلی (شامل نام ساده، نسخه و داده‌های کالچر (culture) آن در صورت وجود) به‌همراه یک کلید عمومی و یک امضای دیجیتال است. در زیر یک نمونه از یک اسمبلی دارای نام قوی را مشاهده می‌کنید:
System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
این نام با استفاده از داده‌های موجود در فایل اصلی یک اسمبلی و نیز یک کلید خصوصی تولید می‌شود. (فایل اصلی اسمبلی فایلی است که حاوی مانیفست اسمبلی است که این مانیفست خود شامل عنوان و هش‌کدهای تمام فایل‌هایی است که اسمبلی را می‌سازند. دات نت از MultiFile Assembly پشتیبانی می‌کند. برای مدیریت این نوع از اسمبلی‌ها می‌توان از (Assembly Linker (al.exe استفاده کرد. البته درحال حاضر امکان توسعه این نوع از اسمبلی‌ها در ویژوال استودیو موجود نیست.) در sdkهای مایکروسافت ابزارهایی برای تولید نام‌های قوی برای اسمبلی‌ها وجود دارد که در ادامه در مورد نحوه استفاده از یک مورد از آن‌ها توضیح داده خواهد شد.
اسمبلی‌هایی که نام‌های قوی یکسانی دارند همانند و یکسان هستند. با اختصاص دادن یک نام قوی به یک اسمبلی می‌توان اطمینان حاصل کرد که نام آن منحصربه‌فرد خواهد شد. به‌طور کلی نام‌های قوی نیازمندی‌های زیر را برطرف می‌کنند:
- نام‌های قوی منحصربه‌فرد بودن نام یک اسمبلی را براساس جفت‌کلیدهای یکتا فراهم می‌کنند. هیچ‌کس دیگری امکان تولید همان اسمبلی‌ای را که شما تولید کرده‌اید ندارد، زیرا اسمبلی‌ای که با یک کلید خصوصی تولید شده است نسبت به اسمبلی دیگری که با یک کلید خصوصی دیگر تولید شده است نام متفاوتی خواهد داشت چون کلید عمومی متناظر با این کلید خصوصی بخشی از نام قوی نهایی تولید شده خواهد بود.
- نام‌های قوی از خط تولید نسخه‌های یک اسمبلی محافظت می‌کنند. یک نام قوی اطمینان می‌دهد تا شخص دیگری نتواند نسخه دیگری از اسمبلی شما را تولید کند. مصرف‌کنندگان می‌توانند مطمئن باشند که نسخه‌ای از اسمبلی را که بارگذاری می‌کنند از همان توزیع‌کننده اسمبلی می‌آید که این نسخه از اسمبلی را تولید کرده است.
- نام‌های قوی بررسی هویت مستحکمی را فراهم می‌کنند. عبور از دروازه امنیتی دات نت فریمورک نشان‌دهنده این است که محتوای اسمبلی پس از تولید آن تغییر نکرده است.
هنگامی‌که به یک اسمبلیِ دارای نام قوی در اسمبلی دیگری ریفرنس داده می‌شود، تا زمانی که به اسمبلی مقصد نیز یک نام قوی داده نشود نمی‌توان در نهایت از مزایای یک نام قوی بهره برد. درواقع در دنیای دات نت به اسمبلی‌های دارای نام قوی تنها می‌توان اسمبلی‌هایی ریفرنس داد که خود نیز دارای نام قوی هستند.
نام قوی یک تکنولوژی براساس اصول کریپتوگرافی و امضاهای دیجیتال است که ایده پایه‌ای آن را می‌توان در تصویر زیر دید:

برای استفاده از این تکنولوژی ابتدا نیاز است تا یک جفت‌کلید عمومی/خصوصی (توسط ادمین، منبع گواهی‌نامه‌ها، یک بانک یا یک ابزار خاص) فراهم شود تا از آن برای اینکریپشن استفاده شود. سپس داده‌های موردنظر (هر داده کلی که قصد ارسال و توزیع آن را داریم مثل یک اسمبلی) با استفاده از یک الگوریتم هش‌کردن (مثل 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

 مقدار نمایش داده در انتهای تصویر فوق به‌عنوان «توکِن کلید عمومی» (Public key Token) درواقع 8 بایت پایانی کد هش‌شده کریپتوگرافیِ محاسبه‌شده از کلید عمومی است. چون خود کلید عمومی همان‌طور که مشاهده می‌شود بسیار طولانی است، دات‌نت‌فریمورک معمولا از این توکِن برای نمایش آن و ریفرنس دادن اسمبلی‌ها استفاده می‌کند. نیازی نیست تا راز این کلیدها توسط توسعه‌دهنده حفظ شود! پس از نام‌گذاری قوی اسمبلی (که در ادامه توضیح داده می‌شود) کامپایلر با استفاده از کلید خصوصی فراهم شده یک امضای دیجیتالی (یک کد اینکریپت شده) با استفاده از داده‌های «مانیفست اسمبلی» تولید می‌کند. در ادامه کامپایلر این «امضای دیجیتال» و «کلید عمومی» را درون اسمبلی قرار می‌دهد تا مصرف‌کننده‌های اسمبلی بتوانند این امضای دیجیتال را تایید کنند. حفظ کردن «کلید خصوصی» بسیار مهم است! اگر کسی به کلید خصوصی اسمبلی دست یابد می‌تواند با استفاده از آن نسخه‌ای تغییریافته از اسمبلی را امضا کرده و در اختیار مصرف‌کنندگان قرار دهد. مصرف‌کنندگان نیز بدون اینکه متوجه شوند می‌توانند از این نسخه تغییر یافته با همان توکِن کلید عمومی که در اختیار دارند استفاده کنند. درحال حاضر روشی برای فهمیدن این تغییر وجود ندارد. اگر کلید خصوصی لو رفت، باید یک جفت کلید دیگر تولید و با استفاده از کلید خصوصی جدید اسمبلی را دوباره امضا کرد و در اختیار مصرف‌کنندگان قرار داد. هم‌چنین باید مشتریانِ اسمبلی را از این تغییر آگاه ساخت و کلید عمومی مورد اطمینان را در اختیار آن‌ها قرار داد.
نکته: معمولا گروه کوچکی از افراد مورد اطمینان (که دسترسی امضای اسمبلی را دارند: signing authority) مسئولیت کلیدهای نامگذاری قوی یک شرکت را بر عهده دارند و برای امضای تمام اسمبلی‌ها قبل از ریلیز نهایی آن‌ها مسئول هستند.
قابلیت امضای تاخیری اسمبلی (که در ادامه بحث می‌شود) تسهیلاتی را برای بهره‌برداری راحت‌تر از این روش و جلوگیری از توزیع کلیدهای خصوصی میان تمام توسعه‌دهندگان را فراهم می‌کند.
یکی از روش‌هایی که sn برای افزایش امنیت کلیدها ارائه می‌دهد، استفاده از مخزن کلید CSP است. پس از تولید فایل حاوی جفت کلید، می‌توان با استفاده از دستور زیر این کلیدها را درون CSP با نام MyStrongNameKeys ذخیره کرد:
sn -i MyKeys.snk MyStrongNameKeys
سپس می‌توان فایل حاوی جفت کلید را حذف کرد.

نکته مهمی که درباره مخازن کلید CSP باید بدان اشاره کرد این است که این مخازن شامل مخازن تعریف‌شده توسط «کاربر» و نیز مخازن «سیستمی» است. سیستم امنیتی ویندوز به کابران اجازه دسترسی به مخازنی غیر از مخازن خودشان و مخازن سیستمی را نمی‌دهد. برنامه sn به‌صورت پیش‌فرض کلیدها را درون مخازن سیستمی ذخیره می‌کند. بنابراین هر کسی که بتواند به سیستم لاگین کند و نیز از نام مخزن مربوطه آگاه باشد، به‌راحتی می‌تواند اسمبلی شما را امضا کند! برای اینکه ابزار sn کلیدها را در مخازن کاربری ذخیره کند باید از دستور زیر استفاده کرد:
sn –m n
برای برگرداند تنظیم به ذخیره در مخازن سیستمی نیز باید از دستور زیر استفاده کرد:
sn –m y

 برای حذف کلیدها از مخزن می‌توان از دستور زیر استفاده کرد:
sn -d MyStrongNameKeys

2. نام‌گذاری قوی یک اسمبلی
نام‌گذاری قوی یک اسمبلی به دلایل زیادی انجام می‌شود:
- برای اینکه اسمبلی شناسه‌ای منحصربه‌فرد داشته باشد، تا کاربران بتوانند مجوزهای ویژه‌ای را در حین تنظیم سیاست‌های امنیتی دسترسی به کد اعمال کنند.
- تا اسمبلی را نتوان تغییر داده و سپس به عنوان اسمبلی اصلی توزیع نمود.
- تا اسمبلی بتواند نسخه‌گذاری (Versioning) و سیاست‌های نسخه‌گذاری را پشتیبانی کند.
- تا بتوان اسمبلی را در GAC (همان Global Assembly Cache که در مسیر %windir%\assembly قرار دارد) ذخیره کرده و آن را بین چند اپلیکیشن به اشتراک گذاشت.
برای نام‌گذاری قوی اسمبلی با استفاده از خط فرمان کامپایلر #C باید از سوییچهای keyfile/ و یا keycontainer/ استفاده کنید.
 

csc /keyfile:d:\mykeys.snk /out:"C:\Projects\ClassLibrary1\Class1.exe" "C:\Projects\ClassLibrary1\Class1.cs" 

نکته: برای استفاده از این ویژگی در ویژوال استودیو، باید در تب Signing در تنظیمات پروژه گزینه Sign the Assembly را انتخاب کرد. سپس می‌توان فایل حاوی جفت کلیدهای تولیدشده را انتخاب یا فایل جدیدی تولید کرد. البته ویژوال استودیو تا نسخه 2010 امکانی جهت استفاده از مخازن CSP را ندارد.

روش ساده دیگر استفاده از attributeهای سطح اسمبلی است:
[assembly:AssemblyKeyFileAttribute("MyKeys.snk")]
3. بررسی اینکه آیا یک اسمبلی قوی-نام‌گذاری‌شده تغییر یافته یا خیر
زمانی‌که CLR در زمان اجرا یک اسمبلی قوی-نام‌گذاری‌شده را بارگذاری می‌کند:
-ابتدا با استفاده از کلید عمومی (که در خود اسمبلی ذخیره شده است) هش‌کد اینکریپت‌شده که در زمان کامپایل محاسبه شده (یا همان امضای دیجیتال که این نیز درون خود اسمبلی ذخیره شده است) را دیکریپت می‌کند. (هش‌کد زمان کامپایل)
-پس از آن هش‌کد اسمبلی را با استفاده از داده‌های مانیفست اسمبلی محاسبه می‌کند. (هش‌کد زمان اجرا)
-سپس این دو مقدار بدست آمده (هش‌کد زمان کامپایل و هش‌کد زمان اجرا) را با یکدیگر مقایسه می‌کند. این عملیات مقایسه و تایید مشخص می‌کند که آیا اسمبلی پس از امضا دچار تغییر شده است یا خیر!
اگر یک اسمبلی نتواند عملیات تایید نام قوی را پشت سر بگذارد، CLR پیغام خطایی به نمایش خواهد گذاشت. این خطا یک اکسپشن از نوع System.IO.FileLoadException با پیغام Strong name validation failed خواهد بود. با استفاده از ابزار sn نیز می‌توان یک اسمبلی قوی-نام‌گذاری شده را تایید کرد. برای مثال برای تایید اسمبلی MyAsm.exe می‌توان از دستور زیر استفاده کرد:
sn –vf MyAsm.exe

سوییچ v موجب تایید نام قوی اسمبلی شده و سوییچ f برنامه را مجبور به بررسی صحت نام قوی اسمبلی می‌کند، حتی اگر این امکان قبلا برای اسمبلی غیرفعال شده باشد. (با استفاده از سویج Vr مثل دستور sn –Vr 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'.
4. امضای تاخیری (Delay Sign) یک اسمبلی
درصورتی‌که بخواهیم یک اسمبلی را امضا کنیم اما نخواهیم تمام اعضای تیم توسعه به کلید خصوصی مربوطه دسترسی داشته باشند باید از تکنیک امضای با تاخیر اسمبلی استفاده کنیم. ابتدا باید کلید عمومی تولیدشده برای اسمبلی را استخراج کرده و آنرا توزیع کنیم. با توجه به توضیحات داده شده در بخش اول، به اسمبلی خود یک نام قوی اختصاص دهید. هم‌چنین اسمبلی خود را با استفاده از سویج delaysign/ باید کامپایل کنید. سپس با استفاده از سوییچ Vr برنامه sn عملیات تایید اسمبلی خود را غیرفعال کنید.
نکته: برای استفاده از این امکان در ویژوال استودیو باید گزینه Delay sign only را در تب Signing از پراپرتی پروژه انتخاب کرد.

 اسمبلی‌هایی که ریفرنسی به اسمبلی‌های نام‌گذاری قوی شده دارند، حاوی توکِن کلید عمومی آن اسمبلی‌ها نیز هستند. این بدین معنی است که این گونه اسمبلی‌ها بایستی قبل از ریفرنس داده شدن امضا شده باشند. در یک محیط توسعه که اسمبلی‌ها مرتبا کامپایل می‌شوند نیاز است تا تمام توسعه دهندگان و آزمایش‌کنندگان به جفت‌کلیدهای موجود دسترسی داشته باشند (یک ریسک امنیتی بزرگ). به جای توزیع کلید خصوصی، دات‌نت‌فریمورک مکانیزمی به نام امضای تاخیری (delay-signing) فراهم کرده است، که به شما اجازه می‌دهد تا یک اسمبلی را به‌صورت ناکامل (ناقص) امضا کنید. اسمبلی «ناقص-نام‌گذاریِ قوی شده»! حاوی کلید عمومی و توکِن کلید عمومی است که برای ریفرنس دادن اسمبلی نیاز است، اما تنها حاوی مکانِ خالیِ امضای دیجیتالی است که توسط کلید خصوصی تولید می‌شود. پس از کامل شدن توسعة برنامه، فرد مسئول امضای اسمبلی‌ها (signing authority - شخصی که مسئول امنیت و حفظ جفت‌کلیدهاست) اسمبلی‌‌های حاوی امضای تاخیری را دوباره امضا می‌کند، تا نام‌گذاریِ قوی آن اسمبلی کامل شود. برای امضای تاخیری یک اسمبلی تنها نیاز به کلید عمومی آن است، که هیچ ریسک امنیتی‌ای برای آن وجود ندارد. برای استخراج کلید عمومی یک جفت کلید همان‌طور که قبلا اشاره شده است، می‌توان از دستورات زیر استفاده کرد:
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
نکته: برای امضای اسمبلی‌های چندفایلی (multifile assembly) باید از Assembly Linker (نام فایل اجرایی آن al.exe است) استفاده کرد. این ابزار نیز مانند ابزار sn.exe در sdkهای ویندوز یافت می‌شود. دستوری که باید برای امضای این نوع اسمبلی‌های به‌کار برد به‌صورت زیر است:
al /out:<assembly name> <module name> /keyfile:<file name>
از آنجاکه درهنگام بارگذاری اسمبلی، CLR اسمبلی را به عنوان یک اسمبلی قوی نام‌گذاری شده درنظر می‌گیرد، همان‌طور که قبلا اشاره شده، سعی می‌کند تا صحت آن را بررسی و تایید کند. اما چون اسمبلی با امضای تاخیری هنوز امضا نشده است، باید CLR را جوری تنظیم کنید تا تایید اعتبار این اسمبلی را در کامپیوتر جاری انجام ندهد. این کار را همان‌طور که در بالا توضیح داده شد، می‌توان با دستور زیر انجام داد:
sn –Vr d:\MyAsm.exe

از لحاظ فنی این دستور اسمبلی موردنظر را در لیست «صرف‌نظر از تایید اسمبلی» ثبت (register) می‌کند. دقت کنید که دستور فوق را باید در تمام سیستم‌هایی که قرار است به نحوی با این اسمبلی سروکار داشته باشند اجرا کنید!
نکته: تا زمانی‌که با استفاده از دستور فوق عملیات تایید اعتبار اسمبلی‌های امضای تاخیری شده را غیرفعال نکنید امکان اجرا یا بارگذاری آن اسمبلی‌ها و نیز دیباگ سورس‌کدهای آن را نخواهید داشت!
پس از تکمیل فاز توسعه باید اسمبلی را دوباره امضا کنید تا نام‌گذاری قوی کامل شود. برنامه sn به شما این امکان را می‌دهد تا بدون تغییر سورس‌کد اسمبلی خود یا کامپایل دوباره آن عملیات امضای دوباره آنرا انجام دهید. اما برای این‌کار شما باید به کلید خصوصی آن (در واقع به فایل حاوی جفت‌کلید مربوطه) دسترسی داشته باشید. برای امضای دوباره می‌توان از دستورات زیر استفاده کرد:
sn –R d:\MyAsm.exe MyKeys.snk
sn –R d:\MyAsm.exe MyKeysContainer

با استفاده از این دستور برنامه sn شروع به محاسبه هش‌کد زمان کامپایل می‌کند و درنهایت مقدار اینکریپت‌شده را درون اسمبلی ذخیره می‌کند.
نکته: هنگام استفاده از اسمبلی‌های با امضای تاخیری، امکان مقایسه بیلدهای مختلف یک اسمبلی خاص برای اطمینان از اینکه تنها در امضای دیجیتال با هم فرق دارند، معمولا مفید است. این مقایسه تنها وقتی امکان‌پذیر است که اسمبلی موردنظر با استفاده از سوییچ R دوباره امضا شود. برای مقایسه دو اسمبلی می‌توان از سوییچ D استفاده کرد:
sn –D assembly1 assembly2
پس از امضای دوباره اسمبلی می‌توان عملیات تایید آنرا که قبلا غیرفعال شده است، با استفاده از دستور زیر دوباره فعال کرد:
sn –Vu d:\MyAsm.exe

دستور فوق اسمبلی موردنظر را از لیست «صرفنظر از تایید اسمبلی» حذف (Unregister) می‌کند.
نکته: درصورتی‌که بخواهید یک اسمبلی را قبل از امضای دوباره (و یا در حالت کلی، قبل از اینکه اسمبلی دارای یک نام قوی کامل شده باشد) اجرا یا از آن به عنوان یک ریفرنس استفاده کنید، بدون اینکه آن را به لیست «صرفنظر از تایید اسمبلی» اضافی کنید،  با خطای زیر مواجه خواهید شد: 

برای فعال‌سازی تایید اسمبلی برای تمامی اسمبلی‌هایی که این ویژگی برای آنان غیرفعال شده است، می‌توانید از دستور زیر استفاده کنید:
sn –Vx
برای لیست کردن اسمبلی‌هایی که تایید آنان غیرفعال شده است، می‌توانید از دستور زیر استفاده کنید:
sn –Vl
نکته: در دات‌نت 1.0 و 1.1 کامپایلر #C فاقد سوییچ delaysign/ است. برای استفاده از امکان امضای تاخیری اسمبلی می‌توان از attribute سطح اسمبلی System.Reflection.AssemblyDelaySignAttribute استفاده کرد. همچنین می‌شود از ابزار لینکر اسمبلی (al.exe) که از این سوییچ پشتیبانی می‌کند استفاده کرد.
نکته: ابزارهای obfuscating که برای پیچیده‌کردن کد IL اسمبلی تولیدی به‌منظور جلوگیری از عملیات تولید دوباره کد (مثل کاری که برنامه Reflector انجام می‌دهد) به‌کار می‌روند، به دلیل تغییراتی که در محتوای اسمبلی ایجاد می‌کنند، درصورتیکه برای اسمبلی‌های دارای نام قوی استفاده شوند موجب ازکار افتادن آن‌ها می‌شوند. بنابراین یا باید آن‌ها را در سیستم‌هایی استفاده کرد که آن اسمبلی موردنظر در لیست صرفنظر از تایید اسمبلی ثبت شده باشد یا اینکه اسمبلی مربوطه را دوباره با استفاده از روش‌های توضیح داده‌شده (مثلا با استفاده از دستور sn –R myAsm.dll MyKeys.snk) برای تخصیص نام قوی جدید امضا کرد. الگوی معمولی که برای استفاده از obfuscating برای اسمبلی‌های دارای نام قوی استفاده می‌شود به‌صورت زیر است:
- ساخت اسمبلی با امضای تاخیری
- افزودن اسمبلی به لیست صرفنظر از تایید اسمبلی (sn -Vr)
- دیباگ و تست اسمبلی
- obfuscate کردن اسمبلی
- دیباگ و تست اسمبلی obfuscate شده
- امضای دوباره اسمبلی (sn -R)
الگوی ساده‌تر دیگری نیز برای این منظور استفاده می‌شود که به‌صورت زیر است:
- تولید اسمبلی بدون استفاده از تنظیمات امضای تاخیری
- دیباگ و تست اسمبلی
- obfuscate اسمبلی
- امضای دوباره اسمبلی (sn -R)
- دیباگ و تست دوباره نسخه obfuscate شده

5. مدیریت کش عمومی اسمبلی‌ها (Global Assembly Cache)
با استفاده از توضیحات این بخش می‌توان اسمبلی‌ها را به GAC اضافه و یا از درون آن حذف کرد. این کار با استفاده از برنامه gacutil.exe انجام می‌شود. مسیر نسخه 4 و 32 بیتی این برنامه به‌صورت زیر است:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
این برنامه به‌همراه SDK ویندوز و یا به‌همراه ویژوال استودیو در مسیری مشابه نشانی بالا نصب می‌شود. همانند توضیحات داده‌شده در مورد برنامه sn.exe، برای راحتی کار می‌توانید از خط فرمان ویژه‌ای که ویژوال استودیو در اختیار شما قرار می‌دهد استفاده کنید. البته قبل از اجرای هر دستوری مطمئن شوید که خط فرمان شما با استفاده از مجوز مدیریتی (Administrator) اجرا شده است! تنها اسمبلی‌های دارای نام قوی می‌توانند در GAC نصب شوند. بنابراین قبل افزودن یک اسمبلی به GAC باید طبق راهنمایی‌های موجود در قسمت‌های قبلی آن را به‌صورت قوی نام‌گذاری کرد. برای افزودن یک اسمبلی با نام MyAsm.dll می‌توان از دستور زیر استفاده کرد:
gacutil /i c:\MyAsm.dll

درصورتی‌که اسمبلی موردنظر دارای نام قوی نباشد، خطایی به صورت زیر نمایش داده خواهد شد:

می‌توان نسخه‌های متفاوتی از یک اسمبلی (با نام یکسان) را با استفاده از این ابزار در GAC رجیستر کرد و آن‌ها را در کنار یکدیگر برای استفاده در نرم‌افزارهای گوناگون در اختیار داشت. برای حذف یک اسمبلی از GAC و یا به اصطلاح uninstall کردن آن می‌توان از دستور زیر استفاده کرد:
gacutil /u MyAsm
نکته: دقت کنید که در این دستور تنها از نام اسمبلی استفاده شده است و نه نام فایل حاوی آن!

دستور فوق تمام نسخه‌های اسمبلی MyAsm موجود در GAC را حذف خواهد کرد. برای حذف نسخه‌ای خاص باید از دستوری مشابه زیر استفاده کرد:
gacutil /u MyAsm,Version=1.3.0.5
برای مشاهده تمام اسمبلی‌های نصب شده در GAC می‌توان از دستور زیر استفاده کرد:
gacutil /l

همان‌طور که مشاهده می‌کنید دستور فوق فهرستی بسیار طولانی از تمام اسمبلی‌های نصب‌شده در GAC را به‌همراه لیست اسمبلی‌هایی که در کش ngen به فرم باینری پیش‌کامپایل (Precompiled) شده‌اند، نمایش می‌دهد. برای تعیین اینکه آیا اسمبلی موردنظر در GAC نصب شده است می‌توان از دستور زیر استفاده کرد:
gacutil /l MyAsm

نکته: دات‌نت از GAC تنها در زمان اجرا استفاده می‌کند. بنابراین کامپایلر #C به‌صورت خودکار درون GAC را برای یافتن ریفرنس‌های یک اسمبلی جستجو نخواهد کرد. در زمان توسعه، کامپایلر #C به یک نسخه لوکال از ریفرنس‌های مذکور نیاز خواهد داشت. برای حل این مشکل می‌توان یک نسخه از این ریفرنس‌ها را به مسیر اسمبلی کپی کرد (در ویژوال استودیو می‌توان از خاصیت Copy Local ریفرنس‌ها استفاده کرد) یا با استفاده از سوییچ lib/ کامپایلر، مسیری را که می‌تواند این ریفرنس‌ها را در آن بیابد معرفی کرد (کاری که ویژوال استودیو به‌صورت خودکار انجام می‌دهد).
نکته: نکته‌ای که در پایان باید اشاره کرد این است که تکنولوژی نام قوی برای بحث امنیت کد اسمبلی (مثلا برای جلوگیری از مهندسی معکوس IL و تغییر آن) بوجود نیامده است زیرا حذف این نام‌های قوی کار سختی نیست. بلکه هدف اصلی این تکنولوژی جلوگیری از تغییرات مخفی خرابکارانه و محرمانه اسمبلی توزیع شده و توزیع این نسخه‌های دستکاری شده به جای نسخه اصلی است. در زیر ابزارها و روش‌هایی که می‌توانند برای حذف کامل نام قوی یک اسمبلی به‌کار روند آورده شده است.
البته باید به این نکته اشاره کرد که در صورت حذف نام قوی یک اسمبلی (یا همان حذف امضای دیجیتال درون آن) تمامی اسمبلی‌هایی که قبل از حذف نام قوی به آن ریفرنس داشتند از کار خواهند افتاد. یعنی درواقع تمامی آن اسمبلی‌ها برای ریفرنس دادن به این اسمبلی با نام جدید (نامی که دیگر قوی نیست) باید آپدیت شوند. هم‌چنین درصورتی‌که اسمبلی‌هایی که قبل از حذف نام قوی به اسمبلی موردنظر ما ریفرنس داشتند، خود نام قوی داشته باشند با حذف نام قوی، آنها از کار خواهند افتاد. چون اسمبلی‌های دارای نام قوی تنها می‌توانند از اسمبلی‌های دارای نام قوی ریفرنس داشته باشند. بنابراین برای کارکردن برنامه موردنظر باید نام قوی تمامی اسمبلی‌های درگیر را حذف کرد!
منابع استفاده شده در تهیه این مطلب:
مطالب
Roslyn #7
معرفی Workspace API

Workspace، در حقیقت نمایش اجزای یک Solution در ویژوال استودیو است و یک Solution متشکل است از تعدادی پروژه به همراه وابستگی‌های بین آن‌ها. هدف از وجود Workspace API در Roslyn، دسترسی به اطلاعات لازم جهت انجام امور Refactoring در سطح یک Solution است. برای مثال اگر قرار است نام خاصیتی تغییر کند و این خاصیت در چندین پروژه‌ی دیگر در حال استفاده است، این نام باید در سراسر Solution جاری یافت شده و تغییر یابد. همچنین برفراز Workspace API تعدادی سرویس زبان مانند فرمت کننده‌های کدها، تغییرنام دهنده‌های سیمبل‌ها و توصیه کننده‌ها نیز تهیه شده‌اند.
همچنین این سرویس‌ها و API تهیه شده، منحصر به ویژوال استودیو نیستند و VS 2015 تنها از آن‌ها استفاده می‌کند. برای مثال نگارش‌های جدیدتر mono-develop لینوکسی نیز شروع به استفاده‌ی از Roslyn کرده‌اند.


نمایش اجزای یک Solution

 در ادامه مثالی را مشاهده می‌کنید که توسط آن نام Solution و سپس تمام پروژه‌های موجود در آن‌ها به همراه نام فایل‌های مرتبط و همچنین ارجاعات آن‌ها در صفحه نمایش داده می‌شوند:
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;


// Print the root of the solution.
Console.WriteLine(Path.GetFileName(sln.FilePath));
 
 
// Get dependency graph to perform a sort.
var g = sln.GetProjectDependencyGraph();
var ps = g.GetTopologicallySortedProjects();
 
 
// Print all projects, their documents, and references.
foreach (var p in ps)
{
    var proj = sln.GetProject(p);
 
    Console.WriteLine("> " + proj.Name);
 
    Console.WriteLine("  > References");
    foreach (var r in proj.ProjectReferences)
    {
        Console.WriteLine("    - " + sln.GetProject(r.ProjectId).Name);
    }
 
    foreach (var d in proj.Documents)
    {
        Console.WriteLine("  - " + d.Name);
    }
}
در ابتدا نیاز است یک وهله از MSBuildWorkspace را ایجاد کرد. اکنون با استفاده از این Workspace می‌توان solution خاصی را گشود و آنالیز کرد. قسمتی از خروجی آن چنین شکلی را دارد:
 Roslyn.sln
> Roslyn01
  > References
  - Program.cs
  - AssemblyInfo.cs
  - .NETFramework,Version=v4.6.AssemblyAttributes.cs


ایجاد یک Syntax highlighter با استفاده از Classification service

هدف از Classification service، رندر کردن فایل‌ها در ادیتور جاری است. برای این منظور نیاز است بتوان واژه‌های کلیدی، کامنت‌ها، نام‌های نوع‌ها و امثال آن‌ها را به صورت کلاسه شده در اختیار داشت و سپس برای مثال هرکدام را با رنگی مجزا نمایش داد و رندر کرد.
در ادامه مثالی از آن‌را ملاحظه می‌کنید:
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;

// Get the Tests\Bar.cs document.
var proj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var test = proj.Documents.Single(d => d.Name == "Bar.cs");
 
var tree = test.GetSyntaxTreeAsync().Result;
var root = tree.GetRootAsync().Result;
 
// Get all the spans in the document that are classified as language elements.
var spans = Classifier.GetClassifiedSpansAsync(test, root.FullSpan).Result.ToDictionary(c => c.TextSpan.Start, c => c);
 
// Print the source text with appropriate colorization.
var txt = tree.GetText().ToString();
 
var i = 0;
foreach (var c in txt)
{
    var span = default(ClassifiedSpan);
    if (spans.TryGetValue(i, out span))
    {
        var color = ConsoleColor.Gray;
 
        switch (span.ClassificationType)
        {
            case ClassificationTypeNames.Keyword:
                color = ConsoleColor.Cyan;
                break;
            case ClassificationTypeNames.StringLiteral:
            case ClassificationTypeNames.VerbatimStringLiteral:
                color = ConsoleColor.Red;
                break;
            case ClassificationTypeNames.Comment:
                color = ConsoleColor.Green;
                break;
            case ClassificationTypeNames.ClassName:
            case ClassificationTypeNames.InterfaceName:
            case ClassificationTypeNames.StructName:
            case ClassificationTypeNames.EnumName:
            case ClassificationTypeNames.TypeParameterName:
            case ClassificationTypeNames.DelegateName:
                color = ConsoleColor.Yellow;
                break;
            case ClassificationTypeNames.Identifier:
                color = ConsoleColor.DarkGray;
                break;
        }
 
        Console.ForegroundColor = color;
    }
 
    Console.Write(c);
 
    i++;
}
با این خروجی:


توضیحات:
در اینجا نیز کار با ایجاد یک Workspace و سپس گشودن Solution ایی مشخص در آن آغاز می‌شود. سپس در آن به دنبال پروژه‌ای به نام Roslyn04.Tests می‌گردیم. این پروژه حاوی تعدادی کلاس، جهت بررسی و آزمایش هستند. برای مثال در اینجا فایل Bar.cs آن قرار است آنالیز شود. پس از یافتن آن، ابتدا syntax tree آن دریافت می‌گردد و سپس به سرویس Classifier.GetClassifiedSpansAsync ارسال خواهد شد. خروجی آن شامل لیستی از Classified Spans است؛ مانند کلمات کلیدی، رشته‌ها، کامنت‌ها و غیره. در ادامه این لیست تبدیل به یک دیکشنری می‌شود که کلید آن محل آغاز این span و مقدار آن، مقدار span است. سپس متن syntax tree دریافت شده و حرف به حرف آن در طی یک حلقه بررسی می‌شود. در این حلقه، مقدار i به محل حروف جاری مورد آنالیز اشاره می‌کند. اگر این محل در دیکشنری Classified Spans وجود داشت، یعنی یک span جدید شروع شده‌است و بر این اساس، نوع آن span را می‌توان استخراج کرد و سپس بر اساس این نوع، رنگ متفاوتی را در صفحه نمایش داد.


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

این سرویس کار فرمت خودکار کدهای بهم ریخته را انجام می‌دهد؛ مانند تنظیم فاصله‌های خالی و یا ایجاد indentation و امثال آن. در حقیقت Ctlr K+D در ویژوال استودیو، دقیقا از همین سرویس زبان استفاده می‌کند.
کار کردن با این سرویس از طریق برنامه نویسی به نحو ذیل است:
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;


// Get the Tests\Qux.cs document.
var proj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var qux = proj.Documents.Single(d => d.Name == "Qux.cs");
 
Console.WriteLine("Before:");
Console.WriteLine();
Console.WriteLine(qux.GetSyntaxTreeAsync().Result.GetText());
 
Console.WriteLine();
Console.WriteLine();
 
 
// Apply formatting and print the result.
var res = Formatter.FormatAsync(qux).Result;
 
Console.WriteLine("After:");
Console.WriteLine();
Console.WriteLine(res.GetSyntaxTreeAsync().Result.GetText());
Console.WriteLine();
با این خروجی:
Before:

using System;

namespace Roslyn04.Tests
{
    class Qux {
        public void Baz()
        { Console.WriteLine(42);
            return;  }
    }
}


After:

using System;

namespace Roslyn04.Tests
{
    class Qux
    {
        public void Baz()
        {
            Console.WriteLine(42);
            return;
        }
    }
}
همانطور که ملاحظه می‌کنید، فایل Qux.cs که فرمت مناسبی ندارد. بنابراین باز شده و syntax tree آن به سرویس Formatter.FormatAsync جهت فرمت شدن ارسال می‌شود.


سرویس یافتن سیمبل‌ها

یکی دیگر از قابلیت‌هایی که در ویژوال استودیو وجود دارد، امکان یافتن سیمبل‌ها است. برای مثال این نوع یا کلاس خاص، در کجاها استفاده شده‌است و به آن ارجاعاتی وجود دارد. مواردی مانند Find all references، Go to definition و نمایش Call hierarchy از این سرویس استفاده می‌کنند.
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;


// Get the Tests project.
var proj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
 
// Locate the symbol for the Bar.Foo method and the Bar.Qux property.
var comp = proj.GetCompilationAsync().Result;
 
var barType = comp.GetTypeByMetadataName("Roslyn04.Tests.Bar");
 
var fooMethod = barType.GetMembers().Single(m => m.Name == "Foo");
var quxProp = barType.GetMembers().Single(m => m.Name == "Qux");
 
 
// Find callers across the solution.
Console.WriteLine("Find callers of Foo");
Console.WriteLine();
 
var callers = SymbolFinder.FindCallersAsync(fooMethod, sln).Result;
foreach (var caller in callers)
{
    Console.WriteLine(caller.CallingSymbol);
    foreach (var location in caller.Locations)
    {
        Console.WriteLine("    " + location);
    }
}
 
Console.WriteLine();
Console.WriteLine();
 
// Find all references across the solution.
Console.WriteLine("Find all references to Qux");
Console.WriteLine();
 
var references = SymbolFinder.FindReferencesAsync(quxProp, sln).Result;
foreach (var reference in references)
{
    Console.WriteLine(reference.Definition);
    foreach (var location in reference.Locations)
    {
        Console.WriteLine("    " + location.Location);
    }
}
در این مثال، پروژه‌ی Roslyn04.Tests که حاوی کلاس‌های Foo و Qux است، جهت آنالیز باز شده‌است. در اینجا برای رسیدن به Symbols نیاز است ابتدا به Compilation API دسترسی یافت و سپس متادیتاها را بر اساس آن استخراج کرد. سپس متدهای Foo و خاصیت Qux آن یافت شده‌اند.
اکنون با استفاده از سرویس SymbolFinder.FindCallersAsync تمام فراخوان‌های متد Foo را در سراسر Solution جاری می‌یابیم.
سپس با استفاده از سرویس SymbolFinder.FindReferencesAsync تمام ارجاعات به خاصیت Qux را در Solution جاری نمایش می‌دهیم.


سرویس توصیه کننده

Intellisense در ویژوال استودیو از سرویس توصیه کننده‌ی Roslyn استفاده می‌کند.
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;

// Get the Tests\Foo.cs document.
var proj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var foo = proj.Documents.Single(d => d.Name == "Foo.cs");
 
 
// Find the 'dot' token in the first Console.WriteLine member access expression.
var tree = foo.GetSyntaxTreeAsync().Result;
var model = proj.GetCompilationAsync().Result.GetSemanticModel(tree);
var consoleDot = tree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().First().OperatorToken;
 
 
// Get recommendations at the indicated cursor position.
//
//   Console.WriteLine
//           ^
var res = Recommender.GetRecommendedSymbolsAtPosition(

                    model, consoleDot.GetLocation().SourceSpan.Start + 1, ws).ToList();
 
foreach (var rec in res)
{
    Console.WriteLine(rec);
}
در این مثال سعی شده‌است لیست توصیه‌های ارائه شده در حین تایپ دات، توسط سرویس Recommender.GetRecommendedSymbolsAtPosition دریافت و نمایش داده شوند. در ابتدای کار، کلاس Foo گشوده شده و سپس Syntax tree و Semantic model آن استخراج می‌شود. این model پارامتر اول متد سرویس توصیه کننده‌است. سپس نیاز است محل مکانی را به آن معرفی کنیم تا کار توصیه کردن را بر اساس آن شروع کند. برای نمونه در اینجا OperatorToken در حقیقت همان دات مربوط به Console.WriteLine است. پس از یافتن این توکن، امکان دسترسی به مکان آن وجود دارد.
تعدادی از خروجی‌های مثال فوق به صورت زیر هستند:
 System.Console.Beep()
System.Console.Beep(int, int)
System.Console.Clear()


سرویس تغییر نام دادن

هدف از سرویس Renamer.RenameSymbolAsync، تغییر نام یک identifier در کل Solution است. نمونه‌ای از نحوه‌ی کاربرد آن‌را در مثال ذیل مشاهده می‌کنید:
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;


// Get Tests\Bar.cs before making changes.
var oldProj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var oldDoc = oldProj.Documents.Single(d => d.Name == "Bar.cs");
 
Console.WriteLine("Before:");
Console.WriteLine();
 
var oldTxt = oldDoc.GetTextAsync().Result;
Console.WriteLine(oldTxt);
 
Console.WriteLine();
Console.WriteLine();
 
 
// Get the symbol for the Bar.Foo method.
var comp = oldProj.GetCompilationAsync().Result;
 
var barType = comp.GetTypeByMetadataName("Roslyn04.Tests.Bar");
var fooMethod = barType.GetMembers().Single(m => m.Name == "Foo");
 
 
// Perform the rename.
var newSln = Renamer.RenameSymbolAsync(sln, fooMethod, "Foo2", ws.Options).Result;
 
 
// Get Tests\Bar.cs after making changes.
var newProj = newSln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var newDoc = newProj.Documents.Single(d => d.Name == "Bar.cs");
 
Console.WriteLine("After:");
Console.WriteLine();
 
var newTxt = newDoc.GetTextAsync().Result;
Console.WriteLine(newTxt);
در این مثال، متد Foo کلاس Bar، قرار است به Foo2 تغییرنام یابد. به همین منظور ابتدا پروژه‌ی حاوی فایل Bar.cs باز شده و اطلاعات این کلاس استخراج می‌گردد. سپس اصل این کلاس تغییر نیافته نمایش داده می‌شود. در ادامه با استفاده از API کامپایل، به متادیتای متد Foo یا به عبارتی Symbol آن دسترسی پیدا می‌کنیم. سپس این Symbol به متد یا سرویس Renamer.RenameSymbolAsync ارسال می‌شود تا کار تغییر نام صورت گیرد. پس از اینکار مجددا متن کلاس تغییر یافته نمایش داده خواهد شد.


سرویس ساده کننده

هدف از سرویس ساده کننده، ساده‌کردن و کاهش کدهای ارائه شده، از دید Semantics است. برای مثال اگر فضای نامی در قسمت using ذکر شده‌است، دیگر نیازی نیست تا این فضای نام به ابتدای فراخوانی یک متد آن اضافه شود و می‌توان این قطعه از کد را ساده‌تر کرد و کاهش داد.
var ws = MSBuildWorkspace.Create();
var sln = ws.OpenSolutionAsync(@"..\..\..\Roslyn.sln").Result;


// Get the Tests\Baz.cs document.
var proj = sln.Projects.Single(p => p.Name == "Roslyn04.Tests");
var baz = proj.Documents.Single(d => d.Name == "Baz.cs");
 
Console.WriteLine("Before:");
Console.WriteLine();
Console.WriteLine(baz.GetSyntaxTreeAsync().Result.GetText());
 
Console.WriteLine();
Console.WriteLine();
 
var oldRoot = baz.GetSyntaxRootAsync().Result;

 
var memberAccesses = oldRoot.DescendantNodes().OfType<CastExpressionSyntax>();
var newRoot = oldRoot.ReplaceNodes(memberAccesses, (_, m) => m.WithAdditionalAnnotations(Simplifier.Annotation));
 
var newDoc = baz.WithSyntaxRoot(newRoot);
 
 
// Invoke the simplifier and print the result.
var res = Simplifier.ReduceAsync(newDoc).Result;
 
Console.WriteLine("After:");
Console.WriteLine();
Console.WriteLine(res.GetSyntaxTreeAsync().Result.GetText());
Console.WriteLine();
در این مثال نحوه‌ی ساده سازی cast‌های اضافی را ملاحظه می‌کنید. برای مثال اگر نوع متغیری int است، دیگر نیازی نیست در سراسر کد در کنار این متغیر، cast به int را هم ذکر کرد و می‌توان این کد را ساده‌تر نمود.


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید:
Roslyn-Samples.zip
نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
در ویندوز، دات نت، از متدهای CoCreateGuid و UuidCreate ویندوز برای تولید Guid استفاده می‌کند. از زمان Windows 2000، اطلاعات بیت‌های اتفاقی این Guid از طریق Windows CryptGenRandom cryptographic API  تامین می‌شود که در نتیجه حداقل 122 bits آن اتفاقی است.
مطالب دوره‌ها
بررسی مثال‌ها و جزئیات بیشتر تولید کدهای پویا توسط Reflection.Emit
نحوه معرفی متغیرهای محلی در Reflection.Emit

ابتدا مثال کامل ذیل را درنظر بگیرید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static int Calculate(int a, int b, int c)
        {
            var result = a * b;
            return result - c;
        }

        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Calculate(10, 2, 3));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "CalculateMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int), typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();

            il.Emit(opcode: OpCodes.Ldarg_0); // بارگذاری اولین آرگومان بر روی پشته ارزیابی 
            il.Emit(opcode: OpCodes.Ldarg_1); // بارگذاری دومین آرگومان بر روی پشته ارزیابی 
            il.Emit(opcode: OpCodes.Mul); // انجام عملیات ضرب
            il.Emit(opcode: OpCodes.Stloc_0); // ذخیره سازی نتیجه عملیات ضرب در یک متغیر محلی
            il.Emit(opcode: OpCodes.Ldloc_0); // متغیر محلی را بر روی پشته ارزیابی قرار می‌دهد تا در عملیات بعدی قابل استفاده باشد
            il.Emit(opcode: OpCodes.Ldarg_2); // آرگومان سوم را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(opcode: OpCodes.Sub); // انجام عملیات تفریق
            il.Emit(opcode: OpCodes.Ret); // بازگشت نتیجه

            //فراخوانی متد پویا
            var method = (Func<int, int, int, int>)myMethod.CreateDelegate(typeof(Func<int, int, int, int>));
            Console.WriteLine(method(10, 2, 3));

        }
    }
}
در این مثال سعی کرده‌ایم معادل متد Calculate را که در ابتدای برنامه ملاحظه می‌کنید، با کدهای IL تولید کنیم. روش کار مانند قسمت قبل است. ابتدا وهله‌ی جدیدی را از کلاس DynamicMethod جهت معرفی امضای متد پویای خود ایجاد می‌کنیم. در اینجا نوع خروجی را int و نوع سه پارامتر آن‌را به نحوی که مشخص شده است توسط آرایه‌ای از typeهای int معرفی خواهیم کرد. سپس محل قرارگیری کد تولیدی پویا مشخص می‌شود.
در ادامه توسط ILGenerator، آرگومان‌های دریافتی بارگذاری شده، در هم ضرب می‌شوند. سپس نتیجه در یک متغیر محلی ذخیره شده و سپس از آرگومان سوم کسر می‌گردد. در آخر هم این نتیجه بازگشت داده خواهد شد.
در اینجا روش سومی را برای کار با متدهای پویا مشاهده می‌کنید. بجای تعریف یک delegate به صورت صریح همانند قسمت قبل، از یک Func یا حتی Action نیز بنابر امضای متد مد نظر، می‌توان استفاده کرد. در اینجا از یک Func که سه پارامتر int را قبول کرده و خروجی int نیز دارد، استفاده شده است.
اگر برنامه را اجرا کنید ... کرش خواهد کرد! با استثنای ذیل:
 System.InvalidProgramException was unhandled
Message=Common Language Runtime detected an invalid program.
علت اینجا است که در حین کار با System.Reflection.Emit، نیاز است نوع متغیر محلی مورد استفاده را نیز مشخص نمائیم. اینکار را توسط فراخوانی متد DeclareLocal که باید پس از فراخوانی GetILGenerator، درج گردد، می‌توان انجام داد:
 il.DeclareLocal(typeof(int));
با این تغییر، برنامه بدون مشکل اجرا خواهد شد.


نحوه تعریف برچسب‌ها در Reflection.Emit

در ادامه قصد داریم یک مثال پیشرفته‌تر را بررسی کنیم.
        static int Calculate(int x)
        {
            int result = 0;
            for (int i = 0; i < 10; i++)
            {
                result += i * x;
            }
            return result;
        }
در اینجا می‌خواهیم کدهای معادل متد محاسباتی فوق را توسط امکانات System.Reflection.Emit و کدهای IL تولید کنیم.
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static int Calculate(int x)
        {
            int result = 0;
            for (int i = 0; i < 10; i++)
            {
                result += i * x;
            }
            return result;
        }

        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Calculate(10));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "CalculateMethod",
                                        returnType: typeof(int), // خروجی متد عدد صحیح است
                                        parameterTypes: new[] { typeof(int) }, // یک پارامتر عدد صحیح دارد
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();

            // از برچسب‌ها برای انتقال کنترل استفاده می‌شود
            // در اینجا به دو برچسب برای تعریف ابتدای حلقه
            // و همچنین برای پرش به جایی که متد خاتمه می‌یابد نیاز داریم
            var loopStart = il.DefineLabel();
            var methodEnd = il.DefineLabel();

            // variable 0; result = 0
            il.DeclareLocal(typeof(int)); //  برای تعریف متغیر محلی نتیجه عملیات
            il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(OpCodes.Stloc_0); // و نهایتا این عدد ثابت به متغیر محلی انتساب داده خواهد شد

            // variable 1; i = 0
            il.DeclareLocal(typeof(int)); // در اینجا کار تعریف و مقدار دهی متغیر حلقه انجام می‌شود
            il.Emit(OpCodes.Ldc_I4_0); // عدد ثابت صفر را بر روی پشته ارزیابی قرار می‌دهد
            il.Emit(OpCodes.Stloc_1); // و نهایتا این عدد ثابت به متغیر حلقه در ایندکس یک انتساب داده خواهد شد

            // در اینجا کار تعریف بدنه حلقه شروع می‌شود
            il.MarkLabel(loopStart); // شروع حلقه را علامتگذاری می‌کنیم تا بعدا بتوانیم به این نقطه پرش نمائیم
            il.Emit(OpCodes.Ldloc_1); // در ادامه می‌خواهیم بررسی کنیم که آیا مقدار متغیر حلقه از عدد 10 کوچکتر است یا خیر
            il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت ده را بر روی پشته ارزیابی قرار می‌دهد
            // برای انجام بررسی‌های تساوی یا کوچکتر یا بزرگتر نیاز است ابتدا دو متغیر مدنظر بر روی پشته قرار گیرند
            il.Emit(OpCodes.Bge, methodEnd);  // اگر اینطور نیست و مقدار متغیر از 10 کمتر نیست، کنترل برنامه را به انتهای متد هدایت خواهیم کرد

            // i * x
            il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Ldarg_0); // مقدار اولین آرگومان متد را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Mul); // انجام عملیات ضرب
            // نتیجه این عملیات اکنون بر روی پشته قرار گرفته است

            // result += 
            il.Emit(OpCodes.Ldloc_0); // متغیر نتیجه را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Add); // اکنون عملیات جمع بر روی نتیجه ضرب قسمت قبل که بر روی پشته قرار دارد و همچنین متغیر نتیجه انجام می‌شود
            il.Emit(OpCodes.Stloc_0); // ذخیره سازی نتیجه در متغیر محلی

            // i++
            // در اینجا کار افزایش متغیر حلقه انجام می‌شود
            il.Emit(OpCodes.Ldloc_1); // مقدار متغیر حلقه بر روی پشته قرار می‌گیرد
            il.Emit(OpCodes.Ldc_I4_1); // عدد ثابت یک بر روی پشته قرار می‌گیرد
            il.Emit(OpCodes.Add); // سپس این دو عدد بارگذاری شده با هم جمع خواهند شد
            il.Emit(OpCodes.Stloc_1); // نتیجه در متغیر حلقه ذخیره خواهد شد

            // مرحله بعد شبیه سازی حلقه با پرش به ابتدای برچسب آن است
            il.Emit(OpCodes.Br, loopStart);

            //در اینجا انتهای متد علامتگذاری شده است
            il.MarkLabel(methodEnd);
            il.Emit(OpCodes.Ldloc_0); // مقدار نتیجه بر روی پشته قرار داده شده
            il.Emit(OpCodes.Ret); // و بازگشت داده می‌شود

            //فراخوانی متد پویا
            var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>));
            Console.WriteLine(method(10));
        }
    }
}
کد کامل معادل را به همراه کامنت گذاری سطر به سطر آن، ملاحظه می‌کنید. در اینجا نکته‌های جدید، نحوه تعریف برچسب‌ها و انتقال کنترل برنامه به آن‌ها هستند؛ جهت شبیه سازی حلقه و همچنین خاتمه آن و انتقال کنترل به انتهای متد.


فراخوانی متدها توسط کدهای پویای Reflection.Emit

در ادامه کدهای کامل یک مثال متد پویا را که متد print را فراخوانی می‌کند، ملاحظه می‌کنید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        public static void print(int i)
        {
            Console.WriteLine("i: {0}", i);
        }

        static void Main(string[] args)
        {
            //روش متداول
            print(10);

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "myMethod",
                                        returnType: typeof(void),
                                        parameterTypes: null, // پارامتری ندارد
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(OpCodes.Ldc_I4, 10); // عدد ثابت 10 را بر روی پشته قرار می‌دهد
            // اکنون این مقدار بر روی پشته است و از آن می‌توان برای فراخوانی متد پرینت استفاده کرد
            il.Emit(OpCodes.Call, typeof(Program).GetMethod("print"));
            il.Emit(OpCodes.Ret);


            //فراخوانی متد پویا
            var method = (Action)myMethod.CreateDelegate(typeof(Action));
            method();
        }
    }
}
در اینجا از OpCode مخصوص فراخوانی متدها به نام Call که در قسمت‌های قبل در مورد آن بحث شد، استفاده گردیده است. برای اینکه امضای دقیقی را در اختیار آن قرار دهیم، می‌توان از Reflection استفاده کرد که نمونه‌ای از آن‌را در اینجا ملاحظه می‌کنید.
به علاوه چون خروجی امضای متد ما از نوع void است، اینبار delegate تعریف شده را از نوع Action تعریف کرده‌ایم و نه از نوع Func.


فراخوانی متدهای پویای Reflection.Emit توسط سایر متدهای پویای Reflection.Emit

فراخوانی یک متد پویای مشخص از طریق متد‌های پویای دیگر نیز همانند مثال قبل است:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "mulMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // اولین آرگومان متد را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Ldc_I4, 42); // عدد ثابت 42 را بر روی پشته قرار می‌دهد
            il.Emit(OpCodes.Mul); // ضرب این دو در هم
            il.Emit(OpCodes.Ret); // بازگشت نتیجه

            //فراخوانی متد پویا
            var method = (Func<int, int>)myMethod.CreateDelegate(typeof(Func<int, int>));
            Console.WriteLine(method(10));

            // فراخوانی متد پویای فوق در یک متد پویای دیگر
            var callerMethod = new DynamicMethod(
                                        name: "callerMethod",
                                        returnType: typeof(int),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var callerMethodIL = callerMethod.GetILGenerator();
            callerMethodIL.Emit(OpCodes.Ldarg_0); // پارامتر اول متد را بر روی پشته قرار می‌دهد
            callerMethodIL.Emit(OpCodes.Ldarg_1); // پارامتر دوم متد را بر روی پشته قرار می‌دهد
            callerMethodIL.Emit(OpCodes.Mul); // ضرب این دو در هم
            //حاصل ضرب اکنون بر روی پشته است که در فراخوانی بعدی استفاده می‌شود
            callerMethodIL.Emit(OpCodes.Call, myMethod); // فراخوانی یک متد پویای دیگر
            callerMethodIL.Emit(OpCodes.Ret);

            //فراخوانی متد پویای جدید
            var method2 = (Func<int, int, int>)callerMethod.CreateDelegate(typeof(Func<int, int, int>));
            Console.WriteLine(method2(10, 2));
        }
    }
}
در مثال فوق ابتدا یک متد پویای ضرب را تعریف کرده‌ایم که عددی صحیح را دریافت و آن‌را در 42 ضرب می‌کند و نتیجه را بازگشت می‌دهد.
سپس متد پویای دومی تعریف شده است که دو عدد صحیح را دریافت و این دو را در هم ضرب کرده و سپس نتیجه را به عنوان پارامتر به متد پویای اول ارسال می‌کند.
هنگام فراخوانی OpCodes.Call، پارامتر دوم باید از نوع MethodInfo باشد. نوع یک DynamicMethod نیز همان MethodInfo است. بنابراین برای فراخوانی آن، کار خاصی نباید انجام شود و صرفا ذکر نام متغیر مرتبط با مد پویای مدنظر کفایت می‌کند.
مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود»، نحوه‌ی مهندسی معکوس ساختار جداول و ارتباطات یک بانک اطلاعاتی از پیش موجود را به روش Code First بررسی کردیم. با توجه به رسمی بودن این ابزار، می‌توان از آن برای یافتن معادل‌های سمت بانک اطلاعاتی، در EF Core نیز استفاده کرد. برای مثال بررسی کرد، درک EF Core از بانک اطلاعاتی طراحی شده چیست و هر چند در آن مطلب عنوان شد که می‌توان با پارامتر data-annotations-- ، خروجی نهایی را بر اساس روش data-annotations، بجای Fluent API به دست آورد، اما در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» مشاهده کردیم که بسیاری از تنظیمات پیشرفته‌ی EF Core، اساسا معادل data-annotation ایی ندارند. بنابراین بهتر است این پارامتر را فعال سازی نکنید.


تنظیمات روابط یک به چند در EF Core

همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانک‌های اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطه‌ی تعریف شده‌ی در آن از نوع one-to-many است: یک بلاگ که می‌تواند چندین مطلب را داشته باشد.


اگر EF Core را وادار به تولید نگاشت‌های Code First معادل آن کنیم، به این خروجی‌ها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
با خروجی:
using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }
        public string Url { get; set; }

        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(entity =>
            {
                entity.Property(e => e.Url).IsRequired();
            });

            modelBuilder.Entity<Post>(entity =>
            {
                entity.HasOne(d => d.Blog)
                    .WithMany(p => p.Post)
                    .HasForeignKey(d => d.BlogId);
            });
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}

نحوه‌ی تشخیص خودکار روابط

EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاس‌ها تشخیص می‌دهد. در اینجا به خاصیت Blog نام navigation property را می‌دهند:
 public virtual Blog Blog { get; set; }
و به خاصیت Post نیز Collection navigation property می‌گویند:
 public virtual ICollection<Post> Post { get; set; }
در اینجا اگر تنها دو navigation property، در کلاس‌های به هم مرتبط شده، یافت شوند، به صورت خودکار به عنوان دو سر رابطه تنظیم می‌شوند. اگر بیشتر از یک navigation property در کلاسی وجود داشت، هیچ رابطه‌ای به صورت خودکار تشکیل نشده و باید ابتدا و انتهای روابط را به صورت دستی مشخص نمود.


نحوه‌ی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطه‌ی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
<primary key property name>
<navigation property name><primary key property name>
<principal entity name><primary key property name>
آنگاه این خاصیت به صورت خودکار به عنوان کلید خارجی تنظیم می‌شود. در رابطه‌ی فوق Blog از نوع principal است (پدر رابطه) و Post از نوع dependent (فرزند رابطه).
برای مثال در رابطه‌ی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطه‌است:
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
بنابراین به صورت خودکار به عنوان کلید خارجی درنظر گرفته می‌شود.

تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آن‌ها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.


استفاده از Fluent API برای تنظیم رابطه‌ی One-to-Many

در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده می‌کنید:
modelBuilder.Entity<Post>(entity =>
{
    entity.HasOne(d => d.Blog)
             .WithMany(p => p.Post)
             .HasForeignKey(d => d.BlogId);
});
از هر طرفی که شروع می‌کنید، متدهای HasOne و یا HasMany، مشخص کننده‌ی navigation property هستند که در سمت موجودیت معرفی شده قرار دارند. در اینجا چون کار با موجودیت Post شروع شده‌است، متد HasOne به خاصیت راهبری در همان سمت و به خاصیت Blog آن اشاره می‌کند.
مرحله‌ی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. این‌کار توسط یکی از متدهای WithOne و یا WithMany انجام می‌شود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره می‌کنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفته‌است.


ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
 dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose -a
با خروجی:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Blog
    {
        public Blog()
        {
            Post = new HashSet<Post>();
        }

        public int BlogId { get; set; }

        [Required]
        public string Url { get; set; }

        [InverseProperty("Blog")]
        public virtual ICollection<Post> Post { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Core1RtmEmptyTest.Entities
{
    public partial class Post
    {
        public int PostId { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        [ForeignKey("BlogId")]
        [InverseProperty("Post")]
        public virtual Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Core1RtmEmptyTest.Entities
{
    public partial class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        public virtual DbSet<Blog> Blog { get; set; }
        public virtual DbSet<Post> Post { get; set; }
    }
}
همانطور که در توضیحات روش Fluent API عنوان شد، این مدل خاص، چون دقیقا بر اساس پیش فرض‌های EF Core طراحی شده‌است، نیازی به هیچگونه تنظیم اضافه‌تری ندارد. اما اگر کلید خارجی، مطابق سه الگویی که عنوان شد، قابل تشخیص نباشد، باید آن‌را در روش data annotations توسط ویژگی ForeignKey، به نحو صریحی مشخص کرد:
  [ForeignKey("BlogId")]
  [InverseProperty("Post")]
  public virtual Blog Blog { get; set; }
  public int BlogId { get; set; }
همچنین اگر بیش از یک خاصیت راهبری (navigation property) وجود داشت، ذکر InverseProperty نیز ضروری است تا مشخص شود سر دیگر این رابطه دقیقا کدام است.
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
   [InverseProperty("Blog")]
  public virtual ICollection<Post> Post { get; set; }

مطالب تکمیلی

علت virtual بودن خواص راهبری تولید شده

اگر دقت کنید، EF Core کدی را که تولید کرده‌است، به همراه خاصیت‌هایی virtual است:
public virtual Blog Blog { get; set; }
در اینجا تمام خاصیت‌های راهبری virtual تعریف شده‌اند. علت آن، به پیاده سازی مباحث AOP بر می‌گردد. زمانیکه خاصیتی به صورت virtual تعریف می‌شود، EF core می‌تواند آن‌را توسط یک شیء پروکسی شفاف احاطه کند. این پروکسی‌ها دو هدف را دنبال می‌کند:
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آن‌ها)
ب) پیاده سازی change tracking

مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمی‌شود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شده‌اند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شده‌اند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعه‌ها نیز ICollection ذکر شده‌است. این مورد نیز یکی دیگر از پیش فرض‌های توکار EF Core است؛ در جهت تشکیل پروکسی‌ها بر روی خواص راهبری مجموعه‌ای (علاوه بر virtual تعریف کردن آن‌ها). عنوان شده‌است که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection  مشتق شده‌است، این پروکسی‌ها تشکیل نخواهند شد.


واکشی اعضای به هم مرتبط

همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمی‌کند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامه‌های وب! چون استفاده‌ی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشت‌های بی‌شماری به بانک اطلاعاتی می‌شود و عموم برنامه نویس‌های وب باید مدام توسط برنامه‌های Profiler بررسی کنند که آیا این مساله رخ داده‌است یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
 var firstPost = context.Post.First();
Console.WriteLine(firstPost.Blog.Url);
چنین خطایی را صادر می‌کند:
 System.NullReferenceException
Object reference not set to an instance of an object.
علت اینجا است که چون Lazy loading غیرفعال است (هنوز در EF Core 1.0 پیاده سازی نشده‌است)، اولین دسترسی به شیء Blog، سبب وهله سازی خودکار آن نشده و این شیء نال است. به همین جهت استثنای فوق را مشاهده می‌کنیم.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading می‌گویند:
 var firstPost = context.Post.Include(x => x.Blog).First();
Console.WriteLine(firstPost.Blog.Url);

نکته‌ای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core

متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، می‌توان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Include(blog => blog.Owner)
                              .ToList();
اما فرض کنید خاصیت Post، دارای یک خاصیت راهبری دیگری به نام Author نیز باشد و می‌خواهیم این خاصیت هم بارگذاری شود:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                      .ThenInclude(post => post.Author)
                              .ToList();
روش انجام چنین کاری در EF Core، توسط متد الحاقی جدید ThenInclude است. ابتدا لیست Blogها عنوان شده‌است. سپس در این لیست علاقمند به واکشی تمام مطالب این بلاگ‌ها هم بوده‌ایم. به علاوه در این مطالب، نیاز است خاصیت Author آن‌ها نیز از پیش مقدار دهی شده و قابل دسترسی باشد. به همین جهت برای دسترسی به چندین سطح مختلف از متد ThenInclude کمک گرفته شده‌است.
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                 .ThenInclude(post => post.Author)
                                        .ThenInclude(author => author.Photo)
                              .ToList();
در این مثال یک سطح دیگر جلو رفته و شیء Photo مربوط به شیء Author را هم واکشی کرده‌ایم.
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                                  .ThenInclude(post => post.Author)
                                      .ThenInclude(author => author.Photo)
                              .Include(blog => blog.Owner)
                                    .ThenInclude(owner => owner.Photo)
                              .ToList();

یک نکته: متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کرده‌ایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر می‌شود؛ مانند کوئری ذیل:
var blogs = context.Blogs
                              .Include(blog => blog.Posts)
                              .Select(blog => new
                               {
                                  Id = blog.BlogId,
                                  Url = blog.Url
                               })
                               .ToList();


تنظیمات حذف آبشاری در رابطه‌ی one-to-many

زمانیکه در رابطه‌ی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف می‌شود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیف‌های فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیت‌هایی اعمال می‌شود که توسط Context بارگذاری شده و در آن وجود دارند. اگر می‌خواهید سایر موجودیت‌های مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
 CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE
و اگر با EF Core بانک اطلاعاتی خود را ایجاد می‌کنید (مباحث مهاجرت‌ها)، این تنظیم به صورت خودکار اعمال خواهد شد؛ اگر DeleteBehavior را به نحو ذیل مشخص کرده باشید:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .OnDelete(DeleteBehavior.Cascade);
ب) SetNull: در این حالت فرزندان وابسته حذف نمی‌شوند و تنها کلید خارجی آن‌ها به نال تنظیم می‌شود.
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمی‌دهد.

یک نکته: به صورت پیش فرض اگر رابطه‌ی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالت‌های Optional، حالت SetNull تنظیم می‌گردد:
modelBuilder.Entity<Post>()
                    .HasOne(p => p.Blog)
                    .WithMany(b => b.Posts)
                    .IsRequired();
در اینجا ذکر صریح متد IsRequired به این معنا است که مقدار دهی کلید خارجی سر دیگر رابطه، اجباری است.
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستون‌ها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته می‌شود. به همین جهت است که در کدهای تولید شده‌ی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمی‌کنید.
بنابراین اگر می‌خواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.
مطالب
چه زمانی بهتر است از Silverlight استفاده شود؟

1- نیاز به توانایی‌های موجود در برنامه‌های Desktop را دارید اما همچنین نیاز است تا آن‌ها را تحت وب نیز ارائه دهید.
یکی از دلایل اقبال به برنامه‌های تحت وب در سازمان‌ها عدم نیاز به نصب آن‌ها و توزیع هر چه ساده‌تر اینگونه برنامه‌ها در شبکه است. تنها کافی است چند فایل را بر روی سرور به روز رسانی کنید و پس از آن تمام کلاینت‌ها از آخرین نگارش برنامه شما بهره‌مند خواهند شد (+). توزیع برنامه‌های سیلورلایت نیز به همین منوال است. علاوه بر آن استفاده از فناورهایی مانند MEF امکان ماژولار ساختن برنامه و دریافت آخرین ماژول‌های تهیه شده (فایل‌های XAP مجزای از برنامه به صورت افزونه) را بر اساس انتخاب و سطح دسترسی کاربر نیز میسر می‌سازد.

2- نیاز است تا یک برنامه‌ی گرافیکی تمام عیار را تحت وب ارائه دهید.
توانایی‌های XAML به همراه یکی از زبان‌های دات نت جهت خلق جلوه‌های بصری، پویانمایی و گرافیکی بسیار بسیار فراتر از کتابخانه‌های جاوا اسکریپتی موجود هستند و نکته‌ی مهم آن‌ها هم این است که لازم نیست حتما یک متخصص مثلا جاوا اسکریپت باشید تا بتوانید برای مثال پویانمایی را ارائه دهید. امکان استفاده از انواع و اقسام قلم‌ها و قرار دادن آن‌ها در برنامه، امکان استفاده از گرافیک برداری و غیره را نیز لحاظ کنید.

3- برنامه‌ی شما نیاز است تا از طریق وب توزیع شود اما نیاز به سطح دسترسی بیشتری نسبت به یک برنامه‌ی وب معمولی دارد.
تمام برنامه‌های توزیع شده از طریق مرورگرها محدود به سطوح دسترسی آن‌ها نیز هستند. اما امکان نصب خارج از مرورگر برنامه‌های سیلورلایت نیز وجود دارد. در این حالت می‌توان در صورت نیاز و همچنین تائید صریح کاربر، به سطوح دسترسی بیشتری دست یافت. برای مثال دسترسی به اسکنر در یک برنامه‌ی وب متداول بی‌معنا است. اما سیلورلایت 4 در حالت اجرای در خارج از مرورگر امکان تعامل با اشیاء COM را نیز دارد.

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

5- از مشکلات مدیریت حالت در برنامه‌های متداول وب به تنگ آمده‌اید.
اگر برای مثال برنامه نویس ASP.NET باشید حتما با مباحث State management آشنایی دارید (از سشن و کوکی گرفته تا ViewState (ایی که همه به نحوی قصد کوچک کردن آن‌را دارند!) و غیره). تمام این‌ها هم برای این است که بتوان تجربه‌ی کاری برنامه‌های دسکتاپ را در محیط مرورگرها شبیه سازی کرد. این مشکلات در سیلورلایت حل شده است. یک برنامه‌ی سیلورلایت State full است نه Stateless . همچنین اگر از حافظه‌ای هم استفاده می‌کند این مورد در سمت کاربر است و نه سمت سرور و نه منقضی شدن زود هنگام سشن‌ها و صدها ترفند برای مقیاس پذیری همین مساله‌ی بسیار کوچک با تعداد کاربران بالا در برنامه‌های متداول وب.
به عبارتی تصور کنید که برنامه‌ی دسکتاپ سال‌های قبل شما هم اکنون داخل مرورگر دارد اجرا می‌شود و چیزی به نام وب سرور وجود ندارد که پس از نمایش صفحه‌ی وب شما، کلیه‌ی اشیاء مرتبط با آن‌را در سمت سرور تخریب کند چون باید پاسخگوی کاربران همزمان بی‌شماری باشد و منابع سرور هم محدود است. (سیلورلایت یک فناوری سمت کاربر است. بنابراین وب سرور صرفا نقش توزیع آن‌را به عهده دارد یا حداکثر ارائه‌ی یک وب سرویس جهت تعاملات بعدی مانند کار با بانک اطلاعاتی)

6- نیاز دارید تا برنامه‌ی وب شما تحت تمام مرورگرها به یک شکل به نظر برسد و همچنین رفتار یکسانی هم داشته باشد.
هیچ وقت روزی را فراموش نمی‌کنم که حین پرداخت الکترونیکی بانک XYZ به کمک مرورگر فایرفاکس، دکمه‌ی پرداخت در مرحله‌ی آخر، کار نمی‌کرد! هر چقدر روی آن کلیک می‌کردم اتفاقی نمی‌افتاد! تراکنش برگشت خورد و همین خرید ساده با مرورگر IE به سادگی انجام شد.
با سیلورلایت این مشکلات را نخواهید داشت زیرا کار نمایش برنامه شما توسط افزونه‌ی مربوطه صورت می‌گیرد و این افزونه مستقل است از نوع مرورگر شما.

7- نیاز است برنامه‌ی وب شما در حالت آفلاین هم کار کند.
برنامه‌های سیلورلایت تنها زمانیکه نیاز به دریافت یا ثبت اطلاعاتی از سرور داشته باشند، باید آنلاین باشند. همچنین این برنامه‌ها دسترسی به مفهوم جدیدی به نام Isolated Storage دارند که در آن می‌توان اطلاعات را به ازای هر کاربر آن هم با ضریب امنیتی بالا بر روی هارد شخص ذخیره کرد و زمان آنلاین شدن برنامه آن‌ها را به سرور انتقال داد.

8- برنامه وب شما نیاز است تا با فایل‌های مالتی مدیا تعامل داشته و آن‌ها را پخش کند.
حتی تگ Video در HTML5 نیز به پای توانایی‌های مالتی مدیا در Silverlight مانند smooth streaming, multicasting, editing, video brushes نمی‌رسد. برای مثال با استفاده از video brushes می‌توان یک فایل ویدیویی در حال پخش را بر روی یک وجه یک شیء در حال پویانمایی نقاشی و نمایش داد.

9- نیاز به پشتیبانی از multi-touch در برنامه‌ی وب شما وجود دارد.
برخلاف HTML ، تعاملات multi-touch در Silverlight میسر است.

10- نیاز به ایجاد برنامه‌های بازی تحت وب دارید.
به طور قطع می‌توان بازیی‌هایی در حد Pong را با جاوا اسکریپت هم ایجاد کرد، اما اگر نیاز به تولید بازی‌هایی جدی‌تر وجود داشت برای مثال انتقال بازی Quake به محیط وب، Silverlight در این زمینه هم حرف‌های زیادی برای گفتن دارد (+).

11- نیاز به تولید برنامه‌ی دسکتاپ چند سکویی دارید.
سیلورلایت هم اکنون تحت ویندوز، MAC OS-X ، لینوکس و ... پشتیبانی می‌شود (+). همچنین برنامه‌های سیلورلایت قابلیت اجرای در خارج از مرورگر را هم دارند.
با سیلورلایت دیگر نیازی نخواهد بود تا کاربران لینوکسی ابتدا Wine را نصب کنند تا بتوانند از یک برنامه‌ی ویندوزی که انتقال پذیر نیست در لینوکس هم بتوانند استفاده کنند؛ چون پروژه‌ی مون لایت لینوکسی برای این منظور مهیا است.

12- نیاز به تولید برنامه‌های تحت وب سریع و با کارآیی بالا دارید.
فایل‌های نهایی Silverlight با توجه به ماهیت کامپایل شده‌ی آن‌ها به طور قطع از کدهای جاوا اسکریپتی سمت کلاینت که باید توسط مرورگر تفسیر و پردازش شوند (و هر کدام هم از موتور خاص خودشان استفاده می‌کنند)، سریعتر اجرا می‌شوند (+).

13- از پیچیدگی‌های پیاده سازی برنامه‌های متداول وب خسته شده‌اید.
هنوز هم با تمام پیشرفت‌های حاصل، تولید برنامه‌های وب پیشرفته مشکل است. از یک طرف ناسازگاری یک سری از مرورگرها با یک سری از قابلیت‌ها را باید در نظر داشت، تا فراگیری فریم ورک‌های Ajax و غیره تا مشکل بودن طراحی کنترل‌های جدید فراتر از آن چیزی که HTML استاندارد ارائه می‌دهد. بله، به طور قطع دانش فنی بالایی در این زمینه در طی سالیان تولید شده است، اما باز هم فراگیری و تسلط به آن‌ها زمان قابل توجهی را طلب می‌کند.
در سیلورلایت کلیه تعاملات با شبکه به صورت پیش فرض غیرهمزمان است (همان ایده‌ی اصلی Ajax) همچنین با توجه به state full بودن اینگونه برنامه‌ها، عملا برنامه نویس‌ها بدون درگیر شدن با مفاهیم اجکسی و مدیریت حالت، برنامه‌ی پیشرفته‌ی وبی را در مدت زمان کوتاهی تولید کرده‌اند و این برنامه در تمام مرورگرهایی که قابلیت بارگذاری افزونه‌ی سیلورلایت را دارند به یک شکل و کیفیت اجرا می‌شود.

14- در زمینه میزان مصرف پهنای باند ملاحظاتی ویژه‌ای وجود دارد.
یک برنامه‌ی سیلورلایت تنها یکبار باید دریافت شود. پس از آن در سمت کاربر کش خواهد شد (تا زمان به روز رسانی بعدی برنامه در سرور). همین مساله در دفعات بعدی مراجعه کاربر به سایت نقش قابل توجهی را در کاهش میزان مصرف پهنای باند (یا به قولی میزان کمتر data transfer) کلی دارد.

15- فرصت کافی برای فراگیری انبوهی از فناوری‌های مختلف را ندارید!
بله! برای ایجاد یک برنامه‌ی تحت وب که کاربر آن پس از مشاهده بگوید WOW نیاز است به HTML ، JS ، CSS ، AJAX ، یکی از فناوری‌های سمت سرور و ... مسلط بود (علاوه بر اینکه باید بدانید فلان کد JS در IE کار می‌کند اما در فایرفاکس خیر. فایرفاکس فلان قسمت CSS را پشتیبانی می‌کند اما IE خیر! و ...).
اما برای استفاده از سیلورلایت فقط کافی است به XAML و یکی از زبان‌های دات نت مانند سی شارپ یا VB.NET مسلط باشید (البته هیچ وقت از دست ASP.NET خلاص نخواهید شد! حداقل در حد راه اندازی یک وب سرویس یا مفاهیم امنیتی آن).
این مورد خصوصا برای افرادی که برنامه نویس دسکتاپ هستند اما علاقمندند تا برنامه‌ی وب نیز تولید کنند بسیار مهم است. با حداقل آموزش می‌توانند توانایی‌های خود را به وب نیز گسترش دهند. علاوه بر آن عمده‌ی دانش Silverlight شما جهت تولید برنامه‌های WPF (با توجه به اینکه Silverlight فرزند WPF محسوب می‌شود) یا Windows phone 7‌ و غیره نیز می‌تواند بکار گرفته شود.

16- نیاز به اجرای کدهای چند ریسمانی در سمت کاربر دارید.
تا این لحظه پشتیبانی رسمی از مباحث چند ریسمانی در JavaScript و استانداردهای مرتبط با آن وجود ندارد. Silverlight به اکثر امکانات Threading موجود در دات نت فریم ورک دسترسی داشته و دانش فعلی شما قابل انتقال است.


و دست آخر باید به نکته اشاره کرد که هدف از Silverlight ساخت وب سایت معمولی نیست. این نوع کارها را با همان ابزارهای متداول انجام دهید. هدف اصلی آن ساخت برنامه است (Application در مقابل Web site). مشتری‌های اصلی این نوع برنامه‌ها هم بیشتر سازمان‌ها و اینترانت‌های پر سرعت و بسته‌ی آن‌ها هستند که نه نگران حجم افزونه‌ی سیلورلایت هستند و نه مشکلی با حجم برنامه‌ی سیلورلایت شما در یک شبکه‌ی داخلی پر سرعت دارند.

نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
سلام
من برای این سری کار‌ها از ویندوز سرویس استفاده می‌کنم. مثلا ویندوز سرویس من از ساعت 8 صبح شروع به کار می‌کنه و رویدادهایی مثل سالروز تولد رو با استفاده از پیامک به کاربران پیام تبریک ارسال می‌کنه.
مهمترین عاملی که باعث شد من از ویندوز سرویس استفاده کنم اجرای مداوم وهمیشگی بدون ارسال درخواست به وب سایت من بود. ولی فکر می‌کنم این کتابخانه شما هم مثل ویندوز سرویس عمل می‌کنه و خودش همیشه در حال اجراست.
حالا به نظرتون آیا از ویندوز سرویس استفاده کنم بهتره و یا اینکه از این کتابخانه استفاده کنم؟
ممنون
اشتراک‌ها
آموزش استفاده از گوگل وب مستر Google Webmaster

سرویس Webmaster Tools گوگل یکی از قدرتمندترین و کاربردی‌ترین ابزارهای مدیریت و آنالیز وب سایت برای وبمسترهاست. استفاده از این ابزار قدرتمند امکان بررسی روزانه وضعیت وبسایت را به شما می‌دهد .

آموزش استفاده از گوگل وب مستر Google Webmaster