مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت دوم - تنظیم مقادیر بازگشتی متدها
در قسمت قبل با مفاهیمی مانند fakes ،stubs ،dummies و mocks آشنا شدیم و در اولین آزمایشی که نوشتیم، کار تدارک dummies را به عنوان پارامترهای سازنده‌ی سرویس مورد بررسی، توسط کتابخانه‌ی Moq و اشیاء <Mock<T آن انجام دادیم؛ پارامترهایی که ذکر آن‌ها ضروری بودند، اما در آزمایش ما مورد استفاده قرار نمی‌گرفتند. در این قسمت می‌خواهیم کار تدارک stubs را توسط کتابخانه‌ی Moq انجام دهیم؛ به عبارتی می‌خواهیم مقادیر بازگشتی از متدهای اشیاء Mock شده را تنظیم و کنترل کنیم.


تنظیم خروجی متدهای اشیاء Mock شده

در انتهای قسمت قبل، آزمون واحد متد Accept، با شکست مواجه شد؛ چون متد Validate استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

در ادامه شیء Mock از نوع IIdentityVerifier را طوری تنظیم خواهیم کرد که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا ابتدا کار با شیء Mock شده آغاز می‌شود. سپس باز ذکر متد Setup، می‌توان به صورت strongly typed به تمام متدهای اینترفیس IIdentityVerifier دسترسی یافت و آن‌ها را تنظیم کرد. تا اینجا متد مدنظر را از اینترفیس IIdentityVerifier انتخاب کردیم. سپس توسط متد Returns، خروجی دقیقی را برای آن مشخص می‌کنیم.
به این ترتیب زمانیکه در متد Process کلاس LoanApplicationProcessor کار به بررسی هویت کاربر می‌رسد، اگر متد Validate آن با اطلاعات applicant مشخصی که تنظیم کردیم، یکی بود، متغیر isValidIdentity که حاصل بررسی identityVerifier.Validate_ است، به true مقدار دهی خواهد شد. برای بررسی آن یک break-point را در این نقطه قرار داده و آزمون واحد را در حالت دیباگ اجرا کنید.
البته هرچند اگر اکنون نیز این آزمایش واحد را مجددا بررسی کنیم، باز هم با شکست مواجه خواهد شد؛ چون مرحله‌ی بعدی بررسی، کار با سرویس ICreditScorer است که هنوز تنظیم نشده‌است:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
فعلا این قسمت از code را comment می‌کنیم تا آزمایش واحد ما با موفقیت به پایان برسد. در قسمت بعدی کار تنظیم مقادیر خواص را انجام داده و این قسمت از code را نیز پوشش خواهیم داد.


تطابق با آرگومان‌های متدها در متدهای Mock شده

با تنظیمی که انجام دادیم، اگر متد Validate به مشخصات شیء applicant مشخص ما برسد، خروجی true را بازگشت می‌دهد. برای مثال اگر در این بین تنها نام شخص تغییر کند، خروجی بازگشت داده شده همان false خواهد بود. اما اگر این نام برای ما اهمیتی نداشت و قصد داشتیم با تمام نام‌های متفاوتی که دریافت می‌کند، بازهم خروجی true را بازگشت دهد، می‌توان از قابلیت argument matching کتابخانه‌ی Moq و کلاس It آن استفاده کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        applicant.Age, 
        applicant.Address))
    .Returns(true);
()<It.IsAny<string در اینجا به این معنا است که هر نوع ورودی رشته‌ای، قابل قبول بوده و دیگر متد Validate بر اساس یک نام مشخص، مورد بررسی قرار نمی‌گیرد. IsAny یک متد جنریک است و بر اساس نوع آرگومان مدنظر که برای مثال در اینجا رشته‌ای است، نوع جنریک آن مشخص می‌شود.
بدیهی است در این حالت باید سایر پارامترها دقیقا با مقادیر مشخص شده تطابق داشته باشند و اگر این موارد نیز اهمیتی نداشتند، می‌توان به صورت زیر عمل کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        //applicant.Age,
        It.IsAny<int>(),
        //applicant.Address
        It.IsAny<string>()
        ))
    .Returns(true);
در این حالت متد Validate، صرفنظر از ورودهای آن، همواره مقدار true را باز می‌گرداند.
البته این نوع تنظیمات بیشتر برای حالات غیرمشخص مانند استفادهاز Guidها به عنوان پارامترها و مقادیر، می‌تواند مفید باشد.


تقلید متدهایی که پارامترهایی از نوع out دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع out است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر isValid، دریافت می‌شود. برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم:
//var isValidIdentity = _identityVerifier.Validate(
//    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
_identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
    out var isValidIdentity);
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم:
var isValidOutValue = true;
mockIdentityVerifier.Setup(x => x.Validate(applicant.Name,
    applicant.Age,
    applicant.Address,
    out isValidOutValue));
برای تنظیم متدهایی که پارامترهایی از نوع out دارند، باید ابتدا مقدار مورد انتظار را مشخص کرد. بنابراین مقدار آن‌را به true در اینجا تنظیم کرده‌ایم. سپس در متد Setup، متدی تنظیم شده‌است که پارامتری از نوع out دارد. در آخر نیازی به ذکر متد Returns نیست؛ چون خروجی متد از نوع void است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تقلید متدهایی که پارامترهایی از نوع ref دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع ref است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {        
          void Validate(string applicantName, int applicantAge, string applicantAddress,
                             ref IdentityVerificationStatus status);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر status، دریافت می‌شود و نوع آن به صورت زیر تعریف شده‌است تا وضعیت تعیین هویت شخص را مشخص کند:
namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}
 برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم تا بتوان به نمونه‌ی وهله سازی شده‌ی status دسترسی یافت:
IdentityVerificationStatus status = null;
  _identityVerifier.Validate(
      application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
      ref status);

if (!status.Passed)
{
    return application.IsAccepted;
}
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم که با متدهای out دار مقداری متفاوت است:
ابتدا در سطح کلاس آزمایش واحد یک delegate را تعریف می‌کنیم:
delegate void ValidateCallback(string applicantName,
    int applicantAge,
    string applicantAddress,
    ref IdentityVerificationStatus status);
این delegate دقیقا دارای همان پارامترهای متد Validate در حال بررسی است.
اکنون روش استفاده‌ی از آن برای برپایی تنظیمات mocking متد Validate از نوع ref دار به صورت زیر است:
mockIdentityVerifier
    .Setup(x => x.Validate(applicant.Name,
        applicant.Age,
        applicant.Address,
        ref It.Ref<IdentityVerificationStatus>.IsAny))
    .Callback(new ValidateCallback(
        (string applicantName,
         int applicantAge,
         string applicantAddress,
         ref IdentityVerificationStatus status) =>
            status = new IdentityVerificationStatus {Passed = true}));
تنظیمات قسمت Setup آن آشنا است؛ بجز قسمت ref آن که از It.Ref<IdentityVerificationStatus>.IsAny استفاده کرده‌است. چون نوع پارامتر، ref است، باید از It.Ref استفاده کرد که به نوع بازگشت داده شده‌ی IdentityVerificationStatus اشاره می‌کند. IsAny آن هم هر نوع ورودی از این دست را می‌پذیرد.
سپس متد جدید Callback را مشاهده می‌کنید. توسط آن می‌توان یک قطعه کد سفارشی را زمانیکه متد Mock شده‌ی Validate ما اجرا می‌شود، اجرا کرد. در اینجا delegate سفارشی ما اجرا شده و مقدار status را بر می‌گرداند؛ اما در ادامه این مقدار را به یک new IdentityVerificationStatus سفارشی تنظیم می‌کنیم که در آن مقدار خاصیت Passed، مساوی true است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تنظیم متدهای Mock شده جهت بازگشت null

