نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
یک نکته‌ی تکمیلی: روش تعیین حداکثر اندازه‌ی فایل قابل آپلود در برنامه‌های ASP.NET Core
الف) اگر برنامه توسط IIS هاست شده‌است
<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <!-- To customize the asp.net core module uncomment and edit the following section. 
 For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
 <system.webServer>
  <handlers>
  <remove name="aspNetCore"/>
  <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
  </handlers>
  <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
  <security>
   <requestFiltering>
    <!-- This will handle requests up to 50MB -->
    <requestLimits maxAllowedContentLength="52428800" />
   </requestFiltering>
  </security>
  </system.webServer>
</configuration>
در اینجا (در فایل web.config برنامه) مقدار خاصیت maxAllowedContentLength است که تعیین کننده‌ی حداکثر اندازه‌ی فایل قابل آپلود است. به علاوه مطمئن شوید که فایل web.config شما حتما publish شده.
همچنین requestTimeout را نیز به صورت زیر می‌توان مقدار دهی کرد:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <aspNetCore requestTimeout="00:20:00"  .... />
  </system.webServer>
</configuration>
به همراه این تنظیمات:
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<IISServerOptions>(options =>
            {
                options.MaxRequestBodySize = int.MaxValue;
            });
            services.Configure<FormOptions>(options =>
            {
                options.ValueLengthLimit = int.MaxValue;
                options.MultipartBodyLengthLimit = long.MaxValue; // <-- ! long.MaxValue
                options.MultipartBoundaryLengthLimit = int.MaxValue;
                options.MultipartHeadersCountLimit = int.MaxValue;
                options.MultipartHeadersLengthLimit = int.MaxValue;
            });
 
ب) اگر برنامه در لینوکس و بر اساس وب سرور Kestrel هاست شده‌است
روش اول: استفاده از ویژگی RequestSizeLimit که از نگارش ASP.NET Core 2.0 به بعد قابل استفاده‌است و صرفا به قسمتی از برنامه اعمال می‌شود:
[HttpPost]
[RequestSizeLimit(40000000)]
public async Task<IActionResult> UploadFiles(IFormFile file)
روش دوم: تنظیم سراسری آن برای کل برنامه:
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>()
                        .ConfigureKestrel(kestrelServerOptions =>
                        {
                            kestrelServerOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
                            kestrelServerOptions.Limits.MaxRequestBodySize = 52428800; //50MB
                        });
                });
بدیهی است تنظیم این دو روش در حالت استفاده‌ی از IIS تاثیری نخواهند داشت.
مطالب
Roslyn #4
بررسی API کامپایل Roslyn

Compilation API، یک abstraction سطح بالا از فعالیت‌های کامپایل Roslyn است. برای مثال در اینجا می‌توان یک اسمبلی را از Syntax tree موجود، تولید کرد و یا جایگزین‌هایی را برای APIهای قدیمی CodeDOM و Reflection Emit ارائه داد. به علاوه این API امکان دسترسی به گزارشات خطاهای کامپایل را میسر می‌کند؛ به همراه دسترسی به اطلاعات Semantic analysis. در مورد تفاوت Syntax tree و Semantics در قسمت قبل بیشتر بحث شد.
با ارائه‌ی Roslyn، اینبار کامپایلرهای خط فرمان تولید شده مانند csc.exe، صرفا یک پوسته بر فراز Compilation API آن هستند. بنابراین دیگر نیازی به فراخوانی Process.Start بر روی فایل اجرایی csc.exe مانند یک سری کتابخانه‌های قدیمی نیست. در اینجا با کدنویسی، به تمام اجزاء و تنظیمات کامپایلر، دسترسی وجود دارد.


کامپایل پویای کد توسط Roslyn

برای کار با API کامپایل، سورس کد، به صورت یک رشته در اختیار کامپایلر قرار می‌گیرد؛ به همراه تنظیمات ارجاعاتی به اسمبلی‌هایی که نیاز دارد. سپس کار کامپایلر شروع خواهد شد و شامل مواردی است مانند تبدیل متن دریافتی به Syntax tree و همچنین تبدیل مواردی که اصطلاحا به آن‌ها Syntax sugars گفته می‌شود مانند خواص get و set دار به معادل‌های اصلی آن‌ها. در اینجا کار Semantic analysis هم انجام می‌شود و شامل تشخیص حوزه‌ی دید متغیرها، تشخیص overloadها و بررسی نوع‌های بکار رفته‌است. در نهایت کار تولید فایل باینری اسمبلی، از اطلاعات آنالیز شده صورت می‌گیرد. البته خروجی کامپایلر می‌تواند اسمبلی‌های exe یا dll، فایل XML مستندات اسمبلی و یا فایل‌های .netmudule و .winmdobj مخصوص WinRT هم باشد.
در ادامه، اولین مثال کار با Compilation API را مشاهده می‌کنید. پیشنیاز اجرای آن همان مواردی هستند که در قسمت قبل بحث شدند. یک برنامه‌ی کنسول ساده‌ی .NET 4.6 را آغاز کرده و سپس بسته‌ی نیوگت Microsoft.CodeAnalysis را در آن نصب کنید. در ادامه کدهای ذیل را به پروژه‌ی آماده شده اضافه کنید:
static void firstCompilation()
{
    var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }");
 
    var mscorlibReference = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
 
    var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
 
    var comp = CSharpCompilation.Create("Demo")
                                .AddSyntaxTrees(tree)
                                .AddReferences(mscorlibReference)
                                .WithOptions(compilationOptions);
 
    var res = comp.Emit("Demo.dll");
 
    if (!res.Success)
    {
        foreach (var diagnostic in res.Diagnostics)
        {
            Console.WriteLine(diagnostic.GetMessage());
        }
    }
}
در اینجا نحوه‌ی کامپایل پویای یک قطعه کد متنی سی‌شارپ را به DLL معادل آن مشاهده می‌کنید. مرحله‌ی اول اینکار، تولید Syntax tree از رشته‌ی متنی دریافتی است. سپس متد CSharpCompilation.Create یک وهله از Compilation API مخصوص #C را آغاز می‌کند. این API به صورت Fluent طراحی شده‌است و می‌توان سایر قسمت‌های آن‌را به همراه یک دات پس از ذکر متد، به طول زنجیره‌ی فراخوانی، اضافه کرد. برای نمونه در این مثال، نحوه‌ی افزودن ارجاعی را به اسمبلی mscorlib که System.Object در آن قرار دارد و همچنین ذکر نوع خروجی DLL یا DynamicallyLinkedLibrary را ملاحظه می‌کنید. اگر این تنظیم ذکر نشود، خروجی پیش فرض از نوع .exe خواهد بود و اگر mscorlib را اضافه نکنیم، نوع int سورس کد ورودی، شناسایی نشده و برنامه کامپایل نمی‌شود.
متدهای تعریف شده توسط Compilation API به یک s جمع، ختم می‌شوند؛ به این معنا که در اینجا در صورت نیاز، چندین Syntax tree یا ارجاع را می‌توان تعریف کرد.
پس از وهله سازی Compilation API و تنظیم آن، اکنون با فراخوانی متد Emit، کار تولید فایل اسمبلی نهایی صورت می‌گیرد. در اینجا اگر خطایی وجود داشته باشد، استثنایی را دریافت نخواهید کرد. بلکه باید خاصیت Success نتیجه‌ی آن‌را بررسی کرده و درصورت موفقیت آمیز نبودن عملیات، خطاهای دریافتی را از مجموعه‌ی Diagnostics آن دریافت کرد. کلاس Diagnostic، شامل اطلاعاتی مانند محل سطر و ستون وقوع مشکل و یا پیام متناظر با آن است.


معرفی مقدمات Semantic analysis

Compilation API به اطلاعات Semantics نیز دسترسی دارد. برای مثال آیا Type A قابل تبدیل به Type B هست یا اصلا نیازی به تبدیل ندارد و به صورت مستقیم قابل انتساب هستند؟ برای درک بهتر این مفهوم نیاز است یک مثال را بررسی کنیم:
        static void semanticQuestions()
        {
            var tree = CSharpSyntaxTree.ParseText(@"
using System;
 
class Foo
{
    public static explicit operator DateTime(Foo f)
    {
        throw new NotImplementedException();
    }
 
    void Bar(int x)
    {
    }
}");
 
            var mscorlib = MetadataReference.CreateFromFile(typeof (object).Assembly.Location);
            var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            var comp = CSharpCompilation.Create("Demo").AddSyntaxTrees(tree).AddReferences(mscorlib).WithOptions(options);
            // var res = comp.Emit("Demo.dll");
 
            // boxing
            var conv1 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Int32),
                comp.GetSpecialType(SpecialType.System_Object)
                );
 
            // unboxing
            var conv2 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetSpecialType(SpecialType.System_Int32)
                );
 
            // explicit reference conversion
            var conv3 = comp.ClassifyConversion(
                comp.GetSpecialType(SpecialType.System_Object),
                comp.GetTypeByMetadataName("Foo")
                );
 
            // explicit user-supplied conversion
            var conv4 = comp.ClassifyConversion(
                comp.GetTypeByMetadataName("Foo"),
                comp.GetSpecialType(SpecialType.System_DateTime)
                );
        }
تا سطر CSharpCompilation.Create این مثال، مانند قبل است و تا اینجا به Compilation API دسترسی پیدا کرده‌ایم. پس از آن می‌خواهیم یک Semantic analysis مقدماتی را انجام دهیم. برای این منظور می‌توان از متد ClassifyConversion استفاده کرد. این متد یک نوع مبداء و یک نوع مقصد را دریافت می‌کند و بر اساس اطلاعاتی که از Compilation API بدست می‌آورد، می‌تواند مشخص کند که برای مثال آیا نوع کلاس Foo قابل تبدیل به DateTime هست یا خیر و اگر هست چه نوع تبدیلی را نیاز دارد؟


برای مثال نتیجه‌ی بررسی آخرین تبدیل انجام شده در تصویر فوق مشخص است. با توجه به تعریف public static explicit operator DateTime در سورس کد مورد آنالیز، این تبدیل explicit بوده و همچنین user defined. به علاوه متدی هم که این تبدیل را انجام می‌دهد، مشخص کرده‌است.
مطالب
ساخت Attribute های دلخواه یا خصوصی سازی شده
در قسمت‌های مختلفی از منابع آموزشی این سایت از متادیتاها attributes استفاده شده و در برخی آموزش هایی چون EF و MVC حداقل یک قسمت کامل را به خود اختصاص داده‌اند. متادیتاها کلاس‌هایی هستند که به روشی سریع و کوتاه در بالای یک Type معرفی شده و ویژگی‌هایی را به آن اضافه می‌کنند. به عنوان مثال متادیتای زیر را ببینید. این متادیتا در بالای یک متد در یک کلاس تعریف شده است و این متد را منسوخ شده اعلام می‌کند و به برنامه نویس می‌گوید که در نسخه‌ی جاری کتابخانه، این متد که احتمال میرود در نسخه‌های پیشین کاربرد داشته است، الان کارآیی خوبی برای استفاده نداشته و بهتر است طبق مستندات آن کلاس، از یک متد جایگزین که برای آن فراهم شده است استفاده کند.
 public static class  MyAttributes
    {
        [Obsolete]
        public static void MyMethod1()
        {

        }

        public static void MyMetho2()
        {

        }
    }
همانطور که ملاحظه می‌کنید می‌توانید اخطار آن را مشاهده کنید:

البته توصیه می‌کنم از ابزارهایی چون Resharper در کارهایتان استفاده کنید، تا طعم کدنویسی را بهتر بچشید. نحوه‌ی نمایش آن در Resharper به مراتب واضح‌تر و گویاتر است:



 حال در این بین این سؤال پیش می‌آید که چگونه ما هم می‌توانیم متادیتاهایی را با سلیقه‌ی خود ایجاد کنیم.
برای تهیه‌ی یک متادیتا از کلاس system.attribute استفاده می‌کنیم:
public  class  MyMaxLength:Attribute
    {
   
    }
در چنین حالتی شما یک متادیتا ساخته‌اید که می‌توان از آن به شکل زیر استفاده کرد:
[MyMaxLength]
    public class GetCustomProperties
    {
//...
     }

ولی اگر بخواهید توسط این متادیتا اطلاعاتی را دریافت کنید، می‌توانید به روش زیر عمل کنید. در اینجا من دوست دارم یک متادیتا به اسم MyMaxLength را ایجاد کرده تا جایگزین MaxLength دات نت کنم، تا طبق میل من رفتار کند.
    public  class  MyMaxLength:Attribute
    {
        private int max;
        public string ErrorText = "";

        public MyMaxLength(int max)
        {
            this.max = max;
            ErrorText = string.Format("max Length is {0} chars", max);
        }
    }

در کد بالا، یک متادیتا با یک پارامتر اجباری در سازنده تعریف شده است. این کلاس هم می‌تواند مثل سایر کلاس‌ها سازنده‌های مختلفی داشته باشد تا چندین شکل تعریف متادیتا داشته باشیم. متغیر ErrorText به عنوان یک پارامتر معرفی نشده، ولی از آن جا که public تعریف شده است می‌تواند مورد استفاده‌ی مستقیم قرار بگیرد و استفاده‌ی از آن نیز اختیاری است. نحوه‌ی معرفی این متادیتا نیز به صورت زیر است:
 [MyMaxLength(30)]
    public class GetCustomProperties
    {
//...
     }

//or 
 [MyMaxLength(30,ErrorText = "شما اجازه ندارید بیش از 30 کاراکتر وارد نمایید")]
    public class GetCustomProperties
    {
//...
     }
در حالت اول از آنجا که متغیر ErrorText اختیاری است، تعریف نشده‌است. پس در نتیجه با مقدار Max length is (x=max) chars پر خواهد شد ولی در حالت دوم برنامه نویس متن خطا را به خود کلاس واگذار نکرده است و آن را طبق میل خود تغییر داده است.