فرض کنید اینترفیسی به صورت زیر تعریف شده‌است:
namespace Loans.Services.Contracts
{
    public interface INullExample
    {
        string SomeMethod();
    }
}
و اگر بخواهیم برای آن آزمون واحدی را بنویسیم که خروجی این متد به صورت مشخصی نال باشد، می‌توان تنظیمات Moq آن‌را به صورت زیر انجام داد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void NullReturnExample()
        {
            var mock = new Mock<INullExample>();

            mock.Setup(x => x.SomeMethod());
            //.Returns<string>(null);

            string mockReturnValue = mock.Object.SomeMethod();

            Assert.IsNull(mockReturnValue);
        }
    }
}
در اینجا دو روش را برای بازگشت نال ملاحظه می‌کنید:
الف) می‌توان همانند سابق متد Returns را ذکر کرد که نال بر می‌گرداند؛ اما با این تفاوت که حتما باید نوع آرگومان جنریک آن‌را نیز بر اساس خروجی متد، مشخص کرد.
ب) کتابخانه‌ی Moq، مقدار خروجی پیش‌فرض تمام متدهایی را که یک نوع ارجاعی را باز می‌گردانند، نال درنظر می‌گیرد و عملا نیازی به ذکر متد Returns در اینجا نیست.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-02.zip
نظرات مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
سلام؛  در مثال فوق وفتی میخوام یک رکور جدید درج کنم خطای Internal Server Error  میده!
راستی این تکه کد منظور از مقدار name چیه؟
          toolbar: [
                   { name: "create", text: "افزودن ردیف جدید" },
                   { name: "save", text: "ذخیره‌ی تمامی تغییرات" },
                   { name: "cancel", text: "لغو کلیه‌ی تغییرات" },
                   { template: kendo.template($("#toolbarTemplate").html()) }
            ],
نظرات مطالب
آشنایی با Refactoring - قسمت 4
در مورد Active Record موافقم و هرگز هم از این الگو استفاده نکرده‌ام. در مورد Attached Property هم با مکانیزم آن آشنا هستم و استفاده‌های متفاوت تری از Layouting همچون اعمال Security توسط AP بر روی یک المان را تجربه کردم که انصافاً طراحی هوشمندانه AP را برایم آشکار کرد اما تصور می‌کنم برخی از موارد مانند hiding و حتی overriding در مورد AP و Dependency Property ها رعایت نمی‌شود (شاید انتظار overriding درست نباشد جون AP و DP ففط مخصوص ذخیره‌سازی هستند). مثلا شما می‌توانید با دستور GetValue مقدار یک خصیصه از جنس DP یک شی را در خارج از آن شی به دست آورید در حالی که اگر بخواهید از خاصیت Binding استفاده کنید استفاده از DP توصیه شده. البته امتحان نکرده‌ام اما فکر می‌کنم با protected کردن متغییرهای static مربوط به DP به این حالت دست یافت اما فکر می‌کنم بازهم از سطح private محروم می‌مانیم.

در نهایت در مورد Active Record و Entity ها صحبتهایتان را با این جمله کامل کردید که: «این نوع کلاس‌ها فقط اهداف display / reference دارند و نه بیشتر ... این‌ کلاس‌ها (کلاسهایی که در پست تعریف شده بود/م) هیچکدام در رده تعریف POCO یا Plain old .Net classes قرار نمی‌گیرند.»
مطالب
استفاده از IIS Express 7.5 در VS.NET

استفاده از IIS در VS.NET و پروژه‌های ASP.NET داستان خودش را دارد. در نگارش‌های 2002 و 2003 آن، تنها وب سرور قابل استفاده جهت کار با VS.NET همان IIS اصلی بود. مهم‌ترین مشکل این روش، نیاز به داشتن دسترسی مدیریتی بر روی سیستم بود (که در بعضی از شرکت‌ها، این مورد برای عموم کاربران ممنوع است) به همراه نصب جداگانه‌ و تنظیمات مخصوص IIS ، صرفا جهت آزمایش یک برنامه‌ی ساده؛ همچنین با توجه به اینکه IIS جزو کامپوننت‌ها ویندوز بوده و هر نگارشی، IIS خاص خودش را دار است، این مورد هم مشکلات ویژه‌ای را به همراه دارد (برای مثال IIS5 ویندوز XP را با IIS7 ویندوز سرور 2008 در نظر بگیرید؛ یکی برای توسعه یکی جهت محیط کاری). این روش در VS.Net 2005 کنار گذاشته شد و از وب سرور توکاری به نام Cassini یا ASP.NET Development Server استفاده گردید. به این صورت دیگر نیازی به نصب مجزای IIS کامل جهت آزمایش‌ برنامه‌های ASP.NET نبود و همچنین نیاز به داشتن دسترسی مدیریتی الزامی نیز منتفی گردید. این روش هنوز هم تا نگارش 2010 ویژوال استودیو مرسوم است؛ اما ... اما کسانی که با Cassini کار کرده باشند می‌دانند که یک سری از رفتار‌های آن با IIS واقعی تطابق ندارد و اگر برنامه‌ی ASP.NET شما با Cassini خوب نمایش داده می‌شود الزامی ندارد که با IIS واقعی هم به همان نحو رفتار کند، برای نمونه رفتار مسیریابی آدرس‌های نسبی در IIS واقعی و Cassini یکی نیست. علاوه بر آن IIS های 7 و 7.5 هم امکانات و ویژگی‌های خاص خود را دارند که Cassini آن‌ها را پوشش نمی‌دهد؛ به علاوه این دو فقط در ویندوزهای جدید مانند ویندوز سرور 2008 یا ویندوز 7 قابل دسترسی هستند. به همین جهت اخیرا یک نسخه‌ی سبک و express از IIS 7.5 به صورت جداگانه برای برنامه نویس‌ها فقط جهت آزمودن برنامه‌های خود تهیه شده‌ است و البته هدفگیری اصلی آن پروژه‌ی WebMatrix است؛ به همراه ویژگی‌های جدید IIS7 مانند امکان آزمودن تنظیمات ویژه IIS7 در وب کانفیگ برنامه، پشتیبانی کامل از SSL ، Url Rewrite و سایر ماژول‌های IIS7، عدم نیاز به دسترسی مدیریتی برای اجرای آن، امکان اجرای آن بر روی پورت‌های مختلف بدون تداخل با وب سرور(های) موجود بر روی سیستم و همچنین برخلاف IIS7 اصلی، بر روی ویندوز XP نیز قابل اجرا است. حجم نگارش IIS Express 7.5 تنها 3.9 مگابایت است:


سرویس پک یک ویژوال استودیوی 2010 (که در زمان نگارش این مطلب نسخه‌ی بتای آن ارائه شده)، یک گزینه‌ی جدید را به منوی کلیک راست بر روی نام پروژه در VS.NET به نام Use IIS Express ، اضافه کرده است تا به سادگی بتوان از این امکان جدید استفاده کرد (یا به عبارتی با IIS Express یکپارچه است و نیاز به تنظیم خاصی ندارد).
در سایر حالات (و نسخه‌هایی که این یکپارچگی وجود ندارد و نخواهد داشت) به صورت زیر می‌توان عمل کرد:
روش اول:
دستور زیر را در خط فرمان وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /path:D:\Prog\1389\MySite\ /port:4326 /clr:v4.0
به این صورت وب سروری جهت ارائه‌ی سایتی با مسیر ذکر شده بر روی پورت 4326 (http://localhost:4326/) بر اساس دات نت 4 تشکیل خواهد شد (برای نمونه جهت دات نت سه و نیم مقدار v3.5 را وارد نمائید).

روش دوم (که در حقیقت همان روش اول با ارائه‌ی پشت صحنه‌ی موقت آن است):
الف) ابتدا به مسیر My Documents\IISExpress\config مراجعه کرده و فایل applicationhost.config را باز کنید. سپس گره مربوط به site را یافته (حدود سطر 153) و گزینه‌ی serverAutoStart را حذف کنید:
<site name="WebSite1" id="1">
<application path="/">
<virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":8080:localhost" />
</bindings>
</site>
ب) سپس تنظیمات سایت مورد نظر خود را به صورت دستی به این فایل اضافه کنید. برای مثال:
<site name="WebSite2" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="D:\Prog\1389\MyTestSite\" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":1389:localhost" />
</bindings>
</site>
توضیحات:
Name در اینجا نامی دلخواه است که وارد خواهید نمود.
Id شماره سایتی است که ثبت خواهد شد.
applicationPool در اینجا بسیار مهم است. اگر سایت شما مبتنی بر دات نت 4 است، Clr4IntegratedAppPool را وارد نمائید و اگر غیر از این است، Clr2IntegratedAppPool باید تنظیم شود.
physicalPath همان مسیر پروژه شما است.
در قسمت bindingInformation هم می‌توان شماره پورت مورد نظر را وارد کرد.

اکنون فایل applicationhost.config را ذخیره کرده و ببندید.
سپس دستور زیر را در خط فرمان ویندوز وارد نمائید:
"C:\Program Files\IIS Express\iisexpress.exe" /site:WebSite2
که در اینجا WebSite2 همان مدخل جدیدی است که به فایل applicationhost.config اضافه شده است. به این صورت آدرس http://localhost:1389/ جهت دسترسی به سایت شما آماده استفاده خواهد بود.