اجباری کردن Type
هر متادیتا می‌تواند مختص  یک نوع Type باشد که این نوع می‌تواند یک کلاس، متد، پراپرتی یا ساختار و ... باشد. نحوه‌ی محدود سازی آن توسط یک متادیتا مشخص می‌شود:
    [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
    public  class  MyMaxLength:Attribute
    {
        private int max;
        public string ErrorText = "";

        public MyMaxLength(int max)
        {
            this.max = max;
            ErrorText = string.Format("max Length is {0} chars", max);
        }
    }
الان این کلاس توسط متادیتای AttributeUsage که پارامتر ورودی آن Enum است محدود به دو ساختار کلاس و Struct شده است. البته در ویژوال بیسیک با نام Structure معرفی شده است. اگر ساختار شمارشی AttributeTarget را مشاهده کنید، لیستی از نوع‌ها را چون All (همه موارد) ، دلیگیت، سازنده، متد و ... را مشاهده خواهید کرد و از آن جا که این متادیتای ما کاربردش در پراپرتی‌ها خلاصه می‌شود، از متادیتای زیر بر روی آن استفاده می‌کنیم:
[AttributeUsage(AttributeTargets.Property)]

  public class User
    {
        [MyMaxLength(30, ErrorText = "شما اجازه ندارید بیش از 30 کاراکتر وارد نمایید")]
        public string Name { get; set; }
}

یکی دیگر از ویژگی‌های AttributeUsage خصوصیتی به اسم AllowMultiple است که اجازه می‌دهد بیش از یک بار این متادیتا، بر روی یک نوع استفاده شود:
 [AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
    public  class  MyMaxLength:Attribute
    {
        //....
     }

که تعریف چندگانه آن به شکل زیر می‌شود:
[MyMaxLength(40, ErrorText = "شما اجازه ندارید بیش از 40 کاراکتر وارد نمایید")]
        [MyMaxLength(50, ErrorText = "شما اجازه ندارید بیش از 50 کاراکتر وارد نمایید")]
        [MyMaxLength(30, ErrorText = "شما اجازه ندارید بیش از 30 کاراکتر وارد نمایید")]
        public string Name { get; set; }
در این مثال ما فقط اجازه‌ی یکبار استفاده را خواهیم داد؛ پس مقدار این ویژگی را false قرار می‌دهم.

آخرین ویژگی که این متادیتا در دسترس ما قرار میدهد، استفاده از خصوصیت ارث بری است که به طور پیش فرض با True مقداردهی شده است. موقعی که شما یک متادیتا را به ویژگی ارث بری مزین کنید، در صورتی که آن کلاس که برایش متادیتا تعریف می‌کنید به عنوان والد مورد استفاده قرار بگیرد، فرزند آن هم به طور خودکار این متادیتا برایش منظور می‌گردد. به مثال‌های زیر دقت کنید:
دو عدد متادیتا تعریف شده که یکی از آن‌ها ارث بری در آن فعال شده و دیگری خیر.
public class MyAttribute : Attribute
{
    //...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class YourAttribute : Attribute
{
    //...
}

هر دو متادیتا بر سر یک متد در یک کلاسی که بعدا از آن ارث بری می‌شود تعریف شده اند.
public class MyClass
{
    [MyAttribute]
    [YourAttribute]
    public virtual void MyMethod()
    {
        //...
    }
}

در کد زیر کلاس بالا به عنوان والد معرفی شده و متد کلاس فرزند الان شامل متادیتایی به اسم MyAttribute است، ولی متادیتای YourAttribute بر روی آن تعریف نشده است.
public class YourClass : MyClass
{
    public override void MyMethod()
    {
        //...
    }

}

الان که با نحوه‌ی تعریف یکی از متادیتاها آشنا شدیم، این بحث پیش می‌آید که چگونه Type مورد نظر را تحت تاثیر این متادیتا قرار دهیم. الان چگونه میتوانم حداکثر متنی که یک پراپرتی می‌گیرد را کنترل کنم. در اینجا ما از مفهومی به نام Reflection  استفاده می‌کنیم. با استفاده از این مفهوم ما میتوانیم به تمامی قسمت‌های یک Type دسترسی داشته باشیم. متاسفانه دسترسی مستقیمی از داخل کلاس متادیتا به نوع مورد نظر نداریم. کد زیر تمامی پراپرتی‌های یک کلاس را چک میکند و سپس ویژگی‌های هر پراپرتی را دنبال کرده و در صورتیکه متادیتای مورد نظر به آن پراپرتی  ضمیمه شده باشد، حالا می‌توانید عملیات را انجام دهید. کد زیر میتواند در هر جایی نوشته شود. داخل کلاسی که که به آن متادیتا ضمیمه می‌کنید یا داخل تابع Main در اپلیکشین‌ها و هر جای دیگر. مقدار True که به متد GetCustomAttributes پاس می‌شود باعث می‌شود تا متادیتاهای ارث بری شده هم لحاظ گردند.
   Type type = typeof (User);

                foreach (PropertyInfo property in type.GetProperties())
                {
                    foreach (Attribute attribute in property.GetCustomAttributes(true))
                    {
                        MyMaxLength max = attribute as MyMaxLength;
                        if (max != null)
                        {
                            string Max = max.ErrorText;
                            //انجام عملیات
                        }
                    }
                }
البته یک ترفند جهت دسترسی به کلاس‌ها از داخل کلاس متادیتا وجود دارد و آن هم این هست که نوع را از طریق پارامتر به سمت متادیتا ارسال کنید. هر چند این کار زیبایی ندارد ولی به هر حال روش خوبی برای کنترل از داخل کلاس متادیتا و هچنین منظم سازی و دسته بندی و کم کردن کد دارد.
[MyMaxLength(30, typeof(User))]
نظرات مطالب
پرسش و پاسخ‌های متداول ایجاد یک وبلاگ بلاگری
چرا استقبال نسبت به بلاگر اینقدر کمه توی ایران؟ با وجود اینکه خیلی خوبه؟
آیا بلاگرهای دیگه ای رو میتونید به من معرفی کنید که در زمینه بلاگر بنویسند؟ آخه من هرچی توی گوگل برای بلاگر و مسائلش میگردم چیزی زیادی دستگیرم نمیشه، همش همون بلاگر فارسیه و یه چندتا وبلاگ دیگه!
تازه مثلا همین بلاگرفارسی که مطالبش خیلی قدیمیه و مثلا دیگه اینکه پوسته هاش هم خیلی محدود و قدیمین و من نتونستم یکی بپسندم! دیگه اینکه مثلا من نتونستم بر اساس دستور العملشون تاریخم رو هجری شمسی کنم چون اصلن کدی رو که گفته بودن نداشت!و سوالهای زیاد دیگه ای هم دارم که باید از یکی که با بلاگر کار کرده بپرسم!
مطالب
آشنایی با Saltarelle کامپایلر قدرتمند #C به جاوااسکریپت

شاید ساده‌ترین تعریف برای  Saltarelle  این باشد که «کامپایلریست که کد‌های C# را به جاوا اسکریپت تبدیل می‌کند». محاسن زیادی را می‌توان برای اینگونه کامپایلر‌ها نام برد؛ مخصوصا در پروژه‌های سازمانی که نگهداری از کد‌های جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.

در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# ساده‌ترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کد‌ها وجود داشته باشد، بی شک Saltarelle یکی از بهترین‌های آنهاست.

قبلا کامپایلر هایی از این دست مانند  Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفاده‌ی حداکثری از کلیه ویژگی‌های C# 5 و با وجود Library ‌های متعدد می‌توان Saltarelle  را عملا یک کامپایلر موفق در این زمینه دانست.

برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ‌ها از پروژه حدف می‌شوند و هر بار Build توسط کامپایلر Saltarelle  انجام می‌شود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:

PM> Install-Package Saltarelle.Compiler
PM> Install-Package Saltarelle.Runtime

در صورتیکه کماکان Build  نهایی با Error همرا بود، یکبار این پروژه را Unload  و سپس مجددا Load نمایید



UI یک پروژه وب MVC است و Client یک Console Application که پکیج‌های مورد نیاز Saltarelle  روی آن نصب شده است.

در صورتیکه پروژه را Build نماییم و نگاهی به پوشه‌ی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:


برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشه‌ی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build  پروژه Client بنویسیم: 

copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"

اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشه‌ی Scripts پروژه UI  آپدیت می‌شود:


در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم. 

<script src="~/Scripts/SaltarelleSample.Client.js"></script>

در پوشه‌ی Saltarelle.Runtime در پکیج‌های نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپت‌های مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپت‌های پروژه UI کپی نمایید و سپس به Layout  اضافه کنید. 

<script src="~/Scripts/mscorlib.min.js"></script>
<script src="~/Scripts/SaltarelleSample.Client.js"></script>

حال نوبت به اضافه نمودن library‌های مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت می‌توان پکیج Saltarelle.Web را در پروژه‌ی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید. 

> Install-Package Saltarelle.Web
> Install-Package Saltarelle.jQuery

به این library‌ها imported library می‌گویند. در واقع، در زمان کامپایل، برای این library‌ها فایل اسکریپتی تولید نمی‌شود و فقط آبجکت‌های #C هستند که که هنگام کامپایل تبدیل به کدهای ساده اسکریپت می‌شوند که اگر اسکریپت مربوط به آنها به صفحه اضافه نشده باشد، اجرای اسکریپت با خطا مواجه می‌شود.

به طور ساده‌تر وقتی از jQuery library استفاده می‌کنید هیچ فایل اسکریپت اضافه‌ای تولید نمی‌شود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.

<script src="~/Scripts/jquery-1.10.2.min.js"></script>

مثال ما یک اپلیکیشن ساده برای خواندن فید‌های همین سایت است. ابتدا کد‌های سمت سرور را در پروژه UI  می نویسیم.

کلاس‌های مورد نیاز ما برای این فید ریدر: 

public class Feed
    {
        public string FeedId { get; set; }
        public string Title { get; set; }
        public string Address { get; set; }

    }
    public class Item
    {
        public string Title { get; set; }
        public string Link { get; set; }
        public string Description { get; set; }
    }

و یک کلاس برای مدیریت منطق برنامه 

 public class SiteManager
    {
        private static List<Feed> _feeds;
        public static List<Feed> Feeds
        {
            get
            {
                if (_feeds == null)
                    _feeds = CreateSites();
                return _feeds;
            }
        }
        private static List<Feed> CreateSites()
        {
            return new List<Feed>() { 
                new Feed(){
                    FeedId = "1",
                    Title = "آخرین تغییرات سایت",
                    Address = "https://www.dntips.ir/rss.xml"
                },
                 new Feed(){
                    FeedId = "2",
                    Title = "مطالب سایت",
                    Address = "https://www.dntips.ir/feeds/posts"
                },
                 new Feed(){
                    FeedId = "3",
                    Title = "نظرات سایت",
                    Address = "https://www.dntips.ir/feeds/comments"
                },
                 new Feed(){
                    FeedId = "4",
                    Title = "خلاصه اشتراک ها",
                    Address = "https://www.dntips.ir/feed/news"
                },
            };
        }

        public static IEnumerable<Item> GetNews(string id)
        {
            XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address);
            var feeds = from feed in feedXML.Descendants("item")
                        select new Item
                        {
                            Title = feed.Element("title").Value,
                            Link = feed.Element("link").Value,
                            Description = feed.Element("description").Value
                        };
            return feeds;
        }

    }

کلاس SiteManager فقط یک لیست از فید‌ها دارد و متدی که با گرفتن شناسه‌ی فید ، یک لیست از آیتم‌های موجود در آن فید ایجاد می‌کند.

حال دو ApiController برای دریافت داده‌ها ایجاد می‌کنیم

public class FeedController : ApiController  
{
        // GET api/<controller>
        public IEnumerable<Feed> Get()
        {
            return SiteManager.Feeds;
        }
    }

public class ItemsController : ApiController
    {
        // GET api/<controller>/5
        public IEnumerable<Item> Get(string id)
        {
            return SiteManager.GetNews(id);
        }
    }

در View پیش‌فرض که Index از کنترلر Home  است،  یک Html ساده برای فرم  صفحه اضافه می‌کنیم 

<div>
    <div>
        <h2>Feeds</h2>
        <ul id="Feeds">
           
        </ul>
    </div>
    <div>
        <h2>Items</h2>
        <p id="FeedItems">
        </p>
    </div>
   
</div>

در المنت Feeds لیست فید‌ها را قرار می‌دهیم و در FeedItems آیتم‌های مربوط به هر فید. حال به سراغ کد‌های سمت کلاینت می‌رویم و به جای جاوا اسکریپت از Saltarelle استفاده می‌کنیم.

کلاس Program را از پروژه Client باز می‌کنیم و متد Main را به شکل زیر تغییر می‌دهیم:

static void Main()
        {
            jQuery.OnDocumentReady(() => {
                FillFeeds();
            });
        }

بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در می‌آید: 

$SaltarelleSample_Client_$Program.$main = function() {
$(function() {
$SaltarelleSample_Client_$Program.$fillFeeds();
});
};
$SaltarelleSample_Client_$Program.$main();

و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.

متد FillFeeds را به شکل زیر پیاده سازی می‌کنیم

private static void FillFeeds()
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/feed",
                Type = "GET",
                Success = (d,t,r) => {

                    // Fill 
                    var ul = jQuery.Select("#Feeds");
                    jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });
                }
            });
        }

آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر می‌پذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی می‌کند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.

خاصیت Success یک Delegate است که 3 پارامتر ورودی را می‌پذیرد.

public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);

data همان مقداریست که api باز می‌گرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه می‌کنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل می‌کنم.

کلاس Feed و Item

 [PreserveMemberCase()]
    public class Feed
    {
        //[ScriptName("FeedId")]
        public string FeedId;

        //[ScriptName("Title")]
        public string Title;

        //[ScriptName("Address")]
        public string Address;

    }

    [PreserveMemberCase()]
    public class Item
    {
        // [ScriptName("Title")]
        public string Title;

        // [ScriptName("Link")]
        public string Link;

        // [ScriptName("Description")]
        public string Description;
    }
Attrubute‌های زیادی در Saltarelle وجود دارند و از آنجایی که کامپایلر اسم فیلد‌ها را camelCase کامپایل می‌کند من برای جلوگیری از آن از PreserveMemberCase  بر روی هر کلاس استفاده کردم. می‌توانید اسم هر فیلد را سفارشی کامپایل نمایید. 
jQuery.Each((List<Feed>)d, (idx,i) => {
                        var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer");
                        li.Click(eve => {
                            FillData(i.FeedId);
                        });
                        ul.Append(li);
                    });

به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد می‌کنیم. همان طور که می‌بینید کلیه خواص، به شکل Fluent قابل اضافه شدن می‌باشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی می‌کند و در آخر li را به المنت ul اضافه می‌کنیم.

برای هر کلیک هم مانند مثال بالا api را با شناسه‌ی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد می‌کنیم.

private static void FillData(string p)
        {
            jQuery.Ajax(new jQueryAjaxOptions()
            {
                Url = "/api/items/" + p,
                Type = "GET",
                Success = (d, t, r) => {
                    var content = jQuery.Select("#FeedItems");
                    content.Html("");
                    foreach (var item in (List<Item>)d)
                    {
                        var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl");
                        var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title);
                        row.Append(link);
                        content.Append(row);
                    }
                }
            });
        }
خروجی برنامه به شکل زیر است: 

در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. library‌های متعددی برای Saltarelle  از قبیل  linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیت‌های زیادی برای نوشتن imported library‌های سفارشی نیز وجود دارد. 

مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.

مطالب
xamarin.android قسمت دوم
در بحث گذشته کنترل‌های مورد نظر را بصورت داینامیک تولید کردیم که در طراحی Appهای پیچیده مناسب نمی‌باشد و بهتر است فرم و طراحی گرافیکی را از قبل آماده کرده و در activity اجرا نماییم. به فرم‌های از قبل طراحی شده، layout گفته میشود. layout‌ها با فرمت xml ساخته می‌شوندو بنابراین به زبان سی شارپ مربوط نمی‌باشد.
   
در زامارین 2 نوع layout داریم
1: صفحات razor از قبل پردازش شده PreProcessRazorPaged
2: layout استاندارد اندروید با ساختار xml که در زامارین ساخته میشود. در اینجا یک طراح جهت طراحی گرافیکی اندروید نصب میشود که خروجی را به xml تبدیل میکند.
  
محل استاندارد طراحی layout
در دایرکتوری resource بر روی دایرکتوری layout راست کلیک نموده و add new item و سپس android layout را انتخاب می‌کنیم که در این حالت یک فایل با پسوند xml اضافه میشود. با انتخاب layout و زدن دکمه f4، پنجره properties باز شده و میتوانیم خصوصیات layout را تغییر دهیم.
پس از ایجاد layout که دستورات غیر اجرایی (مرده) می‌باشد، بایستی آن‌ها به کلاس‌های کنترل معادل خود در اندروید تبدیل شوند که به این عملیات inflating و یا inflation نیز گفته میشود. پس از عملیات inflating می‌توان کنترل‌ها را پیدا کرده و آنها را برنامه نویسی کنیم.

FindViewbyid در پارامتر ورودی خود از طریق Resource.id، نام کنترل را دریافت نموده و بصورت Object باز می‌گرداند که بایستی نتیجه خروجی را به کلاس همان کنترل Cast نماییم:
Button btn = FindViewById<Button>(Resource.Id.button1);
در اینجا جهت ساده شدن دستور Find، از ساختار Generic استفاده می‌شود. در این روش پس از دستور FindviewById، نوع کنترل را مشخص می‌نماییم و نتیجه خروجی را در متغیری از نوع var ذخیره نماییم که بطور اتوماتیک در سی شارپ نوع var به نوع آن شیء تبدیل میشود.
EditText همان Textbox خودمان می‌باشد. در Toolbox کنترلی به نام PlainText یک TextBox را به layout اضافه میکند؛ ولی در Activity یا همان برنامه نویسی، نام کلاس اصلی Textbox جهت برنامه نویسی، EditText می‌باشد:
FindViewById<EditText>(Resource.Id.txtname).Text = "";
که برای دسترسی مستقیم به Text و مقدار دهی به Property و یا Event‌ها، اینطور هم استفاده میشود.
  
ساخت برنامه‌های چندین فرمی

هر layout یک اکتیویتی مربوط به خودش را دارد. اگر بخواهیم بین فرم‌های مختلف حرکت کنیم، به ازای هر فرم، یک Activity کنترل کننده همان فرم را اضافه می‌کنیم. در یک Activity با ارسال سیگنالی به نام Intent که پارامتر اول آن، Activity مبدا یا This و پارامتر دوم آن، Activity مقصد می‌باشد. سپس به سیستم عامل اطلاع میدهیم که می‌خواهیم این سیگنال را ارسال کنیم و ارسال آن با StartActivity می‌باشد:
Intent _intent = new Intent(this, typeof(Activity2));
StartActivity(_intent);


ارسال پارامتر

جهت ارسال داده‌های معمولی bool,double,string,float,long,... و آرایه ای از آن، از دستور PutExtra استفاده می‌شود:
Intent _intent = new Intent(this, typeof(Activity2));
string Test="Vlaueone";

_intent.PutExtra("mainactivity", Test);
StartActivity(_intent);



گرفتن پارامتر

در Activity مقصد، اطلاعات مربوط به سیگنال ارسال شده، در مقادیر Global intent ذخیره می‌شود و بر اساس نوع داده ارسال شده، تابع GetExtra را می‌نویسیم:
string Getintent = Intent.GetStringExtra("mainactivity");



ارسال object از کلاس‌ها بین Activityها


در زامارین، object کلاس‌ها را به یک string استاندارد بصورت json تبدیل می‌کنیم و به این عملیات Serialization گفته میشود. این کار نیز توسط کتابخانه‌های مختلفی انجام می‌شود مانند NewtonSoft. سپس رشته json ایجاد شده را با تابع PutExtra، به همراه سیگنال Intent، به Activity دوم پاس میدهیم:
List<Product> products;
products = new List<Product>();
Product p = new Product
            {

                name = FindViewById<EditText>(Resource.Id.mainedittextname).Text,
                price = Convert.ToInt32(FindViewById<EditText>(Resource.Id.mainedittextprice).Text)
            };
products.Add(p);
Intent _intent = new Intent(this, typeof(Activity2));
_intent.PutExtra("products", JsonConvert.SerializeObject(products));
StartActivity(_intent);
در Activity دوم آن‌را توسط کتابخانه‌ی NewtonSoft، به کلاس اصلی DeSerialize میکنیم که عملیات آن با دستورات ذیل انجام  میشود:
var p = JsonConvert.DeserializeObject<List<Product>>(Intent.GetStringExtra("products"));
foreach (var item in p)
{
   Toast.MakeText(this, $" {item.name} , {item.price}",
                  ToastLength.Long).Show();
};
در کد بالا Toast را در حلقه می‌بینید که در واقع همانند alert در وب و یا همانند MessageBox در ویندوز فرم یا Wpf عمل میکند. در پارامتر اول به آن میگوییم که برای این اکتیویتی است. پارامتر دوم آن جهت نمایش مقدار و در پارامتر سوم، مدت نمایش طولانی برای آن در نظر گرفته شده است و با جایگزینی Long با Short، زمان نمایش آن کوتاه‌تر میشود و در آخر با show هم آن را نمایش میدهد.

در ادامه کلاس‌های مورد نظر را جهت تعریف در دایرکتوری Model ایجاد میکنیم. مثل کلاس Product که بصورت Public نیز می‌باشد. از منوی Tools -> Nuget Package Management -> Manage nuget Package for Solution  را انتخاب و سپس NewtonSoft را اضافه می‌کنیم.
از متد Finish برای اتمام کار و یا پایان Activity نیز استفاده میشود.
 
AleartDialog

AlertDialog زیر کلاس Dialog است که می‌تواند یک، دو و یا سه دکمه را نمایش دهد. اگر فقط می‌خواهید یک رشته را در این کادر محاوره ای نمایش دهید، از روش SetMessage استفاده کنید. قطعه کد زیر می‌تواند برای ایجاد یک AlertDialog ساده با دو دکمه حذف و لغو استفاده شود: 
//set alert for executing the task
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.SetTitle("Confirm delete");
alert.SetMessage("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.");
alert.SetPositiveButton("Delete", (senderAlert, args) =>
            {
                Toast.MakeText(this, "Deleted!", ToastLength.Short).Show();
            });

alert.SetNegativeButton("Cancel", (senderAlert, args) =>
            {
                Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show();
            });

Dialog dialog = alert.Create();
dialog.Show();
 
DialogFragment
AleartDialog فقط میتواند اطلاعات محدودی را نمایش دهد؛ مانند نمایش حداکثر یک یا دو Button,EditText به کاربر. در حالیکه در برنامه نیاز به دیالوگی با ظاهر سفارشی داریم تا بتوانیم ظاهر آن را تغییر دهیم.

Fragment

با استفاده از Fragment میتوانیم layout هایی از پیش طراحی شده را بسازیم و چند صفحه را بصورت layout با Activity پیاده سازی کنیم. در اینجا اگر layout دوم load بشود، در این حالت اگر نیاز به ورود داده‌های مورد نیاز از طریق layout اول یا اصلی را داشته باشد، برای کاربر خسته کننده می‌‌شود. از این رو بهتر است layout دوم کوچکی بر روی layout اول یا اصلی نمایش داده شود. از این رو میتوان بجای layout از DialogFragment استفاده کنیم.
کلاس  DialogFragment میتواند دیالوگی را در وسط صفحه نمایش دهد ولی view و ظاهر آن از یک layout از قبل ساخته شده load و نمایش داده میشود. این کار از طریق تکنیک Inflate انجام میشود.
در ادامه یک کلاس را از Dialog Fragment مشتق کرده و در تابع oncreate، ظاهر و یا layout از پیش طراحی شده را بصورت ذیل در حافظه load کرده و بعنوان نتیجه خروجی باز می‌گردانیم:
public class DialogFragmentActivity : DialogFragment
{
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var dlg = inflater.Inflate(Resource.Layout.layoutDialog, container, false);
            return dlg;
        }
}
و در اکتیویتی اصلی برای فراخوانی کلاس فرگمنت از کد زیر استفاده میشود:
DialogFragmentActivity dlg = new DialogFragmentActivity ();
dlg.Show(this.FragmentManager, "Fragment");
پارامتر اول در واقع بعنوان fragment manager معاون اکتیویتی می‌باشد و در پارامتر دوم هم یک اسم دلخواه را برای آن انتخاب میکنیم. در متد باز نویسی شده On start نیز میتوان طول و عرض آن و رنگ پشت زمینه Fragment را تغییر داد که کد‌های آن را در زیر میتوانید مشاهده کنید:
public override void OnStart()
{
            base.OnStart();
            Dialog dialog = Dialog;
            if (dialog != null)
            {
                dialog.Window.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                dialog.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
            }
}
اگر برنامه را اجرا و فرگمنت را نیز اجرا کنید، می‌بینید که یک کادر، بصورت Header در بالای فرگمنت نمایان شده‌است که زیاد جالب نیست. برای حذف Header فرگمنت، از کد ذیل میتوانید استفاده کنید:
public override Dialog OnCreateDialog(Bundle savedInstanceState)
{
            Dialog NotTitle = base.OnCreateDialog(savedInstanceState);
            NotTitle.Window.RequestFeature(WindowFeatures.NoTitle);

            return NotTitle;
}
مطالب
استفاده از MembershipProvider و RoleProvider در windows Application
برای استفاده از  سیستم مدیریت کاربران و نقش‌های آنها به یک پیاده سازی از کلاس انتزاعی MembershipProvider نیاز داریم. SQL Membership Provider تو کار دات نت، انتخاب پیش فرض ماست ولی به دلیل طراحی در دات نت 2 و نیاز سنجی قدیمی اون و همچنین گره زدن برنامه با sql server  (استفاده از stored procedure و... ) انتخاب مناسبی نیست. پیشنهاد خود مایکروسافت استفاده از SimpleMembership است که این پیاده سازی قابلیت‌های بیشتری از MembershipProvider پایه رو دارد. این قابلیت‌های بیشتر با استفاده از کلاس انتزاعی ExtendedMembershipProvider که خود از از MembershipProvider مشتق شده است میسر شده است.
برای این آموزش ما از SimpleMembership  استفاده می‌کنیم اگر  شما دوست ندارید از SimpleMembership  استفاده کنید می‌تونید از Provider  های دیگه ای استفاده کنید و حتی می‌تونید یک پروایدر سفارشی برای خودتون بنویسید.
برای شروع یک پروژه ConsoleApplication  تعریف کنید و رفرنس‌های زیر رو اضافه کنید.
System.Web.dll 
System.Web.ApplicationServices.dll
 خاصیت  Copy Local دو کتابخانه زیر رو true  ست کنید.
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies\WebMatrix.Data.dll
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies\WebMatrix.WebData.dll
- در صورتیکه یک پروژه Asp.Net MVC 4 به همراه تمپلت Internet Application بسازید بصورت خودکار SimpleMembership  و رفرنس‌های آن به پروژه اضافه می‌شود.
 یک فایل App.config با محتویات زیر به پروژه اضافه کنید  و تنظیمات ConnectionString را مطابق با دیتابیس موجود در کامپیوتر خود تنظیم کنید:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <connectionStrings>
    <add name="DefaultConnection" 
         connectionString="Data Source=.;Initial Catalog=SimpleMembershipProviderDB;Integrated Security=True;"
         providerName="System.Data.SqlClient"/>
  </connectionStrings>
  
  <system.web>
    <roleManager enabled="true" defaultProvider="SimpleRoleProvider">
      <providers>
        <clear/>
        <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
      </providers>
    </roleManager>
    <membership defaultProvider="SimpleMembershipProvider">
      <providers>
        <clear/>
        <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/>
      </providers>
    </membership>

  </system.web>
</configuration>
محتویات فایل Program.cs :
using System; 
using System.Security.Principal; 
using System.Web.Security;
using WebMatrix.WebData;