تنظیمات دیباگر VS.NET :
تا اینجا تنها موفق شده‌ایم که این وب سرور آزمایشی را راه اندازی کنیم. اما نکته‌ی مهم امکان دیباگ کردن برنامه توسط آن‌را از دست داده‌ایم. برای این منظور در VS.NET به خواص پروژه، برگه‌ی Web آن مراجعه کنید. در قسمت Servers گزینه‌ی use custom web server را انتخاب کرده و آدرسی را که در یکی از دو روش فوق ساخته‌اید وارد نمایید. برای مثال http://localhost:4326/
همچنین باید دقت داشت که در همین قسمت هیچکدام از debuggers ذیل گزینه‌ی use custom web server نباید تیک خورده باشند (چون VS.NET دقیقا نمی‌داند که باید به کدام پروسه در ویندوز attach شود).
اکنون برنامه را در حالت دیباگ در VS.NET آغاز کنید (بدیهی است فرض بر این است که iisexpress.exe با تنظیمات ذکر شده باید در حال اجرا باشد).
و ... حداقل مزیت آن بسیار سریع‌تر بودن این روش نسبت به Cassini یا ASP.NET Development Server است.
تا اینجا فقط VS.NET به صورت خودکار مرورگر را باز کرده و سایت نمایش داده می‌شود؛ اما اگر در قسمتی از کدهای خود breakpoint قرار دهیم کار نمی‌کند. برای این منظور باید در حین اجرای برنامه، از منوی debug ، گزینه‌ی attach to process را انتخاب کرده و به iisexpress متصل شوید.