namespace MemberShipConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            WebSecurity.InitializeDatabaseConnection("DefaultConnection",
                "UserProfile", "UserId", "UserName", 
                autoCreateTables: true);

            AddUserAndRolSample();
            Login();
            if (System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated)
                RunApp();

        }

        static void AddUserAndRolSample()
        {
            if (WebSecurity.UserExists("iman"))
                return;

            //  No implements in SimpleMembershipProvider :
            //     Membership.CreateUser("iman", "123");
            WebSecurity.CreateUserAndAccount("iman", "123");
            Roles.CreateRole("admin");
            Roles.CreateRole("User");
            Roles.AddUserToRole("iman", "admin");
        }

        static void Login()
        {
            for (int i = 0; i < 3; i++)
            {
                Console.Write("UserName: ");
                var userName = Console.ReadLine();
                Console.Write("Password: ");
                var password = Console.ReadLine();
                if (Membership.ValidateUser(userName, password))
                {

                    var user = Membership.GetUser(userName);
                    var identity = new GenericIdentity(user.UserName);
                    var principal = new RolePrincipal(identity);
                    System.Threading.Thread.CurrentPrincipal = principal;

                    Console.Clear();
                    return;
                }


                Console.WriteLine("User Name Or Password Not Valid.");
            }

        }
        static void RunApp()
        {
            Console.WriteLine("Welcome To MemberShip. User Name: {0}",
                System.Threading.Thread.CurrentPrincipal.Identity.Name);

            if (System.Threading.Thread.CurrentPrincipal.IsInRole("admin"))
                Console.WriteLine("Hello Admin User!");

            Console.Read();
        }
    }
}
در ابتدا با فراخوانی متد InitializeDatabaseConnection  تنظیمات اولیه simpleMembership را مقدار دهی می‌کنیم. این متد حتما باید یکبار اجرا شود.
InitializeDatabaseConnection(string connectionStringName, 
                             string userTableName, 
                             string userIdColumn, 
                             string userNameColumn, 
                             bool autoCreateTables)                            
ملاحظه می‌کنید پارامتر آخر متد مربوط به ساخت جداول مورد نیاز این پروایدر است. در صورتی که بخواهیم در پروژه از EntityFramework استفاده کنیم می‌تونیم موجودیت‌های معادل جدول‌های مورد نیاز SimpleMembership  رو در EF  بسازیم و در این متد AutoCreateTables رو False مقدار دهی کنیم. برای بدست آوردن موجودیت‌های معادل جدول‌های این پروایدر با ابزار Entity Framework Power Tools و روش مهندسی معکوس ، تمام موجودیت‌های یک دیتابیس ساخته شده رو استخراج می‌کنیم.
 - SimpleMembership  از تمام خانواده microsoft sql پشتبانی می‌کنه (SQL Server, SQL Azure, SQL Server CE, SQL Server Express,LocalD)   برای سایر دیتابیس‌ها به علت تفاوت در دستورات ساخت تیبل‌ها و ... مایکروسافت تضمینی نداده ولی اگر خودمون جدول‌های مورد نیاز SimpleMembership  رو ساخته باشیم احتمالا در سایر دیتابیس‌ها هم قابل استفاده است.
 در ادامه برنامه بالا یک کاربر و دو نقش تعریف کردیم و نقش admin  رو به کاربر نسبت دادیم. در متد login در صورت معتبر بودن کاربر  ، اون رو به ترد اصلی برنامه معرفی می‌کنیم. هر جا خواستیم می‌تونیم نقشهای کاربر رو چک کنیم و نکته آخر با اولین چک کردن نقش یک کاربر تمام نقش‌های اون در حافظه سیستم کش می‌شود و تنها مرتبه اول با دیتابیس ارتباط برقرار می‌کند.

مطالب
نحوه کار Expression و ایجاد یک DynamicFilter
ساختار Expression‌ها شبیه به ساختار یک درخت است. به عنوان مثال زمانیکه شما یک فیلتر ساده را مانند دستور زیر اجرا میکنید:
Expression<Func<string, bool>> f = s => s.Length < 5;
Expression ایجاد شده از فیلتر شما به صورت زیر میباشد:

منبع : کتاب C# 8 in a Nutshell 

ParameterCollection به پارامترهای استفاده شده در فیلتر اشاره دارد که در فیلتر بالا فقط s استفاده شده‌است و از نوع string است.

BinaryExpression شامل سه قسمت مهم Left , Right و NodeType میباشد. برای فیلتر بالا، مقدار پراپرتی Left برابر s.Length می‌باشد و پراپرتی Right شامل مقدار 5 و مقدار NodeType هم برابر LessThan میباشد. یعنی فیلتر بالا به یک درخت تبدیل شده که نود اصلی آن LessThan است و دو مقدار Left و Right را باهم مقایسه میکند. اما اگر یک شرط دیگر را به فیلتر بالا اعمال کنیم، ساختار Expression کمی تغییر میکند. برای مثال: 

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;

Expression ایجاد شده برای این فیلتر شامل همان ساختار قبلی است؛ اما با این تغییر که هر کدام از پراپرتی‌های Right و Left، خود یک BinaryExpression شده‌اند و مقدار NodeType اصلی از LessThan به AndAlso تغییر پیدا کرده‌است. Expression ایجاد شده از فیلتر بالا ( filter.Body ) به این صورت است که پراپرتی Left آن برابر است با یک BinaryExpression که مقدار NodeType آن برابر است با GreaterThan و پراپرتی Left آن شامل s.Length میباشد و پراپرتی Right آن برابر 5 میباشد. همچنین پراپرتی Right مربوط به filter.Body برابر یک ExpressionBinary است که مقدار NodeType آن برابر است با LessThan و پراپرتی Left آن برابر s.Length است و پراپرتی Right آن برابر 45 میباشد.

filter.Body شبیه به تصویر زیر میباشد :

اگر بخواهیم خودمان یک Expression tree را ایجاد کنیم، باید از پایین‌ترین نود آن شروع کنیم. یعنی ابتدا باید پراپرتی Left و Right را ایجاد کنیم و سپس این دو پراپرتی را با هم مقایسه کنیم (NodeType). در کد زیر Expression مربوط به فیلتر بالا را نوشته‌ایم:

ParameterExpression parameterExpression = Expression.Parameter(typeof(string));
MemberExpression memberExpression = Expression.Property(parameterExpression, "Length");

ConstantExpression greaterThanConstantExpression = Expression.Constant(5);
BinaryExpression greaterThanComparison = Expression.GreaterThan(memberExpression, greaterThanConstantExpression);
var greaterThan = Expression.Lambda<Func<string, bool>>(greaterThanComparison, parameterExpression);

ConstantExpression lessThanConstantExpression = Expression.Constant(45);
BinaryExpression lessThanComparsion = Expression.LessThan(memberExpression, lessThanConstantExpression);
var lessThan = Expression.Lambda<Func<string, bool>>(lessThanComparsion, parameterExpression);

var param = Expression.Parameter(typeof(string), "x");
var body = Expression.AndAlso(
            Expression.Invoke(greaterThan, param),
            Expression.Invoke(lessThan, param)
        );
Expression<Func<string, bool>> filter = Expression.Lambda<Func<string, bool>>(body, param);

ParameterExpression : نوع پارامتری را که میخواهیم روی آن شرط را روی آن اعمال کنیم، مشخص کرده‌ایم.

MemberExpression  : پراپرتی Length را معرفی کرده‌ایم که قرار است شرطی بر روی این پراپرتی اعمال شود.

ConstantExpression   : مقدار ثابتی که پراپرتی MemeberExpression قرار است با آن مقایسه شود.

BinaryExpression   : نود تایپ را مشخص کرده‌ایم که برابر است با GreaterThan.

سپس Expression مربوط به هرکدام را در greaterThan و lessThan ایجاد کرده‌ایم و این دو را باهم And کرده و در متغییر body قرار داده‌ایم و در نهایت filter را با دستور Expression.Lambda ایجاد کرده‌ایم که برابر است با :

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;


ساخت یک داینامیک فیلتر

در ادامه میخواهیم یک داینامیک فیلتر را ایجاد کنیم که به طور مثال برنامه نویس از سمت فرانت‌اند بتواند فیلتر‌های ساده‌ای را اعمال کند. برای این کار یک کلاس برای فیلتر ایجاد میکنیم :

public class DynamicModel
{
    public string Name { get; set; }
    public string Comparison { get; set; }
    public object Data { get; set; }
}

پراپرتی Data مقداری است که باید با آن مقایسه انجام شود.

Comparison نوع عملیات را مشخص میکند مانند : Equal, LessThan, GreaterThan و... .

پراپرتی Name نام پراپرتی است که باید شرط روی آن اعمال شود.

کلاس ثابت ها:

public static class ComparisonConstant
{
    public const string LessThan = "LesThan";
    public const string LessThanEqual = "LesThanEqual";
    public const string GreaterThan = "GreaterThan";
    public const string GreaterThanEqual = "GreaterThanEqual";
    public const string Equal = "Equal";
    public const string NotEqual = "NotEqual";
}