نظرات مطالب
ASP.NET MVC #12
می‌تونید داخل templated helper ایی که تعریف می‌کنید یک
Html.TextBox اضافه کنید که پارامتر html attributes آن مقدار دهی شده است. چون برای نمونه این datePicker مثلا به TextBox ایی با class خاص اعمال می‌شود. مثلا: new { @class = "datepicker"
مطالب
پشتیبانی از حذف و به‌روز رسانی دسته‌ای رکوردها در EF 7.0
همواره حذف و به روز رسانی تعداد زیادی رکورد توسط EF، بسیار غیربهینه و کند بوده‌است؛ از این جهت که یکی از روش‌های انجام اینکار، کوئری گرفتن از رکوردهای مدنظر جهت حذف، سپس بارگذاری آن‌ها در حافظه و در آخر حذف یکی یکی آن‌ها بوده‌است:
using var dbContext = new MyDbContext();
var objectToDelete = await dbContext.Objects.FirstAsync(o => o.Id == id);
dbContext.Objects.Remove(objectToDelete);
await dbContext.SaveChangesAsync();
در اینجا در ابتدا، شیء‌ای که قرار است حذف شود، از بانک اطلاعاتی کوئری گرفته می‌شود تا وارد سیستم Change Tracking شود. سپس از این سیستم ردیابی اطلاعات درون حافظه‌ای، حذف خواهد شد و در نهایت این تغییرات به بانک اطلاعاتی اعمال می‌شوند. بنابراین در این مثال ساده، حداقل دوبار رفت و برگشت به بانک اطلاعاتی وجود خواهد داشت.
البته راه دومی نیز برای انجام اینکار وجود دارد:
using var dbContext = new MyDbContext();
var objectToDelete = new MyObject { Id = id };
dbContext.Objects.Remove(objectToDelete);
await dbContext.SaveChangesAsync();
در این مثال، رفت و برگشت ابتدایی، حذف شده‌است و با فرض معلوم بودن کلید اصلی رکورد مدنظر، آن‌را وارد سیستم Change Tracking کرده و درنهایت آن‌را حذف می‌کنیم. کار متد Remove در اینجا، علامتگذاری این شیء دارای Id، به صورت EntityState.Deleted است.

اکنون می‌توان در EF 7.0، روش سومی را نیز به این لیست اضافه کرد که فقط یکبار رفت و برگشت به بانک اطلاعاتی را سبب می‌شود:
await dbContext.Objects.Where(x => x.Id == id).ExecuteDeleteAsync();


معرفی متدهای حذف و به‌روز رسانی دسته‌ای رکوردها در EF 7.0

EF 7.0 به همراه دو متد جدید ExecuteUpdate و ExecuteDelete (و همچنین نگارش‌های async آن‌ها) است که کار به‌روز رسانی و یا حذف دسته‌ای رکوردها را بدون دخالت سیستم Change tacking میسر می‌کنند. مزیت مهم این روش، عدم نیاز به کوئری گرفتن از بانک اطلاعاتی جهت بارگذاری رکوردهای مدنظر در حافظه و سپس حذف یکی یکی آن‌ها است. فقط باید دقت داشت که چون این روش خارج از سیستم Change tracking صورت می‌گیرد، نتیجه‌ی حاصل، دیگر با اطلاعات درون حافظه‌ای سمت کلاینت، هماهنگ نخواهد بود و کار به روز رسانی دستی آن‌ها به‌عهده‌ی شماست.


بررسی نحوه‌ی عملکرد ExecuteUpdate و ExecuteDelete با یک مثال

فرض کنید مدل‌های موجودیت‌های برنامه شامل کلاس‌های زیر هستند:
public class User
{
    public int Id { get; set; }
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public virtual List<Book> Books { get; set; } = new();
    public virtual Address? Address { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public required string Type { get; set; }
    public required string Name { get; set; }

    public virtual User User { get; set; } = default!;
    public int UserId { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public required string Street { get; set; }
    
    public virtual User User { get; set; } = default!;
    public int UserId { get; set; }
}
که در اینجا یک کاربر می‌تواند دارای یک آدرس و چندین کتاب تعریف شده باشد؛ با این Context ابتدایی:
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    public DbSet<User> Users { get; set; } = default!;

    public DbSet<Book> Books { get; set; } = default!;

    public DbSet<Address> Addresses { get; set; } = default!;
}

مثال 1: حذف دسته‌ای تعدادی کتاب
context.Books.Where(book => book.Name.Contains("1")).ExecuteDelete();
در اینجا نحوه‌ی استفاده از متد ExecuteDelete را مشاهده می‌کنید که به انتهای LINQ Query، اضافه شده‌است. در این مثال، تمام کتاب‌هایی که در نامشان حرف 1 وجود دارد، حذف می‌شوند. این کوئری، به صورت زیر بر روی بانک اطلاعاتی اجرا می‌شود:
DELETE FROM [b]
FROM [Books] AS [b]
WHERE [b].[Name] LIKE N'%1%'
مهم‌ترین مزیت این روش، عدم نیاز به بارگذاری و یا ساخت درون حافظه‌ای لیست کتاب‌هایی است که قرار است حذف شوند. کل این عملیات در یک رفت و برگشت ساده و سریع انجام می‌شود.

یک نکته: متد ExecuteDelete، تعداد رکوردهای حذف شده را نیز بازگشت می‌دهد.


مثال 2: حذف کاربران و تمام رکوردهای وابسته به آن

فرض کنید می‌خواهیم تعدادی از کاربران را از بانک اطلاعاتی حذف کنیم:
context.Users.Where(user => user.Id <= 500).ExecuteDelete();
اگر این کوئری را با تنظیمات فعلی اجرا کنیم، با خطای زیر متوقف خواهیم شد:
DELETE FROM [u]
FROM [Users] AS [u]
WHERE [u].[Id] <= 500

The DELETE statement conflicted with the REFERENCE constraint "FK_Books_Users_UserId".
The conflict occurred in database "EF7BulkOperations", table "dbo.Books", column 'UserId'.
عنوان می‌کند که یک کاربر، دارای تعدادی کتاب و آدرسی از پیش ثبت شده‌است و نمی‌توان آن‌را بدون حذف وابستگی‌های آن، حذف کرد. اگر کاربری را حذف کنیم، کلید‌های خارجی ذکر شده‌ی در جداولی که این کلید خارجی را به همراه دارند، غیرمعتبر می‌شوند (و این کلید خارجی تعریف شده، نال پذیر هم نیست). برای رفع این مشکل، یا باید ابتدا در طی دستوراتی جداگانه، وابستگی‌های ممکن را حذف کنیم و یا می‌توان تنظیم cascade delete را به نحو زیر به تعریف جداول مرتبط اضافه کرد تا صدور یک دستور delete، به صورت خودکار سبب حذف وابستگی‌های مرتبط نیز شود:
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    public DbSet<User> Users { get; set; } = default!;

    public DbSet<Book> Books { get; set; } = default!;

    public DbSet<Address> Addresses { get; set; } = default!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder
            .Entity<User>()
            .HasMany(user => user.Books)
            .WithOne(book => book.User)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder
            .Entity<User>()
            .HasOne(user => user.Address)
            .WithOne(address => address.User)
            .HasForeignKey<Address>(address => address.UserId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}
همانطور که ملاحظه می‌کنید، به متد OnModelCreating تنظیم cascade delete وابستگی‌های جدول کاربران اضافه شده‌است. پس از این تنظیم، دستور مثال دوم، بدون مشکل اجرا شده و حذف یک کاربر، سبب حذف خودکار کتاب‌ها و آدرس او نیز می‌شود.


مثال 3: به‌روز رسانی دسته‌ای از کاربران

فرض کنید می‌خواهیم LastName تعدادی کاربر مشخص را به مقدار جدید Updated، تغییر دهیم:
context.Users.Where(user => user.Id <= 400)
   .ExecuteUpdate(p => p.SetProperty(user => user.LastName,  user => "Updated"));
برای اینکار، پس از مشخص شدن شرط کوئری در قسمت Where، کار به روز رسانی توسط متد ExecuteUpdate و سپس متد SetProperty صورت می‌گیرد. در اینجا در ابتدا مشخص می‌کنیم که کدام خاصیت قرار است به روز رسانی شود و پارامتر دوم آن، مقدار جدید را مشخص می‌کند. این کوئری به نحو زیر به بانک اطلاعاتی اعمال خواهد شد:
UPDATE [u]
SET [u].[LastName] = N'Updated'
FROM [Users] AS [u]
WHERE [u].[Id] <= 400
در اینجا می‌توان در پارامتر دوم متد SetProperty، از مقدار فعلی سایر خواص نیز استفاده کرد:
context.Users.Where(user => user.Id <= 300)
  .ExecuteUpdate(p => p.SetProperty(user => user.LastName,
      user => "Updated" + user.LastName));
که خروجی زیر را تولید می‌کند:
UPDATE [u]
SET [u].[LastName] = N'Updated' + [u].[LastName]
FROM [Users] AS [u]
WHERE [u].[Id] <= 300
همچنین می‌توان چندین متد SetProperty را نیز به صورت زنجیروار، جهت به روز رسانی چندین خاصیت و فیلد، ذکر کرد:
context.Users.Where(user => user.Id <= 800)
   .ExecuteUpdate(p => p.SetProperty(user => user.LastName,
        user => "Updated" + user.LastName)
             .SetProperty(user => user.FirstName,
                 user => "Updated" + user.FirstName));
با این خروجی نهایی:
UPDATE [u]
SET [u].[FirstName] = N'Updated' + [u].[FirstName],
[u].[LastName] = N'Updated' + [u].[LastName]
FROM [Users] AS [u]
WHERE [u].[Id] <= 800
متد ExecuteUpdate، تعداد رکوردهای به‌روز رسانی شده را نیز بازگشت می‌دهد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید:  EF7BulkOperations.zip
بازخوردهای پروژه‌ها
چند متد الحاقی پیشنهادی
با سلام
جناب آقای اسم رام من از این چند متد الحاقی خیلی استفاده می‌کنم شاید اگر به این پروژه اضافه شود

using System;
using System.Text;
using System.Xml.Linq;

namespace DotNetTips
{
    public static class ExtentionMethods
    {
        public static string Set_PersianCharacter(this string value)
        {
            return value.Replace('ی', 'ی').Replace('ک', 'ک').Replace('ة', 'ت').Replace('إ', 'ا').Replace('أ', 'ا').Replace('ؤ', 'و');
        }

        public static string Format(this string value, params object[] param)
        {
            //For Example :  "test {0} , {1}, {2} , {3}".Format(10,11,12,13);    return => test 10 , 11, 12 , 13

            return string.Format(value, param);
        }

        public static string Apend(this string text, string value)
        {
            return new StringBuilder(text).Append(value).ToString();
        }

        public static string ApendFormat(this string text, string value, params object[] param)
        {
            return new StringBuilder(text).AppendFormat(value, param).ToString();
        }

        public static int ToInt(this string value)
        {
            return Convert.ToInt32(value);
        }

        public static bool IsNullOrEmpty(this string value)
        {
            return string.IsNullOrEmpty(value.Trim());
        }

        public static bool IsNull(this object obj)
        {
            return obj == null;
        }

        public static XElement AddElement(this XElement element, XElement subElement)
        {
            /* For Example :
            var element = new XElement("Root").AddElement(new XElement("element1")).AddElement(new XElement("element2")); 
            
            return => <Root>
                         <element1 />
                         <element2 />
                      </Root>
            */
            element.Add(subElement);
            return element;
        }
        public static XElement SetAttribute(this XElement element, XName name, object value)
        {
            //For Example : var elementt = new XElement("Root").SetAttribute("name", "").SetAttribute("Id", 10);    return => <Root name="" url="10" />

            element.SetAttributeValue(name, value);
            return element;
        }
    }
}


مطالب
Attribute Routing در ASP.NET MVC 5
Routing مکانیزم مسیریابی ASP.NET MVC است، که یک URI را به یک اکشن متد نگاشت می‌کند. MVC 5 نوع جدیدی از مسیر یابی را پشتیبانی میکند که Attribute Routing یا مسیریابی نشانه ای نام دارد. همانطور که از نامش پیداست، مسیریابی نشانه ای از Attribute‌ها برای این امر استفاده میکند. این روش به شما کنترل بیشتری روی URI‌های اپلیکیشن تان می‌دهد.
مدل قبلی مسیریابی (conventional-routing) هنوز کاملا پشتیبانی می‌شود. در واقع می‌توانید هر دو تکنیک را بعنوان مکمل یکدیگر در یک پروژه استفاده کنید.
در این پست قابلیت‌ها و گزینه‌های اساسی مسیریابی نشانه ای را بررسی میکنیم.
  • چرا مسیریابی نشانه ای؟
  • فعال سازی مسیریابی نشانه ای
  • پارامتر‌های اختیاری URI و مقادیر پیش فرض
  • پیشوند مسیر ها
  • مسیر پیش فرض
  • محدودیت‌های مسیر ها
      • محدودیت‌های سفارشی
  • نام مسیر ها
  • ناحیه‌ها (Areas)


چرا مسیریابی نشانه ای

برای مثال یک وب سایت تجارت آنلاین بهینه شده اجتماعی، می‌تواند مسیرهایی مانند لیست زیر داشته باشد:
  • {productId:int}/{productTitle}
نگاشت می‌شود به: (ProductsController.Show(int id
  • {username}
نگاشت می‌شود به: (ProfilesController.Show(string username
  • {username}/catalogs/{catalogId:int}/{catalogTitle}
نگاشت می‌شود به: (CatalogsController.Show(string username, int catalogId
در نسخه قبلی ASP.NET MVC، قوانین مسیریابی در فایل RouteConfig.cs تعریف می‌شدند، و اشاره به اکشن‌های کنترلرها به نحو زیر انجام می‌شد:
routes.MapRoute(
    name: "ProductPage",
    url: "{productId}/{productTitle}",
    defaults: new { controller = "Products", action = "Show" },
    constraints: new { productId = "\\d+" }
);
هنگامی که قوانین مسیریابی در کنار اکشن متدها تعریف می‌شوند، یعنی در یک فایل سورس و نه در یک کلاس پیکربندی خارجی، درک و فهم نگاشت URI‌ها به اکشن‌ها واضح‌تر و راحت می‌شود. تعریف مسیر قبلی، می‌تواند توسط یک attribute ساده بدین صورت نگاشت شود:
[Route("{productId:int}/{productTitle}")]
public ActionResult Show(int productId) { ... }

فعال سازی Attribute Routing

برای فعال سازی مسیریابی نشانه ای، متد MapMvcAttributeRoutes را هنگام پیکربندی فراخوانی کنید.
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        routes.MapMvcAttributeRoutes();
    }
}
همچنین می‌توانید مدل قبلی مسیریابی را با تکنیک جدید تلفیق کنید.
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    routes.MapMvcAttributeRoutes();
 
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

پارامترهای اختیاری URI و مقادیر پیش فرض

می توانید با اضافه کردن یک علامت سوال به پارامترهای مسیریابی، آنها را optional یا اختیاری کنید. برای تعیین مقدار پیش فرض هم از فرمت parameter=value استفاده می‌کنید.
public class BooksController : Controller
{
    // eg: /books
    // eg: /books/1430210079
    [Route("books/{isbn?}")]
    public ActionResult View(string isbn)
    {
        if (!String.IsNullOrEmpty(isbn))
        {
            return View("OneBook", GetBook(isbn));
        }
        return View("AllBooks", GetBooks());
    }
 
    // eg: /books/lang
    // eg: /books/lang/en
    // eg: /books/lang/he
    [Route("books/lang/{lang=en}")]
    public ActionResult ViewByLanguage(string lang)
    {
        return View("OneBook", GetBooksByLanguage(lang));
    }
}
در این مثال، هر دو مسیر books/ و books/1430210079/ به اکشن متد "View" نگاشت می‌شوند، مسیر اول تمام کتاب‌ها را لیست میکند، و مسیر دوم جزئیات کتابی مشخص را لیست می‌کند. هر دو مسیر books/lang/ و books/lang/en/ به یک شکل نگاشت می‌شوند، چرا که مقدار پیش فرض این پارامتر en تعریف شده.



پیشوند مسیرها (Route Prefixes)

برخی اوقات، تمام مسیرها در یک کنترلر با یک پیشوند شروع می‌شوند. بعنوان مثال:
public class ReviewsController : Controller
{
    // eg: /reviews
    [Route("reviews")]
    public ActionResult Index() { ... }
    // eg: /reviews/5
    [Route("reviews/{reviewId}")]
    public ActionResult Show(int reviewId) { ... }
    // eg: /reviews/5/edit
    [Route("reviews/{reviewId}/edit")]
    public ActionResult Edit(int reviewId) { ... }
}
همچنین می‌توانید با استفاده از خاصیت [RoutePrefix] یک پیشوند عمومی برای کل کنترلر تعریف کنید:
[RoutePrefix("reviews")]
public class ReviewsController : Controller
{
    // eg.: /reviews
    [Route]
    public ActionResult Index() { ... }
    // eg.: /reviews/5
    [Route("{reviewId}")]
    public ActionResult Show(int reviewId) { ... }
    // eg.: /reviews/5/edit
    [Route("{reviewId}/edit")]
    public ActionResult Edit(int reviewId) { ... }
}
در صورت لزوم، می‌توانید برای بازنویسی (override) پیشوند مسیرها از کاراکتر ~ استفاده کنید:
[RoutePrefix("reviews")]
public class ReviewsController : Controller
{
    // eg.: /spotlight-review
    [Route("~/spotlight-review")]
    public ActionResult ShowSpotlight() { ... }
 
    ...
}

مسیر پیش فرض

می توانید خاصیت [Route] را روی کنترلر اعمال کنید، تا اکشن متد را بعنوان یک پارامتر بگیرید. این مسیر سپس روی تمام اکشن متدهای این کنترلر اعمال می‌شود، مگر آنکه یک [Route] بخصوص روی اکشن‌ها تعریف شده باشد.
[RoutePrefix("promotions")]
[Route("{action=index}")]
public class ReviewsController : Controller
{
    // eg.: /promotions
    public ActionResult Index() { ... }
 
    // eg.: /promotions/archive
    public ActionResult Archive() { ... }
 
    // eg.: /promotions/new
    public ActionResult New() { ... }
 
    // eg.: /promotions/edit/5
    [Route("edit/{promoId:int}")]
    public ActionResult Edit(int promoId) { ... }
}

محدودیت‌های مسیر ها

با استفاده از Route Constraints می‌توانید نحوه جفت شدن پارامتر‌ها در قالب مسیریابی را محدود و کنترل کنید. فرمت کلی {parameter:constraint} است. بعنوان مثال:
// eg: /users/5
[Route("users/{id:int}"]
public ActionResult GetUserById(int id) { ... }
 
// eg: users/ken
[Route("users/{name}"]
public ActionResult GetUserByName(string name) { ... }
در اینجا، مسیر اول تنها در صورتی انتخاب می‌شود که قسمت id در URI یک مقدار integer باشد. در غیر اینصورت مسیر دوم انتخاب خواهد شد.
جدول زیر constraint‌ها یا محدودیت هایی که پشتیبانی می‌شوند را لیست می‌کند.
 مثال  توضیحات  محدودیت
 {x:alpha}  کاراکترهای الفبای لاتین را تطبیق (match) می‌دهد (a-z, A-Z).  alpha
 {x:bool}  یک مقدار منطقی را تطبیق می‌دهد.  bool
 {x:datetime}  یک مقدار DateTime را تطبیق می‌دهد.  datetime
 {x:decimal}  یک مقدار پولی را تطبیق می‌دهد.  decimal
 {x:double}  یک مقدار اعشاری 64 بیتی را تطبیق می‌دهد.  double
 {x:float}  یک مقدار اعشاری 32 بیتی را تطبیق می‌دهد.  float
 {x:guid}  یک مقدار GUID را تطبیق می‌دهد.  guid
 {x:int}  یک مقدار 32 بیتی integer را تطبیق می‌دهد.  int
 {(x:length(6}
{(x:length(1,20}
 رشته ای با طول تعیین شده را تطبیق می‌دهد.  length
 {x:long}  یک مقدار 64 بیتی integer را تطبیق می‌دهد.  long
 {(x:max(10}  یک مقدار integer با حداکثر مجاز را تطبیق می‌دهد.  max
 {(x:maxlength(10}  رشته ای با حداکثر طول تعیین شده را تطبیق می‌دهد.  maxlength
 {(x:min(10}  مقداری integer با حداقل مقدار تعیین شده را تطبیق می‌دهد.  min
 {(x:minlength(10}  رشته ای با حداقل طول تعیین شده را تطبیق می‌دهد.  minlength
 {(x:range(10,50}  مقداری integer در بازه تعریف شده را تطبیق می‌دهد.  range
 {(${x:regex(^\d{3}-\d{3}-\d{4}  یک عبارت با قاعده را تطبیق می‌دهد.  regex

توجه کنید که بعضی از constraint ها، مانند "min" آرگومان‌ها را در پرانتز دریافت می‌کنند.
می توانید محدودیت‌های متعددی روی یک پارامتر تعریف کنید، که باید با دونقطه جدا شوند. بعنوان مثال:
// eg: /users/5
// but not /users/10000000000 because it is larger than int.MaxValue,
// and not /users/0 because of the min(1) constraint.
[Route("users/{id:int:min(1)}")]
public ActionResult GetUserById(int id) { ... }
مشخص کردن اختیاری بودن پارامتر ها، باید در آخر لیست constraints تعریف شود:
// eg: /greetings/bye
// and /greetings because of the Optional modifier,
// but not /greetings/see-you-tomorrow because of the maxlength(3) constraint.
[Route("greetings/{message:maxlength(3)?}")]
public ActionResult Greet(string message) { ... }

محدودیت‌های سفارشی

با پیاده سازی قرارداد IRouteConstraint می‌توانید محدودیت‌های سفارشی بسازید. بعنوان مثال، constraint زیر یک پارامتر را به لیستی از مقادیر قابل قبول محدود می‌کند:
public class ValuesConstraint : IRouteConstraint
{
    private readonly string[] validOptions;
    public ValuesConstraint(string options)
    {
        validOptions = options.Split('|');
    }
 
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
        }
        return false;
    }
}
قطعه کد زیر نحوه رجیستر کردن این constraint را نشان می‌دهد:
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        var constraintsResolver = new DefaultInlineConstraintResolver();
 
        constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
 
        routes.MapMvcAttributeRoutes(constraintsResolver);
    }
}
حالا می‌توانید این محدودیت سفارشی را روی مسیرها اعمال کنید:
public class TemperatureController : Controller
{
    // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
    [Route("temp/{scale:values(celsius|fahrenheit)}")]
    public ActionResult Show(string scale)
    {
        return Content("scale is " + scale);
    }
}

نام مسیر ها

می توانید به مسیرها یک نام اختصاص دهید، با این کار تولید URI‌ها هم راحت‌تر می‌شوند. بعنوان مثال برای مسیر زیر:
[Route("menu", Name = "mainmenu")]
public ActionResult MainMenu() { ... }
می‌توانید لینکی با استفاده از Url.RouteUrl تولید کنید:
<a href="@Url.RouteUrl("mainmenu")">Main menu</a>

ناحیه‌ها (Areas)

برای مشخص کردن ناحیه ای که کنترلر به آن تعلق دارد می‌توانید از خاصیت [RouteArea] استفاده کنید. هنگام استفاده از این خاصیت، می‌توانید با خیال راحت کلاس AreaRegistration را از ناحیه مورد نظر حذف کنید.
[RouteArea("Admin")]
[RoutePrefix("menu")]
[Route("{action}")]
public class MenuController : Controller
{
    // eg: /admin/menu/login
    public ActionResult Login() { ... }
 
    // eg: /admin/menu/show-options
    [Route("show-options")]
    public ActionResult Options() { ... }
 
    // eg: /stats
    [Route("~/stats")]
    public ActionResult Stats() { ... }
}
با این کنترلر، فراخوانی تولید لینک زیر، رشته "Admin/menu/show-options/" را بدست میدهد:
Url.Action("Options", "Menu", new { Area = "Admin" })
به منظور تعریف یک پیشوند سفارشی برای یک ناحیه، که با نام خود ناحیه مورد نظر متفاوت است می‌توانید از پارامتر AreaPrefix استفاده کنید. بعنوان مثال:
[RouteArea("BackOffice", AreaPrefix = "back-office")]
اگر از ناحیه‌ها هم بصورت مسیریابی نشانه ای، و هم بصورت متداول (که با کلاس‌های AreaRegistration پیکربندی می‌شوند) استفاده می‌کنید باید مطمئن شوید که رجیستر کردن نواحی اپلیکیشن پس از مسیریابی نشانه ای پیکربندی می‌شود. به هر حال رجیستر کردن ناحیه‌ها پیش از تنظیم مسیرها بصورت متداول باید صورت گیرد. دلیل آن هم مشخص است، برای اینکه درخواست‌های ورودی بدرستی با مسیرهای تعریف شده تطبیق داده شوند، باید ابتدا attribute routes، سپس area registration و در آخر default route رجیستر شوند. بعنوان مثال:
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    routes.MapMvcAttributeRoutes();
 
    AreaRegistration.RegisterAllAreas();
 
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

مطالب
بخش سوم - بررسی امکانات و کدنویسی در کامپایلر Svelte
در بخش‌های قبل تا حدودی با کامپایلر Svelte و ساختار فایل‌های آن آشنا شدیم. در این بخش با چند مثال قصد دارم امکاناتی را که Svelte در اختیارمان قرار میدهد، شرح دهم. 

Dynamic attributes : 
در بخش قبل دیدیم که با استفاده از علامت ( آکولاد {} ) میتوانیم مقادیر موجود در تگ اسکریپت را در html خود رندر کنیم. ولی از این علامت میتوان برای مقدار دهی attribute‌ها هم استفاده کرد. 
<script>
let src = 'https://svelte.dev/tutorial/image.gif';
let name = 'Rick Astley';
</script>

<img src={src} alt="{name} dancing">
اگر این کد را در بدنه کامپوننت خود یعنی همان فایل App.svelte قرار دهیم، به درستی کار خواهد کرد و مقدار‌های متغیر‌های src و name، در تگ img ما قرار خواهند گرفت. چند نکته در اینجا وجود دارد که بهتر است به آنها اشاره کنم.

  • نکته اول : اگر در تگ img مقدار alt را وارد نکنیم و یا alt در این تگ وجود نداشته باشد، یک هشدار توسط کامپایلر svelte برای ما با عنوان <img> element should have an alt attribute> ایجاد میشود. زمان ساخت یک برنامه بسیار مهم است تا قوانین نوشتن یک کد html خوب را رعایت کنیم تا برای تمامی کاربران احتمالی برنامه قابل استفاده باشد. در همین مثال با ایجاد یک هشدار Svelte تلاش میکند که ما را از اشتباه در نوشتن کد html مطلع سازد.

  • نکته دوم : اگر نام یک آبجکت تعریف شده و یک attribute، برابر باشد میتوانیم از نسخه کوتاه شده یا Shorthand attributes در svelte استفاده کنیم. به طور مثال در مثال بالا میتوانیم از کد زیر در خط 6 استفاده کنیم.
<img {src} alt="{name} dancing">


Nested components :
همانطور که قبلا اشاره کرده بودم، همه‌چیز در svelte از کامپوننت‌ها تشکیل میشود. در یک برنامه واقعی درست نیست که تمامی کد برنامه را مستقیما در کامپوننت اولیه برنامه بنویسیم. به جای آن بهتر است هر بخش از وبسایت، کامپوننت مجزایی داشته باشد تا کدها و استایل‌ها و html ما نظم و خوانایی بیشتری پیدا کنند.
یک فایل دیگر را در کنار کامپوننت App.svelte که در بخش قبلی ایجاد کرده بودیم، به نام Nested.svelte ایجاد کنید و در آن کد زیر را قرار دهید.
<script>
  export let siteName = "dotnettips";
</script>

<p>this is a nested component for third tutorial on {siteName}</p>
در این مرحله ما یک کامپوننت جدید را ایجاد کردیم که برای نمایش آن باید به App.svelte اضافه شود. برای انجام اینکار محتوای App.svelte را به کد زیر تغییر دهید.
<script>
  import Nested from "./Nested.svelte";

  export let name;
</script>

<h1>Hello {name}!</h1>

<Nested siteName="dotnettips.info" />
که در نهایت چنین خروجی خواهیم داشت. 
 Hello world!

this is a nested component for third tutorial on dotnettips.info


در مثال بالا ما یک کامپوننت جدید را ایجاد کرده و از طریق دستور import به App.svelte اضافه کردیم. نکته‌ای که در اینجا وجود دارد، نحوه مقدار دهی props در کامپوننت‌ها است. اگر به خط 9 دقت کنیم، کامپوننت ما از طریق تگ جدیدی با نام (Nested) به بدنه html برنامه اضافه شده است که یک attribute به نام siteName دارد. siteName متغیر export شده در کامپوننت Nested.svelte است که در کامپوننت‌ها به این صورت مقدار دهی میشود. قبلا نحوه مقدار دهی این خصیصه‌ها را در فایل‌های جاوا اسکریپت مشاهده کرده بودیم. نکته دیگری که باید به آن دقت داشت این است که خصیصه siteName مقدار پیش فرض dotnettips را در Nested.svelte به خود اختصاص داده بود. به همین جهت اگر ما siteName را هنگام استفاده از کامپوننت مقدار دهی نکنیم، از مقدار پیش فرض خود استفاده خواهد کرد. ولی اینجا ما با مقدار دهی آن، siteName را به dotnettips.info تغییر داده‌ایم.

نکته مهم : دقت داشته باشید کامپوننت‌های شما همیشه باید با حروف بزرگ شروع شوند؛ به طور مثال در صورت نوشتن <nested/> محتوای کامپوننت نمایش داده نخواهد شد. svelte، از طریق زیر نظر گرفتن حروف کوچک و بزرگ در ابتدای تگ‌ها، بین تگ‌های html و کامپوننت‌ها تمایز قائل میشود.



Spread  props :

تا اینجا به صورت خلاصه با props یا خصیصه‌ها آشنا شده‌اید و دیدیم که با export کردن یک متغیر در یک کامپوننت، میتوانیم آن را هنگام استفاده مقدار دهی نماییم. برای اینکه تمرینی هم باشد با توجه به مطالبی که تاکنون گفته شده، پروژه‌ی جدیدی را ایجاد کنید و محتوای App.svelte را مانند کد زیر تغییر دهید.

<script>
import Info from './Info.svelte';

const pkg = {
name: 'svelte',
version: 3,
speed: 'blazing',
website: 'https://svelte.dev'
};
</script>

<Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>

همانطور که در خط دوم کد می‌بینید، کامپوننتی به نام Info.svelte به این بخش اضافه شده‌است. این کامپوننت را با محتوای زیر ایجاد نمایید:

<script>
export let name;
export let version;
export let speed;
export let website;
</script>

<p>
The <code>{name}</code> package is {speed} fast.
Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a>
and <a href={website}>learn more here</a>
</p>

اگر برنامه را اجرا کنید یک چنین خروجی را مشاهده خواهید کرد: 

 The svelte  package is  blazing  fast. Download version  3  from  npm  and  learn more here
در این مثال در کامپوننت info.svelte حدودا تعداد زیادی متغیر export شده داریم که دقیقا همنام با آنها در App.svelte یک آبجکت به نام pkg مقدار دهی شده‌است (خط 4-9). در این شرایط نیازی به نوشتن تک تک خصیصه‌ها مانند کاری که در خط 12 کردیم نیست. خیلی ساده میتوانیم از امکانات ES6 برای destruct کردن این آبجکت و ست کردن این خصیصه‌ها استفاده کنیم؛ به این صورت : 
<Info {...pkg}/>



مروری کوتاه بر مدیریت Event ها  در svelte:
قدرت اصلی svelte، واکنش پذیر بودن آن است. به این معنی که همیشه DOM را با وضعیت یا state برنامه هماهنگ نگه میدارد. یکی از مواردی که بطور مثال این امکان را به خوبی نمایش میدهد، event‌ها هستند. 
به مثال زیر توجه کنید:
<script>
  let count = 0;

  function handleClick() {
    count += 1;
  }
</script>

<p>Count : {count}</p>
<button on:click={handleClick}>
  Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
در مثال بالا در خط 10، یک button را ایجاد کردیم که به متد click آن یک function اختصاص داده شده که در خط 4 تعریف کرده بودیم. برای bind کردن یک event در svelte از on: استفاده میکنیم. البته دقت کنید که در خط 10، نام function بدون پرانتز نوشته شده‌است؛ چرا که ما قصد اجرای آن را نداریم و صرفا میخواهیم این فانکشن پس از کلیک شدن بر روی button اجرا شود. در خط 5 هم در بدنه فانکشن خود که پس از هر کلیک اجرا خواهد شد، یک واحد به متغیر count اضافه میکنیم. در خطوط 9 و 11 هم مقدار count را نمایش داده‌ایم. 
در بخش‌های بعد به جزئیات کار با event‌ها بیشتر میپردازیم.

واکنش پذیری یا Reactivity در svelte :
svelte به صورت خودکار DOM را پس از اینکه وضعیت کامپوننت یا آبجکت‌های شما تغییر کند، آپدیت میکند؛ ولی در این بین چندین حالت وجود دارند که به صورت خودکار svelte از تغییر وضعیت کامپوننت شما آگاه نمیشود. 
عملگر :$
به مثال زیر توجه کنید. 
<script>
let count = 0;
let doubled = count * 2;

function handleClick() {
count += 1;
}
</script>

<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>
در این مثال در خط سوم متغیر doubled تعریف شده که قرار است دو برابر count را در خود نگه دارد. اگر این کد را اجرا نمایید، متوجه خواهید شد که مقدار double پس از تغییر count، تغییری نخواهد کرد. چرا که بر خلاف سایر فریم ورک‌ها، مانند react برای بهبود performance برنامه، svelte از Virtual-DOM استفاده نمیکند و این امر سبب میشود که کل کدهای کامپوننت ما در هر بار تغییر، در وضعیت بخشی از آن، مجددا اجرا نشود و مستقیما بخش مورد نظر در DOM تغییر کند. این امر سبب شده از نظر performance در کامپوننت‌های پیچیده که به طور مثال از انیمیشن یا visualisation‌ها استفاده میکنند، کامپوننت‌های svelte چندین برابر پرسرعت‌تر از سایر فریم ورک‌های مشابه عمل کند. ولی اگر بخواهیم که مقدار doubled آپدیت شود چه کاری باید کرد؟ 
راهکار اول مقدار دهی doubled در function مرتبط است، چرا که svelte همیشه به عملگر مساوی = در کد جاوا اسکریپت نگاه میکند. ولی این راهکار در مثال بالا منطقی به نظر نمی‌آید چرا که وظیفه متد ما صرفا دو برابر کردن مقدار count است.
راهکار دوم استفاده از عملگر :$  قبل از تعریف متغیر میباشد که به نظر راهکار مناسب‌تری است. پس در کد بالا فقط لازم است خط سوم را به کد زیر تغییر دهیم تا برنامه بدرستی مقدار doubled را پس از تغییر count محاسبه کند. 
$: doubled = count * 2;
به این طریق میتوانیم متغیر خود را وادار کنیم درصورت تغییر مقادیر، در سمت راست مساوی، مقدار doubled را مجددا محاسبه نماید.

استفاده از این عملگر در svelte به تعریف متغیر‌ها محدود نمیشود. ما هر جائیکه نیاز به واکنش پذیری نسبت به کامپوننت‌ها یا متغیر‌های خود داشتیم، میتوانیم از این عملگر استفاده کنیم. به طور مثال برای لاگ کردن اطلاعات، هر بار که مقدار count تغییر کرد میتوانیم از کد زیر استفاده کنیم. 
$: console.log(`the count is ${count}`);
همچنین میتوانیم به آسانی گروهی از فعالیت‌ها را با هم واکنش پذیر کنیم. مثال :
$: {
  console.log(`the count is ${count}`);
  alert(`I SAID THE COUNT IS ${count}`);
}
حتی میتوان از این عملگر قبل از بلاک‌های کد، مانند if یا for استفاده کرد. مثال:
$: if (count >= 10) {
     alert(`count is dangerously high!`);
     count = 9;
}

واکنش پذیری در آرایه‌ها و آبجکت‌ها : 
به مثال زیر توجه کنید:
<script>
  let numbers = [1, 2, 3, 4];

  function addNumber() {
    let newNumber = numbers.length + 1;
    numbers.push(newNumber);
  }

  $: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}>Add a number</button>

در مثال بالا پس از هر کلیک، یک عدد به آرایه numbers اضافه میشود؛ ولی در خط 11 که این آرایه را نمایش میدهیم، مقدار آن تغییر نخواهد کرد! همانطور که قبلا اشاره کرده بودم svelte برای اینکه متوجه شود تغییری در آبجکت‌های ما صورت گرفته است، به مشاهده عملگر مساوی در کدهای ما میپردازد و چون اینجا در خط 6، ما از متد push آرایه برای افزودن مقداری جدید به آن درحال استفاده هستیم svelte واکنشی به این تغییر ندارد. برای رفع این مشکل دو راهکار وجود دارد. 
راهکار اول استفاده از عملگر مساوی و reassign کردن مقدار آبجکت یا متغیر مورد نظر است به این صورت : 
  function addNumber() {
    let newNumber = numbers.length + 1;
    numbers.push(newNumber);
    numbers = numbers;
  }
راهکار دوم استفاده از امکانات ES6 جاوا اسکریپت است برای افزودن مقدار جدید به آرایه که چون در این روش از مساوی استفاده میکنیم، تغییرات در numbers واکنش پذیر خواهد بود. به این صورت : 
  function addNumber() {
    let newNumber = numbers.length + 1;
    numbers = [...numbers, newNumber];
  }

مروری بر Two way bindings :
فرض کنید قصد داریم که یک input برای دریافت نام در صفحه قرار داده و در متغیری به نام name ذخیره کنیم. سپس این متغیر را در صفحه نشان دهیم. برای انجام اینکار حدودا با توجه به مطالبی که تا الان آموخته‌ایم میتوان کد زیر را تولید کرد : 
<script>
  let name = "";
  function updateName(event) {
    name = event.target.value;
  }
</script>

<h4>My Name Is {name}</h4>
<input value={name} on:input={updateName} />
در خط 9 یک input به کامپوننت خود اضافه کردیم و مقدار value آن را به متغیر name نسبت دادیم. تا اینجا فقط اگر name تغییری کند آن را در input میتوانیم نمایش دهیم ولی پس از تایپ در این input به صورت خودکار مقدار name آپدیت نمیشود چون ما در این حالت از One way binding درحال استفاده هستیم. به همین جهت یک متد برای آپدیت مقدار name توسط input هم باید مینوشتیم که اگر دقت کنید در کد فوق از ایونت input تگ input در خط 11 به همین منظور استفاده شده‌است. در خط سوم اینبار متد ما یک پارامتر هم به نام event دارد. به طور پیش فرض میتوانیم همیشه از طریق این پارامتر به ایونت آبجکت مبداء که فانکشن را صدا زده است، دسترسی داشته باشیم که خیلی ساده از طریق این ایونت در خط 4 مقدار name را پس تغییر در input آپدیت میکنیم. 
شاید کد بالا کاری که خواستیم را انجام داده باشد. ولی پیاده سازی Two way binding به شکلی که نوشتیم ساده نیست و مشکلات خاص خودش را دارد. خبر خوب این است که svelte این امکان را به صورت توکار محیا کرده است. 
برای اتصال یک attribute به یک متغیر یا آبجکت به صورت دو طرفه از کلمه :bind قبل از نام attribute در svelte استفاده میشود. مثال بالا را اگر با استفاده از این توضیح بازنویسی کنیم، به این کد خواهیم رسید:
<script>
  let name = "";
</script>

<h4>My Name Is {name}</h4>
<input bind:value={name} />
همانطور که مشاهده میکنید دیگر نیازی به نوشتن یک فانکشن مجزا و گرفتن اطلاعات از طریق پارامتر ایونت نیست و به سادگی مقدار value به متغیر name به صورت دو طرفه متصل شده است.


expressing logic :
html امکانی برای پیاده سازی منطق برنامه، برخلاف svelte ندارد. در ادامه با syntax پیاده سازی منطق برنامه در کنار کدهای html در svelte آشنا میشویم. 

If blocks
برای نوشتن If در svelte از if# در میان دو آکولاد {} استفاده میشود و برای پایان دادن به آن از if/ در میاد دو آکولاد. به این صورت : 
{#if condition}
    <!-- you html codes ...  -->
{/if}
به مثال زیر توجه کنید : 
در این مثال قصد داریم پس از کلیک بر روی کلید لاگین، وضعیت کاربر را لاگین شده قرار دهیم و کلید لاگین را مخفی کنیم. سپس کلید دیگری برای خروج از برنامه به نام logout را نمایش دهیم که با کلیک بر روی آن کاربر logout شود و مجددا کلید لاگین نمایش داده شود. برای شبیه سازی این سناریو از کد زیر میتوانیم استفاده کنیم :
<script>
let user = { loggedIn: false };

function toggle() {
user.loggedIn = !user.loggedIn;
}
</script>

{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{/if}

{#if !user.loggedIn}
<button on:click={toggle}>
Log in
</button>
{/if}
کدهای داخل اسکریپت که چندان نیازی به توضیح ندارند و صرفا برای toggle کردن وضعیت کاربر هستند. ولی در خطوط 9 - 13 و 15 - 19 طریقه نوشتن if در svelte را با یک مثال واقعی مشاهده میکنید.
در مثال بالا دو شرط با یک مقدار مشابه true/false نوشته شده است که بهتر بود مانند سایر زبان‌های برنامه نویسی از else در اینجا استفاده میشد. در ادامه با نحوه نوشتن else در svelte آشنا خواهیم شد.
Else blocks
برای نوشتن else در svelte از else: در میاد دو آکولاد {} بین یک block مانند if میتوانیم استفاده کنیم. به این صورت :
{#if condition}
    <!-- you html code when condition is true -->
{:else}
    <!-- you html code when condition is false -->
{/if}
باز نویسی دو if block در مثال بالا با استفاده از else : 
{#if user.loggedIn}
<button on:click={toggle}>
Log out
</button>
{:else}
<button on:click={toggle}>
Log in
</button>
{/if}

همینطور شما میتوانید از else if هم استفاده نمایید. به این صورت : 
{#if condition}
    <!-- you html code when condition is true -->
{:else if condition2}
    <!-- you html code when condition2 is true -->
{:else}
    <!-- you html code when condition and condition2 are false -->
{/if}

نکته تکمیلی : در svelte علامت # همیشه به معنای شروع یک بلاک کد است و علامت / به معنای پایان بلاک. همینطور علامت : نشان دهنده ادامه بلاک کد است. شاید در ابتدا کمی گیج کننده به نظر بیاید ولی در مثال‌های بعدی بیشتر با این علائم آشنا میشویم.

each blocks : 
برای حرکت در میان لیستی از اطلاعات در svelte میتوانیم از each# استفاده نماییم و مانند بلاک if برای خاتمه دادن به آن از each/ استفاده میشود. همینطور از کلمه کلیدی as هم برای دسترسی به هر آیتم در لیست استفاده میشود. به این صورت : 
{#each list as item}
      <!-- you html code per each item in list -->
{/each}
به مثال زیر توجه کنید : 
<script>
let cats = [
{ id: 'J---aiyznGQ', name: 'Keyboard Cat' },
{ id: 'z_AbfPXTKms', name: 'Maru' },
{ id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
];
</script>

<h1>The Famous Cats of YouTube</h1>

<ul>
{#each cats as cat}
<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
{cat.name}
</a></li>
{/each}
</ul>
در خط 2 ما یک آرایه از گربه‌ها را تعریف کرده ایم و در خط 11 با استفاده از each به ازای هر کدام از آیتم‌های داخل این آرایه یک تگ li ایجاد میکنیم که لینک مرتبط با آن آیتم را نمایش میدهد. البته کد فوق را به شکل دیگری با استفاده از امکانات destructing جاوا اسکریپت هم میتوانستیم بنویسیم. به این صورت : 
<ul>
{#each cats as {id,name}}
<li><a target="_blank" href="https://www.youtube.com/watch?v={id}">
{name}
</a></li>
{/each}
</ul>
در این حالت ابتدا id و name به صورت مجزا destruct شده اند و میتوانیم مانند مثال بالا از آنها بدون نوشتن نام آیتم استفاده کنیم . مانند خط 4 کد بالا.
همینطور در each بلاک‌ها امکان دریافت ایندکس جاری آیتم از طریق آرگومان دوم هم وجود دارد. به این صورت : 
<ul>
{#each cats as { id, name }, i}
<li><a target="_blank" href="https://www.youtube.com/watch?v={id}">
{i + 1}: {name}
</a></li>
{/each}
</ul>
اگر به کد فوق دقت کنید درخط 2 مقدار i ایندکس آیتم جاری آرایه ما یعنی همان cats است.


نکته : در این بخش من سعی کردم تا حدودی به ترتیب بخش آموزشی خود وبسایت Svelte، موارد را بیان کنم؛ ولی با توجه به اینکه شاید دوستان ترجیح بدهند روش آموزشی خود  آن وبسایت که امکان تغییر و نوشتن کد را هم محیا کرده است، امتحان کنند  لینک آن  را به اشتراک میگذارم. 
مطالب دوره‌ها
ایجاد یک اسمبلی جدید توسط Reflection.Emit
مطابق استاندارد ECMA-335 قسمت دوم آن، یک اسمبلی از یک یا چند ماژول تشکیل می‌شود. هر ماژول از تعدادی نوع، enum و delegate تشکیل خواهد شد و هر نوع دارای تعدادی متد، فیلد، خاصیت و غیره می‌باشد. به همین جهت در حین کار با Reflection.Emit نیز این مراحل رعایت می‌شوند. ابتدا یک اسمبلی (AppDomain.DefineDynamicAssembly) ایجاد خواهد شد (یا از اسمبلی موجود استفاده می‌شود). سپس یک ماژول (AssemblyBuilder.DefineDynamicModule) را باید به آن اضافه کنیم (یا از ماژول اسمبلی جاری استفاده نمائیم). در ادامه یک Type باید به این ماژول اضافه شود (ModuleBuilder.DefineType) و اکنون می‌توان به این نوع جدید، سازنده (TypeBuilder.DefineConstructor)، متد (TypeBuilder.DefineMethod)، فیلد (TypeBuilder.DefineField)، خاصیت (TypeBuilder.DefineProperty) و رخداد (TypeBuilder.DefineEvent) اضافه کرد.


using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            var name = "HelloWorld.exe";
            var assemblyName = new AssemblyName(name);
            // ایجاد یک اسمبلی جدید با قابلیت ذخیره سازی آن
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                            name: assemblyName,
                                            access: AssemblyBuilderAccess.RunAndSave);
            // افزودن یک ماژول به اسمبلی
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
            // تعریف یک کلاس در این ماژول
            var programmClass = moduleBuilder.DefineType("Program", TypeAttributes.Public);
            // افزودن یک متد به این کلاس
            // این متد خروجی ندارد اما ورودی آن شبیه به متد اصلی یک برنامه کنسول است
            var mainMethod = programmClass.DefineMethod(name: "Main",
                                            attributes: MethodAttributes.Public | MethodAttributes.Static,
                                            returnType: null,
                                            parameterTypes: new Type[] { typeof(string[]) });
            // تعیین بدنه متد اصلی برنامه
            var il = mainMethod.GetILGenerator();
            il.Emit(OpCodes.Ldstr, "Hello World!");
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("WriteLine", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("ReadKey", new Type[0]));
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ret);

            // تکمیل کار ایجاد نوع جدید
            programmClass.CreateType();

            // تعیین نقطه شروع فایل اجرایی برنامه کنسول تهیه شده
            assemblyBuilder.SetEntryPoint(((Type)programmClass).GetMethod("Main"));

            // ذخیره سازی این اسمبلی بر روی دیسک سخت
            assemblyBuilder.Save(name);
        }
    }
}
مراحلی را که توضیح داده شد، در کدهای فوق ملاحظه می‌کنید. انتخاب حالت دسترسی AssemblyBuilderAccess.RunAndSave سبب می‌شود تا بتوان نتیجه حاصل را ذخیره کرد. فایل Exe نهایی را اگر در برنامه ILSpy باز کنیم چنین شکلی دارد:
using System;
public class Program
{
    public static void Main(string[] array)
    {
       Console.WriteLine("Hello World!");
       Console.ReadKey();
    }
}