ساخت اکستنشن متد:

public static IQueryable<TModel> DynamicFilter<TModel>(this IQueryable<TModel> iqueryable, IEnumerable<DynamicModel> dynamicModel)
{
    return iqueryable.Where(Filter<TModel>(dynamicModel));
}  
public static Expression<Func<TModel, bool>> Filter<TModel>(IEnumerable<DynamicModel> dynamicModel)
{
    Expression<Func<TModel, bool>> result = a => true;
    foreach (var item in dynamicModel)
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(TModel));
        MemberExpression memberExpression = Expression.Property(parameterExpression, item.Name);
        ConstantExpression constantExpression = Expression.Constant(item.Data);
        BinaryExpression comparison = GetBinaryExpression(item.Comparison, memberExpression, constantExpression);
        var expression = Expression.Lambda<Func<TModel, bool>>(comparison, parameterExpression);
        var param = Expression.Parameter(typeof(TModel), "x");
        var body = Expression.AndAlso(
                    Expression.Invoke(result, param),
                    Expression.Invoke(expression, param)
                );
        result = Expression.Lambda<Func<TModel, bool>>(body, param);
    }
    return result;
}

ورودی این مدل، لیستی از DynamicModel میباشد که به ازای هر کدام از آیتم‌ها، یک BinaryExpression ایجاد میکند و آن را با result تعریف شده And میکند. یعنی تمامی آیتم‌های ارسال شده باهم And میشوند.

متد GetBinaryExpression بر اساس مقدار فیلد Comparison که از سمت فرانت ارسال میشود، کار میکند:

private static BinaryExpression GetBinaryExpression(string comparison, MemberExpression memberExpression, ConstantExpression constantExpression)
{
    switch (comparison)
    {
        case ComparisonConstant.Equal:
            return Expression.Equal(memberExpression, constantExpression);
        case ComparisonConstant.LessThan:
            return Expression.LessThan(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThan:
            return Expression.GreaterThan(memberExpression, constantExpression);
        case ComparisonConstant.NotEqual:
            return Expression.NotEqual(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThanEqual:
            return Expression.GreaterThanOrEqual(memberExpression, constantExpression);
        case ComparisonConstant.LessThanEqual:
            return Expression.LessThanOrEqual(memberExpression, constantExpression);
        default:
            return null;
    }
}

 کلاس Category را در نظر بگیرید که شامل دو پراپرتی Title و Id میباشد و میخواهیم از این داینامیک فیلتر، برای فیلتر کردن دیتاها استفاده کنیم از سمت فرانت‌اند. اگر از سمت فرانت‌اند چنین دیتایی ارسال شود:

[
   {
      "Name":"Title",
      "Comparison":"Equal",
      "Data":"Hi"
   },
   {
      "Name":"Id",
      "Comparison":"LesThanEqual",
      "Data": 100
   }
]

تمامی رکوردهایی که مقدار پراپرتی Title آنها برابر Hi باشد و Id آن کوچکتر مساوی 100 باشد، از دیتابیس خوانده میشود.

var categories = _dbContext.Categories
                         .DynamicFilter(filter)//filter => IEnumerable<DynamicModel>
                         .ToList();

گیت هاب داینامیک فیلتر 

مطالب
بالا بردن سرعت بارگذاری اولیه EF Code first با تعداد مدل‌های زیاد
EF Code first هربار در حین آغاز اجرای برنامه و اولین کوئری که به بانک اطلاعاتی ارسال می‌کند، کار تشخیص روابط بین کلاس‌ها و همچنین نگاشت آن‌ها را به بانک اطلاعاتی، انجام می‌دهد. این مورد شاید با تعداد کم کلاس‌ها آنچنان به نظر نرسد، اما اگر تعداد کلاس‌های شما به بالای 200 عدد رسید، زمان آغاز برنامه آزار دهنده خواهد شد. راه حلی برای این مساله وجود دارد به نام ایجاد Viewهای متناظر با نگاشت‌ها و سپس کامپایل آن به عنوان جزئی از برنامه، که در ادامه نحوه انجام این‌کار را مرور خواهیم کرد.

بررسی ساختار pre-generated views

برای کامپایل نگاشت‌های EF در خود برنامه (بجای تولید پویای هربار آن‌ها)، ابتدا باید فایل edmx متناظر با مدل‌ها و روابط بین آن‌ها تشکیل شود:
    var ms = new MemoryStream();
    using (var writer = XmlWriter.Create(ms))
    {
        EdmxWriter.WriteEdmx(new Context(), writer);
    }
پس از اینکه edmx تشکیل شد، باید از ساختار فشرده آن سه جزء زیر را استخراج کرد:
الف) ssdl : storageModels
ب) csdl : conceptualModels
ج) msl : mappings

اینکار را به صورت زیر می‌توان انجام داد:
    var xDoc = XDocument.Load(ms);

    var ssdl = xDoc.Descendants("{http://schemas.microsoft.com/ado/2009/02/edm/ssdl}Schema").Single();
    var csdl = xDoc.Descendants("{http://schemas.microsoft.com/ado/2008/09/edm}Schema").Single();
    var msl = xDoc.Descendants("{http://schemas.microsoft.com/ado/2008/09/mapping/cs}Mapping").Single();
پس از آن باید محتوای این سه جزء را توسط متد Save هر کدام، در فایل‌های xml ایی ذخیره کرد و توسط ابزاری به نام EdmGen.exe که جزئی از  ویژوال استودیو است، فایل Context.Views.cs را تولید، به برنامه اضافه و سپس کامپایل کرد:
 EdmGen.exe /mode:ViewGeneration /incsdl:Context.csdl  /inmsl:Context.msl /inssdl:Context.ssdl /outviews:Context.Views.cs
بهتر است این پروسه هر بار که قرار است ارائه نهایی برنامه صورت گیرد، انجام شود.

علاوه بر این‌ها اگر علاقمند باشید که کار فایل EdmGen را شبیه سازی کنید، کلاس زیر این‌کار را انجام داده و قادر است خروجی vb یا cs متناظری را نیز تولید کند:
using System;
using System.Data.Entity;
using System.Data.Entity.Design;
using System.Data.Entity.Infrastructure;
using System.Data.Mapping;
using System.Data.Metadata.Edm;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

namespace EfUtils
{
    public static class PreGeneratedViewsWriter
    {
        public static void CreatePreGeneratedViewsFile(
                this DbContext contextInstance,
                LanguageOption language = LanguageOption.GenerateCSharpCode,
                string viewsFile = "Context.Views.cs",
                string edmxFile = "context.edmx",
                string ssdlFile = "context.ssdl.xml",
                string csdlFile = "context.csdl.xml",
                string mslFile = "context.msl.xml")
        {
            using (var contextViewsMemoryStream = new MemoryStream())
            {
                using (var edmxMemoryStream = new MemoryStream())
                {
                    var edmx = createEdmx(contextInstance, edmxFile, edmxMemoryStream);
                    var mappingItemCollection = createMappingItemCollection(ssdlFile, csdlFile, mslFile, edmx);
                    generateViews(language, viewsFile, contextViewsMemoryStream, mappingItemCollection);
                }
            }
        }

        private static void generateViews(LanguageOption language, string viewsFile, MemoryStream contextViewsMemoryStream, StorageMappingItemCollection mappingItemCollection)
        {
            var viewGenerator = new EntityViewGenerator // It's defined in System.Data.Entity.Design.dll
            {
                LanguageOption = language
            };
            using (var streamWriter = new StreamWriter(contextViewsMemoryStream))
            {
                var errors = viewGenerator.GenerateViews(mappingItemCollection, streamWriter).ToList();

                if (errors.Any())
                    throw new InvalidOperationException(errors.First().Message);

                contextViewsMemoryStream.Position = 0;
                using (var reader = new StreamReader(contextViewsMemoryStream))
                {
                    var codeData = reader.ReadToEnd();
                    File.WriteAllText(viewsFile, codeData);
                }
            }
        }

        private static StorageMappingItemCollection createMappingItemCollection(string ssdlFile, string csdlFile, string mslFile, XDocument edmx)
        {
            var ssdl = edmx.Descendants("{http://schemas.microsoft.com/ado/2009/02/edm/ssdl}Schema").Single();
            ssdl.Save(ssdlFile);
            var storeItemCollection = new StoreItemCollection(new[] { ssdl.CreateReader() });

            var csdl = edmx.Descendants("{http://schemas.microsoft.com/ado/2008/09/edm}Schema").Single();
            csdl.Save(csdlFile);
            var edmItemCollection = new EdmItemCollection(new[] { csdl.CreateReader() });

            var msl = edmx.Descendants("{http://schemas.microsoft.com/ado/2008/09/mapping/cs}Mapping").Single();
            msl.Save(mslFile);

            var mappingItemCollection = new StorageMappingItemCollection(edmItemCollection, storeItemCollection, new[] { msl.CreateReader() });
            return mappingItemCollection;
        }

        private static XDocument createEdmx(DbContext contextInstance, string edmxFile, MemoryStream edmxMemoryStream)
        {
            var settings = new XmlWriterSettings { Indent = true };
            using (var writer = XmlWriter.Create(edmxMemoryStream, settings))
            {
                EdmxWriter.WriteEdmx(contextInstance, writer);
            }
            File.WriteAllBytes(edmxFile, edmxMemoryStream.ToArray());

            edmxMemoryStream.Position = 0;
            var edmx = XDocument.Load(edmxMemoryStream);
            return edmx;
        }
    }
}
در اینجا همان مراحلی که عنوان شد، تکرار می‌شود. فایل edmx متناظر با وهله‌ای از DbContext برنامه، تولید شده و سه جزء آن استخراج می‌شوند. سپس این موارد به EntityViewGenerator موجود در اسمبلی System.Data.Entity.Design.dll ارسال شده و کد نهایی متناظر قابل کامپایل در برنامه تولید می‌گردد.
پس از تولید فایل Context.Views.cs یا Context.Views.vb، آن‌را به پروژه اضافه کنید.
اینبار نحوه استفاده از آن باید به صورت زیر باشد:
 Database.SetInitializer<MyContext>(null);
 از این جهت که تمام اطلاعات لازم جهت آغاز کار، در فایل تولیدی Context.Views وجود دارد و اکنون جزئی از فایل اجرایی برنامه است و نیازی به تکرار ساخت مجدد پویای آن نیست.

مرجع:
Entity Framework Code First View Generation Templates On Visual Studio Code Gallery

مطالب
Static Reflection

قابلیت Dynamic reflection یا به اختصار همان reflection متداول، از اولین نگارش‌های دات نت فریم در دسترس است و امکان دسترسی به اطلاعات مرتبط با کلاس‌ها، متدها، خواص و غیره را در زمان اجرا مهیا می‌سازد. تابحال به کمک این قابلیت، امکان تهیه‌ی ابزارهای پیشرفته‌ی زیر مهیا شده است:
انواع و اقسام
- فریم ورک‌های آزمون واحد
- code generators
- ORMs
- ابزارهای آنالیز کد
و ...


برای مثال فرض کنید که می‌خواهید برای یک کلاس به صورت خودکار، متدهای آزمون واحد تهیه کنید (تهیه یک code generator ساده). اولین نیاز این برنامه، دسترسی به امضای متدها به همراه نام آرگومان‌ها و نوع آن‌ها است. برای حل این مساله باید برای مثال یک parser زبان سی شارپ یا اگر بخواهید کامل‌تر کار کنید، به ازای تمام زبان‌های قابل استفاده در دات نت فریم ورک باید parser تهیه کنید که ... کار ساده‌ای نیست. اما با وجود reflection به سادگی می‌توان به این نوع اطلاعات دسترسی پیدا کرد و نکته‌ی مهم آن هم این است که مستقل است از نوع زبان مورد استفاده. به همین جهت است که این نوع ابزارها را در فریم ورک‌هایی که فاقد امکانات reflection هستند، کمتر می‌توان یافت. برای مثال کیفیت کتابخانه‌های آزمون واحد CPP در مقایسه با آنچه که در دات نت مهیا هستند، اصلا قابل مقایسه نیستند. برای نمونه به یکی از معظم‌ترین فریم ورک‌های آزمون واحد CPP که توسط گوگل تهیه شده مراجعه کنید : (+)
قابلیت Reflection ، مطلب جدیدی نیست و برای مثال زبان جاوا هم سال‌ها است که از آن‌ پشتیبانی می‌کند. اما نگارش سوم دات نت فریم ورک با معرفی lambda expressions ، LINQ و Expressions در یک سطح بالاتر از این Dynamic reflection متداول قرار گرفت.

تعریف Static Reflection :
استفاده از امکانات Reflection API بدون بکارگیری رشته‌ها، به کمک قابلیت اجرای به تعویق افتاده‌ی LINQ، جهت دسترسی به متادیتای المان‌های کد، مانند خواص، متدها و غیره.
برای مثال کد زیر را در نظر بگیرید:
//dynamic reflection
PropertyInfo property = typeof (MyClass).GetProperty("Name");
MethodInfo method = typeof (MyClass).GetMethod("SomeMethod");
این کد، یک نمونه از دسترسی به متادیتای خواص یا متدها را به کمک Reflection متداول نمایش می‌دهد. مهم‌ترین ایراد آن استفاده از رشته‌ها است که تحت نظر کامپایلر نیستند و تنها زمان اجرا است که مشخص می‌شود آیا MyClass واقعا خاصیتی به نام Name داشته است یا خیر.
چقدر خوب می‌شد اگر این قابلیت بجای dynamic بودن (مشخص شدن در زمان اجرا)، استاتیک می‌بود و در زمان کامپایل قابل بررسی می‌شد. این امکان به کمک lambda expressions و expression trees دات نت سه بعد، میسر شده است. کلیدهای اصلی Static Reflection کلاس‌های Func و Expression هستند. با استفاده از کلاس Func می‌توان lambda expression ایی را تعریف کرد که مقداری را بر می‌گرداند و توسط کلاس Expression می‌توان به محتوای یک delegate دسترسی یافت. ترکیب این دو، قدرت دستیابی به اطلاعاتی مانند PropertyInfo را در زمان طراحی کلاس‌ها، می‌دهد؛ با توجه به اینکه:
- کاملا توسط intellisense موجود در VS.NET پشتیبانی می‌شود.
- با استفاده از ابزارهای refactoring قابل کنترل است.
- از همه مهم‌تر، دیگری خبری از رشته‌ها نبوده و همه چیز تحت کنترل کامپایلر قرار می‌گیرد.

و شاید هیچ قابلیتی به اندازه‌ی Static Reflection در این چندسال اخیر بر روی اکوسیستم دات نت فریم ورک تاثیرگذار نبوده باشد. این روزها کمتر کتابخانه یا فریم ورکی را می‌توانید پیدا کنید که از Static Reflection استفاده نکند. سرآغاز استفاده گسترده از آن به Fluent NHibernate بر می‌گردد؛ سپس در انواع و اقسام mocking frameworks‌ ، ORMs و غیره استفاده شد و مدتی است که در ASP.NET MVC نیز مورد استفاده قرار می‌گیرد (برای مثال TextBoxFor معروف آن):
public string TextBoxFor<T>(Expression<Func<T,object>> expression);
به این ترتیب حین استفاده از آن دیگری نیازی نخواهد بود تا نام خاصیت مدل مورد نظر را به صورت رشته وارد کرد:
<%= this.TextBoxFor(model => model.FirstName); %>

یک مثال ساده از تعریف و بکارگیری Static Reflection :
public PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;

if (memberExpression == null)
throw new InvalidOperationException("Not a member access.");

return memberExpression.Member as PropertyInfo;
}
همانطور که عنوان شد کلیدهای اصلی بهر‌ه‌گیری از امکانات Static reflection ، استفاده از کلاس‌های Expression و Func هستند که در آرگومان متد فوق بکارگرفته شده‌اند و در حقیقت یک expression of a delegate است که به آن Lambdas as Data نیز گفته می‌شود. این delegate پارامتری از نوع T را دریافت کرده و سپس مقداری از نوع object را بر می‌گرداند. اما زمانیکه از کلاس Expression در اینجا استفاده می‌شود، این Func دیگر اجرا نخواهد شد، بلکه از آن به عنوان قطعه‌ کدی که اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده می‌شود.
برای نمونه Fluent NHibernate‌ در پشت صحنه متد Map ، به کمک متدی شبیه به GetProperty فوق، a => a.Address1 را به رشته متناظر خاصیت Address1 تبدیل کرده و جهت تعریف نگاشت‌ها مورد استفاده قرار می‌دهد:
public class AddressMap : DomainMap<Address>
{
public AddressMap()
{
Map(a => a.Address1);
}
}

جهت اطلاع؛ قابلیت استفاده از «کد به عنوان اطلاعات» هم مفهوم جدیدی نیست و برای مثال زبان Lisp چند دهه است که آن‌را ارائه داده است!

برای مطالعه بیشتر: