نظرات مطالب
خودکار کردن تعاریف DbSetها در EF Code first
در EF Core یک چنین شکلی را پیدا می‌کند (البته «روش صحیح بارگذاری پویای اسمبلی‌ها در NET Core.» را هم مدنظر داشته باشید):
    public class BloggingContext : DbContext
    {        
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            loadEntities(modelBuilder,
            asmPath: @"D:\Prog\SomeAsm1\bin\Debug\netstandard2.0\SomeAsm1.dll",
            nameSpace: "SomeAsm1.Models");
            base.OnModelCreating(modelBuilder);
        }

        private static void loadEntities(ModelBuilder modelBuilder, string asmPath, string nameSpace)
        {
            var modelInAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(asmPath);
            // var modelInAssembly = Assembly.Load(new AssemblyName("ModuleApp"));
            var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[] { });
            foreach (var type in modelInAssembly.ExportedTypes)
            {
                if (type.BaseType is System.Object && !type.IsAbstract && type.Namespace == nameSpace)
                {
                    entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
                }
            }
        }
نظرات مطالب
C# 7 - Pattern matching and switch expressions
C# 7.1 - Pattern-Matching with Generics

C# 7.1 پشتیبانی بهتری از pattern-matching را جهت کار با Generics ارائه داده‌است.
public class Car {}
public class SportsCar : Car
{
   public string Color { get; set; }
}
در اینجا یک کلاس پایه خودرو و سپس یک کلاس مشتق شده‌ی خودرو‌های ورزشی را داریم. اکنون در جائی از برنامه می‌خواهیم متد راندن این خودروها را تعریف کنیم:
public static void Run<T>(T car) where T : Car
{
   if (car is SportsCar sportsCar)
   {
   }

   switch (car)
   {
      case SportsCar sCar:
      break;
   }
}
در اینجا نوع خودرو به صورت جنریک تعریف شده‌است و سپس با استفاده از قابلیت‌های pattern-matching سعی در انطباق با آن‌ها را داریم. کامپایل این قطعه کد در C# 7.0 با خطای کامپایلر ذیل متوقف می‌شود:
 An expression of type "T" cannot be handled by a pattern of type "SportsCar"

اگر این قطعه کد را بخواهیم با C# 7.0 کامپایل کنیم نیاز است ابتدا شیء دریافتی به object تبدیل شود و سپس کار pattern-matching با موفقیت صورت خواهد گرفت:
public static void Run<T>(T car) where T : Car
{
   if ((object)car is SportsCar sportsCar)
   {
   }

   switch ((object)car)
   {
      case SportsCar sCar:
      break;
   }
}
این محدودیت در C# 7.1 برطرف شده‌است و دیگر نیازی به این cast اضافه نیست و می‌توان (object) را از قطعه کد فوق حذف کرد.
مطالب
آموزش Cache در ASP.NET Core - (قسمت اول : مفاهیم اولیه)
امروزه در وب‌سایت‌های شخصی و تجاری، یکی از مهم‌ترین پارامتر‌ها، سرعت پاسخگویی درخواست‌ها به وب‌سایت است. طبق آمار، کاربران آنلاین کنونی که ما با آن‌ها طرفیم، سطح تحملشان به سه ثانیه در یک صفحه میرسد؛ پس ما باید بتوانیم سرعت وب‌سایت‌های خودمان را تا حد ممکن بهبود بخشیم. از طرفی پارامتر سرعت، روی سئو گوگل هم تاثیر بسزایی دارد و Ranking وب‌سایت شمارا تا حد زیادی افزایش می‌دهد. قطعا همه می‌دانید که سرعت وبسایت و برنامه چقدر مهم هست؛ پس زیاده گویی نمی‌کنیم و می‌رویم سراغ اصل مطلب.
یکی از کارهایی که میتوانیم برای افزایش سرعت برنامه انجام دهیم، استفاده از Cache هست. بطور خیلی ساده، Cache یعنی قرار دادن دیتای پرکاربرد، در یک حافظه‌ی نزدیک‌تر از دیتابیس که هروقت به آن نیاز داشتیم، به آن دسترسی سریعی داشته باشیم و سرعت واکشی اطلاعات، از سرعتی که دیتابیس به ما می‌دهد، بیشتر باشد تا درخواست‌های ما با پاسخ سریع‌تری همراه شوند.
این حافظه، Ram هست و عمل Caching به اینصورت خواهد بود که هر وقت دیتای مورد نظر یکبار از دیتابیس واکشی شود، از دفعات بعد، آن دیتا را در Ram ذخیره میکند و برای درخواست‌های بعدی به دیتابیس Query نمیزند و دیتای مورد نیازش را از Ram میگیرد.
این امر در کنار مزایایی که دارد ، حساسیت بالایی هم بهمراه خواهد داشت؛ چرا که حافظه مورد استفاده Ram، یک حافظه محدود هست همچنین میتواند برای هر سخت افزاری متفاوت باشد. پس پیاده سازی این سیستم نیاز به دو دو تا چهارتا و ساختار درست دارد؛ در غیر اینصورت Cache کردن دیتای غلط میتواند به تنهایی وب‌سایتتان را Down کند؛ پس خیلی باید به این موضوع دقت داشت.

چه زمانی بهتر است از کش استفاده کنیم؟
  • وقتی دیتایی داریم که به تکرار از آن در برنامه استفاده میکنیم.
  • وقتی بعد از گرفتن دیتایی از دیتابیس، محاسباتی بر روی آن انجام میدهیم و پاسخ نهایی محاسبه را به کاربر نمایش میدهیم، میتوانیم یکبار پاسخ را کش کنیم تا از محاسبه‌ی هر باره‌ی آن جلوگیری شود.

آیا تمام اطلاعات را میتوان کش کرد؟
خیر.
  • سخت افزاری که برای کش استفاده میکنیم یعنی Ram، بسیار گران‌تر از دیتابیس برای ما تمام میشود؛ چرا که محدود است.
  • اگر همه دیتاهارا کش کنید، عمل سرچ میان آن زمان بیشتری خواهد برد.
پس اکنون میدانید که میتوانیم داده‌های بی نهایتی را در دیتابیس ذخیره کنیم و فقط با ارزش‌ترین‌ها و پر مصرف‌ترین هارا در حافظه کش، ذخیره میکنیم.

عملیات Cache در Asp.Net Core توسط اینترفیس‌های IMemoryCache و IDistributedCache مدیریت میشود و میتوانید با تزریق این اینترفیس‌ها براحتی از متدهایشان استفاده کنید؛ اما قبل از استفاده لازم است با عملکرد هر یک از آن‌ها آشنا شویم.

روش اول : In-memory Caching (Local Caching)
معمول‌ترین و ابتدایی‌ترین روش برای کش کردن اطلاعات، روش Local Caching و بصورت In-Memory است که اطلاعات را در حافظه Ram همان سروری که برنامه در آن اجرا میشود، کش میکند.

این روش تا زمانیکه برنامه‌ی ما برای اجرا شدن، تنها از یک سرور استفاده کند، بهترین انتخاب خواهد بود؛ چرا که به دلیل نزدیک بودن، سریع‌ترین بازخورد را نیز به درخواست‌ها ارائه میدهد.


اما شرایطی را فرض کنید که برنامه از چندین سرور برای اجرا شدن استفاده میکند و به طبع هر سرور درخواست‌های خودش را داراست که ما باید برای هر یک بصورت جداگانه‌ای یک کش In-Memory را در حافظه Ram هرکدام ایجاد کنیم. 

فرض کنید دیتای ما 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 باشد. بخشی از دیتا در Server 1 کش میشود (1 , 3 , 5 , 9) و بخشی دیگر در Server 2 کش خواهد شد (2 , 4 , 6 ,7 , 8 , 10).


در اینجا مشکلات و ضعف هایی به وجود خواهد آمد :


  • برای مثال اگر Server 1 به هر دلیلی از بین برود یا Down شود، اطلاعات کش درون آن نیز پاک خواهد شد و بعد از راه اندازی باید همه آن را دوباره از دیتابیس بخواند.
  • هر کدام از سرور‌ها کش‌های جدایی دارند و باهم Sync نیستند و امکان وجود یک داده‌ی حیاتی در یکی و عدم وجود آن در دیگری، بالاست. فرض کنید برنامه برای هر درخواست، نیاز به اطلاعات دسترسی کاربری را دارد. دسترسی‌های کاربر، در Server 1 کش شده، اما در Server 2 موجود نیست. در Server 2 به دلیل عدم وجود این کش، برنامه برای درخواست‌های معمول خود و چک کردن دسترسی کاربر یا باید هربار به دیتابیس درخواستی را ارسال کند که این برخلاف خواسته ماست و یا باید دیتای مربوط به دسترسی‌های کاربر را بعد از یکبار درخواست، از دیتابیس در خودش کش کند که این‌هم دوباره کاری به حساب میاید و دوبار کش کردن یک دیتا، امر مطلوبی نخواهد بود.

روش هایی وجود دارد که بتوان از سیستم Local Caching در حالت چند سروری هم استفاده کرد و این مشکلات را از بین برد، اما روش استاندارد در حالت چند سروری، استفاده از Distributed Cache‌ها است.


روش دوم : Distributed Caching

در این روش برنامه‌ی ما برای اجرا شدن از چندین سرور شبکه شده به هم، در حال استفاده هست و Cache برنامه، توسط سرورها به اشتراک گذاشته شده. 

در این حالت سرور‌های ما از یک کش عمومی استفاده میکنند که مزایای آن شامل :

■ درخواست‌ها به چندین سرور مختلف از هم ارسال شده، اما دیتای کش بصورت منسجم در هریک وجود خواهد داشت.

■ با خراب شدن یا Down شدن یک سرور، کش موجود در سرور‌های دیگر پاک نمیشود و کماکان قابل استفاده است.

■ به حافظه Ram یک سرور محدود نیست و مشکلات زیادی همچون کمبود سخت افزاری و محدودیت‌های حافظه‌ی Ram را تا حد معقولی کاهش میدهد.


طریقه استفاده از Cache در Asp.Net Core :

  • بر خلاف ASP.NET web forms و ASP.NET MVC در نسخه‌های Core به بعد، Cache بصورت از پیش ثبت شده، وجود ندارد. کش در Asp.Net Core با فراخوانی سرویس‌های مربوطه‌ی آن قابل استفاده است و نیاز است قبل از استفاده، سرویس آن را در کلاس Startup برنامه فراخوانی کنید. 
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddMemoryCache();
}

  • اینترفیس IMemoryCache از سیستم تزریق وابستگی‌ها در Core استفاده میکند و برای استفاده از اینترفیس آن، پس از اضافه کردن MemoryCache به Startup ، باید در کنترلر، عمل تزریق وابستگی (DI) را انجام دهید؛ سپس متد‌های مورد نیاز برای کش، در دسترس خواهد بود. 
public class HomeController : Controller
{
    private readonly IMemoryCache  _cache;
    public HomeController(IMemoryCache  cache)
    {
        _cache = cache;
    }
    ....
}

  • برای ذخیره‌ی کش میتوانید از متد Set موجود در این اینترفیس استفاده کنید. 
public IActionResult Set()
{
  _cache.Set("CacheKey", data , TimeSpan.FromDays(1));
  return View();
}

در پارامتر اول این متد (CacheKey)، یک کلید، برای اطلاعاتی که میخواهیم کش کنیم قرار میدهیم. دقت کنید که این کلید، شناسه‌ی دیتای شماست و باید طوری آن را در نظر گرفت که با صدا زدن این کلید از سرویس کش، همان دیتای مورد نظر را برگشت دهد (هر Object دیتا، باید کلید Unique خود را داشته باشد).


در پارامتر دوم، دیتای مورد نظر را که میخواهیم کش کنیم، به متد میدهیم و در پارامتر سوم نیز زمان اعتبار و تاریخ انقضای دیتای کش شده را وارد میکنیم؛ به این معنا که دیتای کش شده، بعد از مدت زمان گفته شده، از حافظه کش(Ram) حذف شود و برای دسترسی دوباره و کش کردن دوباره اطلاعات، نیاز به خواندن مجدد از دیتابیس باشد.


  • برای دسترسی به اطلاعات کش شده میتوانید از متد Get استفاده کنید. 
public IActionResult Get()
{
  string data = _cache.Get("CacheKey");
  return View(data);
}

تنها پارامتر ورودی این متد، کلید از قبل نسبت داده شده به اطلاعات کش هست که با استفاده از یکسان بودن کلید در ورودی این متد و کلید Set شده از قبل در حافظه Ram، دیتا مربوط به آن را برگشت میدهد.


  • متد TryGetValue برای بررسی وجود یا عدم وجود یک کلید در حافظه کش هست و یک Boolean را خروجی میدهد. 
public IActionResult Set()
  {
        DateTime data;
       // Look for cache key.
       if (!_cache.TryGetValue( "CacheKey" , out data))
       {
              // Key not in cache, so get data.
              data= DateTime.Now;

            // Save data in cache and set the relative expiration time to one day
             _cache.Set( "CacheKey" , data, TimeSpan.FromDays(1));
        }
        return View(data);
  }

این متد ابتدا بررسی میکند که کلیدی با نام "CacheKey" وجود دارد یا خیر؟ در صورت عدم وجود، آن را میسازد و دیتای مورد نظر را به آن نسبت میدهد.


  • با استفاده از متد GetOrCreate میتوانید کار متد‌های Get و Set را باهم انجام دهید و در یک متد، وجود یا عدم وجود کش را بررسی و در صورت وجود، مقداری را return و در صورت عدم وجود، ابتدا ایجاد کش و بعد return مقدار کش شده را انجام دهید. 
 public IActionResult GetOrCreate()
{
         var data = _cache.GetOrCreate( "CacheKey" , entry =>
         entry.SlidingExpiration = TimeSpan.FromSeconds(3);
         return View(data);
});
    return View(data);
}

  • برای مدیریت حافظه‌ی Ram شما باید یک Expiration Time را برای کش‌های خود مشخص کنید؛ تا هم حافظه Ram را حجیم نکنید و هم در هر بازه‌ی زمانی، دیتای بروز را از دیتابیس بخوانید. برای این کار option‌های متفاوتی از جمله absolute expiration و sliding expiration وجود دارند.

در اینجا absolute expiration به این معنا است که یک زمان قطعی را برای منقضی شدن کش‌ها مشخص میکند؛ به عبارتی میگوییم کش با کلید فلان، در تاریخ و ساعت فلان حذف شود. اما در sliding expiration یک بازه زمانی برای منقضی شدن کش‌ها مشخص میکنیم؛ یعنی میگوییم بعد از گذشت فلان دقیقه از ایجاد کش، آن را حذف کن و اگر در طی این مدت مجددا خوانده شد، طول مدت زمان آن تمدید خواهد شد.

این تنظیمات را میتوانید در قالب یک option زمان Set کردن یک کش، به آن بدهید. 

MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
options.AbsoluteExpiration = DateTime.Now.AddMinutes(1);
options.SlidingExpiration = TimeSpan.FromMinutes(1);
_cache.Set("CacheKey", data, options );

در مثال بالا هردو option اضافه شده یک کار را انجام میدهند؛ با این تفاوت که absolute expiration تاریخ now را گرفته و یک دقیقه بعد را به آن اضافه کرده و تاریخ انقضای کش را با آن تاریخ set میکند. اما sliding expiration از حالا بمدت یک دقیقه اعتبار دارد.


  • یکی از روش‌های مدیریت حافظه Ram در کش‌ها این است که برای حذف شدن کش‌ها از حافظه، اولویت بندی‌هایی را تعریف کنید. اولویت‌ها در چهار سطح قابل دسترسی است: 

  1.  NeverRemove = 3
  2.  High = 2
  3. Normal = 1
  4.  Low = 0 

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

MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
// Low / Normal / High / NeverRemove
options.Priority = CacheItemPriority.High;
cache.Set("CacheKey", data, options);

به این صورت میتوانید الویت‌های متفاوت را در قالب option به کش‌های خود اختصاص دهید. 

در این مقاله سعی شد مفاهیم اولیه Cache، طوری گفته شود، تا برای افرادی که میخواهند به تازگی این سیستم را بیاموزند و در پروژه‌های خود استفاده کنند، کاربردی باشد و درک نسبی را نسبت به مزایا و محدودیت‌های این سیستم بدست آورند.


در قسمت دوم همین مقاله بطور تخصصی‌تر به این مبحث میپردازیم و یک پکیج آماده را معرفی میکنیم که خیلی راحت‌تر و اصولی‌تر کش را برای ما پیاده سازی میکند.

مطالب
هاست سرویس های Asp.Net Web Api با استفاده از OWIN و TopShelf
زمانیکه از Template‌های پیش فرض تدارک دیده شده در VS.Net برای اپلیکیشن‌های وب خود استفاده می‌کنید، وب اپلیکیشن و سرور با هم یکپارچه هستند و تحت IIS  اجرا می‌شوند. به وسیله Owin می‌توان این دو مورد را بدون وابستگی به IIS به صورت مجزا اجرا کرد. در این پست قصد داریم سرویس‌های Web Api را در قالب یک Windows Service با استفاده از کتابخانه‌ی TopShelf هاست نماییم.
پیش نیاز ها:
»Owin چیست
»تبدیل برنامه‌های کنسول ویندوز به سرویس ویندوز ان تی

  برای شروع یک برنامه Console Application ایجاد کرده و اقدام به نصب پکیج‌های زیر نمایید:
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package TopShelf

حال یک کلاس Startup برای پیاده سازی Configuration‌های مورد نیاز ایجاد می‌کنیم
در این قسمت می‌توانید تنظیمات زیر را پیاده سازی نمایید:
»سیستم Routing؛
»تنظیم  Dependency Resolver برای تزریق وابستگی کنترلر‌های Web Api؛
»تنظیمات hub‌های SignalR(در حال حاضر SignalR به صورت پیش فرض نیاز به Owin برای اجرا دارد)؛
»رجیستر کردن Owin Middleware‌های نوشته شده؛
»تغییر در Asp.Net PipeLine؛
»و...

public class Startup 
    {       
        public void Configuration(IAppBuilder appBuilder) 
        {          
            HttpConfiguration config = new HttpConfiguration(); 
            config.Routes.MapHttpRoute( 
                name: "DefaultApi", 
                routeTemplate: "api/{controller}/{id}", 
                defaults: new { id = RouteParameter.Optional } 
            ); 

            appBuilder.UseWebApi(config); 
        } 
    }
* به صورت پیش فرض نام این کلاس باید Startup و نام متد آن نیز باید Configuration باشد.

در این مرحله یک کنترلر Api به صورت زیر به پروژه اضافه نمایید:
public class ValuesController : ApiController 
    {        
        public IEnumerable<string> Get() 
        { 
            return new string[] { "value1", "value2" }; 
        } 
      
        public string Get(int id) 
        { 
            return "value"; 
        } 

        public void Post([FromBody]string value) 
        { 
        } 

        public void Put(int id, [FromBody]string value) 
        { 
        }        
    }
کلاسی به نام ServiceHost ایجاد نمایید و کد‌های زیر را در آن کپی کنید:
public class ServiceHost
    {     
        private IDisposable webApp;             

        public static string BaseAddress 
        {
            get
            {
                return "http://localhost:8000/";
            }
        }

        public void Start()
        {           
            webApp = WebApp.Start<Startup>(BaseAddress);          
        }

        public void Stop()
        {           
            webApp.Dispose();          
        }
    }
واضح است که متد Start در کلاس بالا با استفاده از متد Start کلاس WebApp، سرویس‌های Web Api را در آدرس مورد نظر هاست خواهد کرد. با فراخوانی متد Stop این سرویس‌ها نیز dispose خواهند شد.
در مرحله آخر باید شروع و توقف سرویس‌ها را تحت کنترل کلاس HostFactory کتابخانه TopShelf در آوریم. برای این کار کافیست کلاسی به نام ServiceHostFactory ایجاد کرده و کد‌های زیر را در آن کپی نمایید:
public class ServiceHostFactory
    {
        public static void Run()
        {
            HostFactory.Run( config =>
            {
                config.SetServiceName( "ApiServices" );
                config.SetDisplayName( "Api Services ]" );
                config.SetDescription( "No Description" );

                config.RunAsLocalService();

                config.Service<ServiceHost>( cfg =>
                {
                    cfg.ConstructUsing( builder => new ServiceHost() );

                    cfg.WhenStarted( service => service.Start() );
                    cfg.WhenStopped( service => service.Stop());
                } );
            } );
        }
    }
توضیح کد‌های بالا:
ابتدا با فراخوانی متد Run سرویس مورد نظر اجرا خواهد شد. تنظیمات نام سرویس و نام مورد نظر جهت نمایش  و همچنین توضیحات در این قسمت انجام می‌گیرد.
با استفاده از متد ConstructUsing عملیات وهله سازی از سرویس انجام خواهد گرفت. در پایان نیز متد Start  و Stop کلاس ServiceHost، به عنوان عملیات شروع و پایان سرویس ویندوز مورد نظر تعیین شد.

حال اگر در فایل Program پروژه، دستور زیر را فراخوانی کرده و برنامه را ایجاد کنید خروجی زیر قابل مشاهده است.
ServiceHostFactory.Run();

در حالیکه سرویس مورد نظر در حال اجراست، Browser را گشوده و آدرس http://localhost:8000/api/values/get را در AddressBar وارد کنید. خروجی زیر را مشاهده خواهید کرد:

مطالب
پیاده سازی authorization به روش AOP به کمک کتابخانه های SNAP و StructureMap
همانطور که پیشتر در این مقاله بحث شده است، بوسیله AOP می‌توان قابلیت‌هایی که قسمت عمده‌ای از برنامه را تحت پوشش قرار می‌دهند، کپسوله کرد. یکی از قابلیت‌هایی که در بخشهای مختلف یک سیستم نرم‌افزاری مورد نیاز است، Authorization یا اعتبارسنجی‌ست. در ادامه به بررسی یک پیاده‌سازی به این روش می‌پردازیم.
 
کتابخانه SNAP
کتابخانه SNAP به گفته سازنده آن، با یکپارچه‌سازی AOP با IoC Container‌های محبوب، برنامه‌نویسی به این سبک را ساده می‌کند. این کتابخانه هم اکنون علاوه بر structureMap از IoC Providerهای Autofac, Ninject, LinFu و Castle Windsor نیز پشتیبانی میکند. 
دریافت SNAP.StructureMap 
برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو اجرا کنید:
PM> Install-Package snap.structuremap
پس از اجرای دستور فوق، کتابخانه SNAP.StructureMap که در زمان نگارش این مطلب نسخه 1.8.0 آن موجود است به همراه کلیه نیازمندی‌های آن که شامل موارد زیر می‌باشد نصب خواهد شد.
StructureMap (≥ 2.6.4.1)
CommonServiceLocator.StructureMapAdapter (≥ 1.1.0.3)
SNAP (≥ 1.8)
fasterflect (≥ 2.1.2)
Castle.Core (≥ 3.1.0)
CommonServiceLocator (≥ 1.0)

تنظیمات SNAP 

از آنجا که تنظیمات SNAP همانند تنظیمات StructureMap تنها باید یک بار اجرا شود، بهترین جا برای آن در یک برنامه وب، Application_Start فایل Global.asax است.
namespace Framework.UI.Asp
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            initSnap();
            initStructureMap();
        }

        private static void initSnap()
        {
            SnapConfiguration.For<StructureMapAspectContainer>(c =>
            {
                // Tell Snap to intercept types under the "Framework.ServiceLayer..." namespace.
                c.IncludeNamespace("Framework.ServiceLayer.*");
                // Register a custom interceptor (a.k.a. an aspect).
                c.Bind<Framework.ServiceLayer.Aspects.AuthorizationInterceptor>()
                .To<Framework.ServiceLayer.Aspects.AuthorizationAttribute>();
            });
        }

        void Application_EndRequest(object sender, EventArgs e)
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        }

        private static void initStructureMap()
        {
            var thread = StructureMap.Pipeline.Lifecycles.GetLifecycle(InstanceScope.HttpSession);
            ObjectFactory.Configure(x =>
            {
                x.For<IUserManager>().Use<EFUserManager>();
                x.For<IAuthorizationManager>().LifecycleIs(thread)
                 .Use<EFAuthorizationManager>().Named("_AuthorizationManager");                
                x.For<Framework.DataLayer.IUnitOfWork>()
                 .Use<Framework.DataLayer.Context>();

                x.SetAllProperties(y =>
                {
                    y.OfType<IUserManager>();
                    y.OfType<Framework.DataLayer.IUnitOfWork>();
                    y.OfType<Framework.Common.Web.IPageHelpers>();
                });
            });
        }
    }
}


بخش اعظم کدهای فوق در مقاله‌های «استفاده از StructureMap به عنوان یک IoC Container» و «تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET Web forms» شرح داده شده‌اند، تنها بخش جدید متد ()initSnap است، که خط اول آن به snap می‌گوید همه کلاس‌هایی که در فضای نام Framework.ServiceLayer و زیرمجموعه‌های آن هستند را پوشش دهد. خط دوم نیز کلاس AuthorizationInterceptor را به عنوان مرجعی برای handle کردن AuthorizationAttribute معرفی می‌کند.
در ادامه به بررسی کلاس AuthorizationInterceptor می‌پردازیم.
namespace Framework.ServiceLayer.Aspects
{
    public class AuthorizationInterceptor : MethodInterceptor
    {
        public override void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute)
        {
            var AuthManager = StructureMap.ObjectFactory
.GetInstance<Framework.ServiceLayer.UserManager.IAuthorizationManager>();
            var FullName = GetMethodFullName(method);
            if (!AuthManager.IsActionAuthorized(FullName))
                throw new Common.Exceptions.UnauthorizedAccessException("");

            invocation.Proceed(); // the underlying method call
        }

        private static string GetMethodFullName(MethodBase method)
        {
            var TypeName = (((System.Reflection.MemberInfo)(method)).DeclaringType).FullName;
            return TypeName + "." + method.Name;
        }
    }

    public class AuthorizationAttribute : MethodInterceptAttribute
    { }
کلاس مذکور از کلاس MethodInterceptor کتابخانه snap ارث بری کرده و متد InterceptMethod را تحریف میکند. این متد، کار اجرای متد اصلی ای که با این Aspect تزئین شده را بر عهده دارد. بنابراین می‌توان پیش از اجرای متد اصلی،  اعتبارسنجی را انجام داد.
 
کلاس MethodInterceptor   
کلاس MethodInterceptor  شامل چندین متد دیگر نیز هست که میتوان برای سایر مقاصد از جمله مدیریت خطا و Event logging از آنها استفاده کرد.
namespace Snap
{
    public abstract class MethodInterceptor : IAttributeInterceptor, IInterceptor, IHideBaseTypes
    {
        protected MethodInterceptor();

        public int Order { get; set; }
        public Type TargetAttribute { get; set; }

        public virtual void AfterInvocation();
        public virtual void BeforeInvocation();
        public void Intercept(IInvocation invocation);
        public abstract void InterceptMethod(IInvocation invocation, MethodBase method, Attribute attribute);
        public bool ShouldIntercept(IInvocation invocation);
    }
}
 

یک نکته  

نکته مهمی که در اینجا پیش می‌آید این است که برای اعتبارسنجی، کد کاربری شخصی که لاگین کرده، باید به طریقی در اختیار متد ()IsActionAuthorized قرار بگیرد. برای این کار می‌توان در یک HttpMudole به عنوان مثال همان ماژولی که برای تسهیل در کار تزریق خودکار وابستگی‌ها در سطح فرم‌ها استفاده می‌شود، با استفاده از امکانات structureMap به وهله‌ی ایجاد شده از AuthorizationManager (که با کمک structureMap با طول عمر InstanceScope.HttpSession ساخته شده است) دسترسی پیدا کرده و خاصیت مربوطه را مقداردهی کرد.
        private void Application_PreRequestHandlerExecute(object source, EventArgs e)
        {
            var page = HttpContext.Current.Handler as BasePage; // The Page handler
            if (page == null)
                return;

            WireUpThePage(page);
            WireUpAllUserControls(page);

            var UsrCod = HttpContext.Current.Session["UsrCod"];
            if (UsrCod != null)
            {
                var _AuthorizationManager = ObjectFactory
                    .GetNamedInstance<Framework.ServiceLayer.UserManager.IAuthorizationManager>("_AuthorizationManager");

                ((Framework.ServiceLayer.UserManager.EFAuthorizationManager)_AuthorizationManager)
                    .AuditUserId = UsrCod.ToString();
            }
        }
 
روش استفاده
نحوه استفاده از Aspect تعریف شده در کد زیر قابل مشاهده است:
namespace Framework.ServiceLayer.UserManager
{
    public class EFUserManager : IUserManager
    {
        IUnitOfWork _uow;
        IDbSet<User> _users;

        public EFUserManager(IUnitOfWork uow)
        {
            _uow = uow;
            _users = _uow.Set<User>();
        }

        [Framework.ServiceLayer.Aspects.Authorization]
        public List<User> GetAll()
        {
            return _users.ToList<User>();
        }
    }
}

مطالب دوره‌ها
نگاهی به انواع Aspects موجود در کتابخانه PostSharp
تعدادی Aspect توکار در کتابخانه PostSharp قرار دارند که نقطه آغازین کار با آن‌را تشکیل می‌دهند. نمونه‌ای از آن‌را در قسمت قبل به نام OnMethodBoundaryAspect بررسی کردیم. اغلب این‌ها کلاس‌هایی هستند Abstract که با تهیه‌ی کلاس‌هایی مشتق شده از آن‌ها و override نمودن متدهای کلاس پایه، می‌توان Aspect جدیدی را ایجاد نمود. تمام این نوع Aspects در حقیقت نوعی مزین کننده به شمار می‌روند. در ادامه قصد داریم نگاهی داشته باشیم به سایر Aspects مهیای در کتابخانه PostSharp.

1) OnExceptionAspect

از OnExceptionAspect برای مدیریت استثناءهای متدها استفاده می‌شود. کار این Aspect، اضافه کردن try/catch به کدهای یک متد است و سپس فراخوانی متد OnException در صورت بروز خطایی در این بین.
using System;
using System.Reflection;
using PostSharp.Aspects;

namespace AOP03
{
    public class ApplicationExceptionHandlerAspect : OnExceptionAspect
    {
        public override void OnException(MethodExecutionArgs args)
        {
            Console.WriteLine("Exception Type: {0}, StackTrace: {1}",
                               args.Exception.GetType().Name,
                               args.Exception.StackTrace);
        }

        public override Type GetExceptionType(MethodBase targetMethod)
        {
            return typeof(ApplicationException);
        }
    }
}
مثالی را در این زمینه در کدهای فوق ملاحظه می‌کنید. اگر تنها متد OnException تحریف شود، try/catch خودکار اضافه شده به کدها، هر نوع استثنایی را مدیریت خواهد کرد. اما اگر متد GetExceptionType نیز در این بین مقدار دهی گردد، بر اساس نوع استثنای تعریف شده، کار فیلتر استثناها انجام می‌پذیرد و از مابقی صرفنظر خواهد شد.
نحوه استفاده از این Aspect نیز همانند مثال قسمت قبل است و جزئیات آن تفاوتی نمی‌کند.


2) LocationInterceptionAspect

این Aspect برخلاف سایر Aspectهایی که تاکنون بررسی کردیم، تنها در سطح خواص و فیلدهای یک کلاس عمل می‌کند. کار Interception در اینجا به معنای تحت کنترل قرار دادن اعمال set (پیش از فراخوانی set) و get (پیش از بازگشت مقدار) این خواص عمومی و حتی خصوصی تعریف شده است. کلمه Location در این Aspect به معنای متادیتای زمینه کاری است؛ مانند Name و FullName خواصی که مشغول به کار با آن‌ها هستیم.
using System;
using PostSharp.Aspects;

namespace AOP03
{
    public class ObjectInitializationAspect : LocationInterceptionAspect
    {
        public override void OnGetValue(LocationInterceptionArgs args)
        {
            if (args.GetCurrentValue() == null)
            {
                Console.WriteLine("Property {0} is null.", args.LocationFullName);
            }
        }
    }
}
یک نمونه از کاربرد آن‌را در مثال فوق مشاهده می‌کنید. در اینجا با تحریف متد OnGetValue، پیش از بازگشت مقداری از یک خاصیت، بررسی می‌شود که آیا مقدار آن null است یا خیر.
برای استفاده از آن نیز کافی است تا ویژگی ObjectInitializationAspect به خاصیتی دلخواه اضافه شود.
در اینجا 4 متد args.GetCurrentValue برای دریافت مقدار جاری خاصیت، args.SetNewValue جهت تنظیم مقداری جدید، args.ProceedGetValue و args.ProceedSetValue سبب اجرای حالت‌های get و set می‌شوند (چیزی شبیه به عملکرد اینترفیس IInterceptor که در قسمت‌های قبلی بررسی کردیم).


3) EventInterceptionAspect

EventInterceptionAspect همانطور که از نام آن نیز پیدا است، در سطح رخدادهای یک کلاس عمل می‌کند. سه متدی که این کلاس پایه برای تحت نظر قرار دادن اعمال رویدادگردان‌های یک کلاس در اختیار ما قرار می‌دهند شامل OnAddHandler، OnRemoveHandler و OnInvokeHandler هستند.
using PostSharp.Aspects;
using System;

namespace AOP03
{
    public class LogEventAspect : EventInterceptionAspect
    {
        public override void OnAddHandler(EventInterceptionArgs args)
        {
            Console.WriteLine("Event {0} added", args.Event.Name);
            args.ProceedAddHandler();
        }

        public override void OnRemoveHandler(EventInterceptionArgs args)
        {
            Console.WriteLine("Event {0} removed", args.Event.Name);
            args.ProceedRemoveHandler();
        }

        public override void OnInvokeHandler(EventInterceptionArgs args)
        {
            Console.WriteLine("Event {0} invoked", args.Event.Name);
            args.ProceedInvokeHandler();
        }
    }
}
مثالی را از نحوه تعریف یک EventInterceptionAspect مشاهده می‌کنید. در تمام حالاتی که متدهای کلاس پایه تحریف شده‌اند نیاز است از متدهای Proceed متناظر نیز استفاده شود تا برای مثال اضافه شدن، حذف و یا اجرای یک رویداد رخ دهند.


مدیریت اعمال Aspects در زمان کامپایل

یکی از متدهایی که در کلیه Aspects توکار فوق قابل تحریف است، CompileTimeValidate نام دارد.
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        public override bool CompileTimeValidate(System.Reflection.MethodBase method)
        {
            return !method.IsStatic;
        }
برای نمونه اگر آن‌را به OnMethodBoundaryAspect پیاده سازی شده در قسمت قبل، با تعاریف فوق اعمال کنیم، این Aspect سفارشی دیگر به متدهای استاتیک، اعمال نخواهد شد. به این ترتیب می‌توان بر روی نحوه کامپایل ثانویه کدهایی که قرار است به اسمبلی برنامه اضافه شوند، تاثیر گذار بود.


چند نکته تکمیلی در مورد توزیع برنامه‌های مبتنی بر PostSharp

الف) اگر نیاز است به اسمبلی‌های خود امضای دیجیتال اضافه کنید، در حالت استفاده از PostSharp به علت بازنویسی کدهای IL اسمبلی تولیدی، نیاز است حالت delay signing انتخاب شود. به این معنا که ابتدا اسمبلی به صورت متداول کامپایل می‌شود. سپس PostSharp کار خود را انجام داده و در نهایت با استفاده از ابزارهای اعمال امضای دیجیتال باید کار افزودن آن‌ها در مرحله آخر انجام شود.
ب) در حال حاضر تنها برنامه Dotfuscator است که با PostSharp برای obfuscation سازگاری دارد.
مطالب
Blazor 5x - قسمت نهم - مبانی Blazor - بخش 6 - ساده سازی تعاریف ویژگی‌های المان‌ها و انتقال پارامترها به چندین زیر سطح
بررسی ویژگی Attribute Splatting

برای تعریف المان‌های فرم‌ها نیاز است ویژگی‌های قابل توجهی را مانند placeholder ،required ،maxlength و غیره، تعریف کرد که در صورت زیاد بودن تعداد المان‌های یک فرم، مدیریت تعریف این ویژگی‌ها مشکل می‌شود. به همین جهت قابلیت ویژه‌ای مخصوص اینکار به نام Attribute Splatting در Blazor درنظر گرفته شده‌است. برای توضیح آن، ابتدا کامپوننت والد Pages\LearnBlazor\AttributeSplatting.razor و کامپوننت فرزند Pages\LearnBlazor\LearnBlazor‍Components\AttributeSplattingChild.razor را ایجاد می‌کنیم.
در کامپوننت فرزند یا همان AttributeSplattingChild، یک المان را به همراه تعدادی ویژگی تعریف شده مشاهده می‌کنید:
<div>
    <h4 class="text-primary pt-3">Attribute Splatting Child Component</h4>

    <input id="roomName"
        placeholder="@Placeholder"
        required="@Required"
        maxlength="@MaxLength"
        class="form-control" />
</div>

@code {
    [Parameter]
    public string Placeholder { get; set; } = "Initial Text";

    [Parameter]
    public string Required { get; set; } = "required";

    [Parameter]
    public string MaxLength { get; set; } = "10";
}
و کامپوننت والد و یا همان AttributeSplatting.razor، از آن به صورت زیر استفاده می‌کند:
@page "/AttributeSplatting"

<h1>Attribute Splatting</h1>

<AttributeSplattingChild
    Placeholder="Enter the Room Name From Parent"
    MaxLength="5">
</AttributeSplattingChild>
روش ارسال پارامترها را به کامپوننت‌های فرزند، در قسمت پنجم این سری بررسی کردیم. تنها نکته‌ی جدید آن، تعریف مقادیر پیش‌فرض پارامترها در کامپوننت فرزند است. برای مثال در حین تعریف المان AttributeSplattingChild در کامپوننت والد، پارامتر Required مقدار دهی نشده‌است. در این حالت، مقدار پیش‌فرض درج شده‌ی در کامپوننت فرزند، مورد استفاده قرار می‌گیرد؛ وگرنه مقادیر تنظیم شده‌ی توسط کامپوننت والد، حق تقدم بالاتری نسبت به مقادیر پیش‌فرض خواهند داشت.

مشکل! کامپوننت AttributeSplattingChild که فقط به همراه یک المان است، تا این لحظه نیاز به تعریف سه پارامتر جدید را جهت تامین ویژگی‌های آن المان داشته‌است. اگر تعداد این المان‌ها افزایش پیدا کرد، آیا راه بهتری برای مدیریت تعداد بالای ویژگی‌های مورد نیاز وجود دارد؟
پاسخ: در یک چنین حالتی می‌توان ویژگی‌های هر المان را توسط پارامتری از نوع Dictionary مدیریت کرد؛ بجای تعریف تک تک آن‌ها به صورت خواصی مجزا. به این قابلیت، Attribute Splatting می‌گویند.
در این حالت تمام کدهای AttributeSplattingChild.razor به صورت زیر خلاصه می‌شوند:
<div>
    <h4 class="text-primary pt-3">Attribute Splatting Child Component</h4>

    <input id="roomName" @attributes="InputAttributes" class="form-control" />
</div>

@code {
    [Parameter]
    public Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>
    {
        { "required" , "required"},
        { "placeholder", "Initial Text"},
        { "maxlength", 10}
    };
}
در اینجا با استفاده از دایرکتیو جدید attributes@ می‌توان لیستی از key/value‌های ویژگی‌های یک المان را به صورت یک دیکشنری دریافت کرد و دیگر نیازی نیست تا تک تک آن‌ها را تبدیل به یک پارامتر و خاصیت عمومی مجزا کرد. در این حالت مقادیری که در سمت کامپوننت فرزند تعریف می‌شوند، به عنوان مقادیر اولیه‌ی قابل بازنویسی توسط کامپوننت والد، درنظر گرفته خواهند شد (مانند مثال پارامتر Required که عنوان شد).
و همچنین در ادامه کامپوننت والد یا AttributeSplatting.razor نیز به صورت زیر تغییر می‌کند:
@page "/AttributeSplatting"

<h1>Attribute Splatting</h1>

<AttributeSplattingChild InputAttributes="InputAttributesFromParent"></AttributeSplattingChild>

@code{
    Dictionary<string, object> InputAttributesFromParent = new Dictionary<string, object>
    {
        { "required" , "required"},
        { "placeholder", "Enter the Room Name From Parent"},
        { "maxlength", 5}
    };
}
با توجه به اینکه پارامتر InputAttributes، یک شیء دیکشنری را دریافت می‌کند، فیلد آن‌را در قسمت کدهای کامپوننت جاری تعریف کرده و مورد استفاده قرار می‌دهیم. در این حالت هر مقداری که در سمت والد تنظیم شود، حق تقدم بیشتری نسبت به مقدار پیش‌فرض ویژگی‌های تنظیم شده‌ی در کامپوننت فرزند خواهد داشت.



ساده سازی روش تعریف key/value‌های شیء دیکشنری Attribute Splatting

تا اینجا موفق شدیم تعداد قابل ملاحظه‌ای از پارامترهای عمومی یک کامپوننت را تنها توسط یک شیء Dictionary مدیریت کنیم. همچنین همانطور که ملاحظه می‌کنید، هم Dictionary سمت کامپوننت فرزند و هم سمت کامپوننت والد، نیاز به مقدار دهی اولیه‌ای را دارند. این مقدار دهی اولیه را می‌توان به نحو دیگری نیز در حین استفاده‌ی از قابلیت Attribute Splatting، انجام داد:
<div>
    <h4 class="text-primary pt-3">Attribute Splatting Child Component</h4>

    <input id="roomName" @attributes="InputAttributes" placeholder="Initial Text" class="form-control" />
</div>

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>();
}
در اینجا مقادیر اولیه‌ی دیکشنری تعریف شده را حذف کرده‌ایم و بجای آن‌ها، این مقادیر اولیه را به صورت ویژگی‌های متداول یک المان HTML ای تعریف کرده‌ایم؛ مانند placeholder تعریف شده. برای اینکه یک چنین ویژگی‌هایی به عنوان key/valueهای دیکشنری تعریف شده قابل استفاده باشند، تنها کافی است خاصیت CaptureUnmatchedValues ویژگی پارامتر را به true تنظیم کرد. در اینجا Unmatched Values، همان ویژگی‌هایی هستند که در حین تعریف یک المان اضافه شده‌اند (مانند placeholder در مثال فوق) اما در حین مقدار دهی اولیه‌ی دیکشنری، تعریف نشده‌اند و یا تمام پارامترهای عمومی دیگری که در اینجا ذکر و تعریف نشده‌اند. بنابراین تنها یک CaptureUnmatchedValues = true را در سطح یک کامپوننت می‌توان تعریف کرد.

پس از این تغییر، کامپوننت والد هم به صورت زیر خلاصه می‌شود و دیگر نیازی به تعریف و مقدار دهی InputAttributes و یا تعریف مجزای یک دیکشنری را ندارد. در اینجا هر ویژگی که به المان نسبت داده شود، به عنوان Unmatched Values تفسیر شده و مورد استفاده قرار می‌گیرد.
@page "/AttributeSplatting"

<h1>Attribute Splatting</h1>

<AttributeSplattingChild placeholder="Placeholder default"></AttributeSplattingChild>


اگر به تصویر فوق دقت کنید، هرچند در کامپوننت والد مقدار placeholder، به متن دیگری تنظیم شده، اما متن تنظیم شده‌ی در کامپوننت فرزند، تقدم بیشتری پیدا کرده و نمایش داده شده‌است. علت اینجا است که محل قرارگیری آن در مثال فوق، در سمت راست دایرکتیو attributes@ است. اگر آن‌را در سمت چپ attributes@ قرار دهیم، حق تقدم attributes@ بیشتر شده و مقدار تنظیم شده‌ی در سمت کامپوننت والد، بجای placeholder اولیه‌ی تعریف شده‌ی در اینجا مورد استفاده قرار می‌گیرد:
<input id="roomName" placeholder="Initial Text" @attributes="InputAttributes" class="form-control" />


روش انتقال پارامترها به چندین زیر سطح

در قسمت قبل، ParentComponent.razor و ChildComponent.razor را تعریف و تکمیل کردیم. هدف از آ‌ن‌ها، بررسی ویژگی Render Fragment‌ها بود. در ادامه‌ی آن، یک زیر کامپوننت دیگر را نیز به نام Pages\LearnBlazor\LearnBlazor‍Components\GrandChildComponent.razor اضافه می‌کنیم. هدف این است که کامپوننت Parent، کامپوننت Child را فراخوانی کند و کامپوننت Child، کامپوننت GrandChild را تا یک سلسله مراتب از کامپوننت‌ها را تشکیل دهیم.
محتوای GrandChildComponent را هم بسیار ساده نگه می‌داریم، تا پارامتری رشته‌ای را دریافت کرده و نمایش دهد:
<div class="row">
    <h4 class="text-primary pl-4 pt-2 col-12">Grand Child Component</h4>
    <br />
    <p> There is a message - @MessageForGrandChild </p>
</div>

@code {
    [Parameter]
    public string MessageForGrandChild { get; set; }
}
در ChildComponent، کامپوننت GrandChild را به نحو زیر فراخوانی کرده و پارامتری را به آن ارسال می‌کنیم:
<div class="mt-2">
    <GrandChildComponent MessageForGrandChild="@MessageForGrandChild"></GrandChildComponent>
</div>


@code {
    [Parameter]
    public string MessageForGrandChild { get; set; }

   // ...
}
و اکنون در بالاترین سطح این سلسه مراتبی که مشاهده می‌کنید یعنی کامپوننت Parent، این پیام MessageForGrandChild را مقدار دهی خواهیم کرد تا توسط GrandChildComponent نمایش داده شود:
<ChildComponent
    MessageForGrandChild="This is a message from Grand Parent"
    Title="This is the second child component">
    <p><b>@MessageText</b></p>
</ChildComponent>
همانطور که مشاهده می‌کنید، انتقال متداول یک پارامتر، از بالاترین سطح سلسه مراتب کامپوننت‌ها به پایین‌ترین سطح موجود، نیاز به مقدار قابل ملاحظه‌ای کد تکراری را دارد. همچنین برای نمونه پارامتر انتقالی تعریف شده‌ی در کامپوننت Child، اصلا در آن کامپوننت استفاده نمی‌شود و هدف از آن، متصل کردن یک سطح بالاتر، به یک سطح پایین‌تر است.
بنابراین اکنون این سؤال مطرح می‌شود که آیا می‌توان پارامتری را در همان کامپوننت Parent تعریف کرد که توسط کامپوننت GrandChild قابل شناسایی و استفاده باشد، بدون اینکه کامپوننت Child را در این بین تغییر دهیم؟
پاسخ: بله. برای اینکار ویژگی‌های CascadingValue و CascadingParameter در Blazor پیش بینی شده‌اند.
در ابتدا، پارامتر MessageForGrandChild کامپوننت Child حذف کرده و سپس آن‌را توسط کامپوننت توکار CascadingValue محصور می‌کنیم. در اینجا نیاز است مقدار انتقالی را نیز مشخص کنیم:
<CascadingValue Value="@MessageForGrandChild">
    <ChildComponent        
        Title="This is the second child component">
        <p><b>@MessageText</b></p>
    </ChildComponent>
</CascadingValue>

@code {
    string MessageForGrandChild = "This is a message from Grand Parent";
پس از این تعریف، به کامپوننت Child مراجعه کرده و پارامتر MessageForGrandChild آن‌را حذف می‌کنیم؛ چون دیگر نیازی به آن نیست. همچنین در این کامپوننت، فراخوانی GrandChildComponent نیز به صورت زیر خلاصه شده و دیگر نیازی به ذکر پارامتر انتقالی MessageForGrandChild حذف شده را ندارد:
<GrandChildComponent></GrandChildComponent>
در آخر به کامپوننت GrandChild مراجعه کرده و اینبار پارامتر مورد استفاده‌ی در آن‌را با ویژگی جدید CascadingParameter مزین می‌کنیم:
[CascadingParameter]
public string MessageForGrandChild { get; set; }


چند نکته:
- در اینجا نوع CascadingParameter تعریف شده، باید با نوع Value کامپوننت CascadingValue، در بالاترین سطح سلسله مراتب کامپوننت‌ها، یکی باشد.
- نام CascadingParameter تعریف شده مهم نیست. فقط نوع آن مهم است.
- تمام کامپوننت‌های موجود و پوشش داده شده‌ی در سلسله مراتب جاری، قابلیت تعریف CascadingParameter ای مانند مثال فوق را دارند و این تعریف، محدود به پایین‌ترین سطح موجود نیست. برای مثال در اینجا در کامپوننت Child هم در صورت نیاز می‌توان همین CascadingParameter را تعریف و استفاده کرد.


روش تعریف پارامترهای آبشاری نام‌دار

تا اینجا روش انتقال یک پارامتر را از بالاترین سطح، به پایین‌ترین سطح سلسله مراتب کامپوننت‌های تعریف شده، بررسی کردیم. اکنون شاید این سؤال مطرح شود که اگر خواستیم بیش از یک پارامتر را بین اجزای این سلسله، به اشتراک بگذاریم چه باید کرد؟
در این حالت می‌توان پارامتر جدید را توسط یک کامپوننت CascadingValue تو در تو، به صورت زیر معرفی کرد؛ که اینبار نامدار نیز هست:
<CascadingValue Value="@MessageForGrandChild" Name="MessageFromGrandParent">
    <CascadingValue Value="@Number" Name="GrandParentsNumber">
        <ChildComponent
            Title="This is the second child component">
            <p><b>@MessageText</b></p>
        </ChildComponent>
    </CascadingValue>
</CascadingValue>

@code {
    string MessageForGrandChild = "This is a message from Grand Parent";
    int Number = 7;
برای نمونه در این مثال، عدد 7 نیز قرار است در اختیار سلسله مراتب شروع شده‌ی از کامپوننت جاری، قرار گیرد. به همین جهت یک CascadingValue تو در توی مختص آن نیز تعریف شده‌است که اینبار نامش GrandParentsNumber است.

پس از این تغییر، GrandChildComponent، این پارامترهای نامدار را از طریق ذکر صریح خاصیت Name ویژگی CascadingParameter، دریافت می‌کند:
<div class="row">
    <h4 class="text-primary pl-4 pt-2 col-12">Grand Child Component</h4>
    <br />
    There is a message: @Message
    <br />
    GrandParentsNumber: @Number
</div>

@code {
    [CascadingParameter(Name = "MessageFromGrandParent")]
    public string Message { get; set; }

    [CascadingParameter(Name = "GrandParentsNumber")]
    public int Number { get; set; }
}


یک نکته: چون نوع پارامترهای ارسالی یکی نیست، الزامی به ذکر نام آن‌ها نبود. در این حالت بر اساس نوع پارامترهای آبشاری، عملیات اتصال مقادیر صورت می‌گیرد. اما اگر نوع هر دو را برای مثال رشته‌ای تعریف می‌کردیم، مقدار Number، بر روی مقدار MessageForGrandChild بازنویسی می‌شد. یعنی در UI، هر دو پارامتر هم نوع، یک مقدار را نمایش می‌دادند که در حقیقت مقدار پایین‌ترین CascadingValue تعریف شده‌است. بنابراین ذکر نام پارامترهای آبشاری، روشی‌است جهت تمایز قائل شدن بین پارامترهای هم نوع.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-09.zip
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
ممنون
در کنترلر Home اکشن Error : 
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error(int? id)
        {
            string ErrorText = "";
            ErrorText = (id.Value == 401 || id.Value == 403) ? "Access Denied"
            : (id.Value == 404 ? "Not Found" : "Error Occured");

            return View(new ErrorViewModel
            {
                RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                ErrorId = id,
                ErrorText = ErrorText
            });
        }
در Error.cshtml : 
<h1 class="text-danger">@Model.ErrorId : @Model.ErrorText</h1>
کلاس مدل :
public class ErrorViewModel
    {
        public string RequestId { get; set; }

        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

        public int? ErrorId { get; set; }

        public string ErrorText { get; set; }
    }

نظرات مطالب
ASP.NET MVC #12
- یک سری پیشنیاز طراحی رو باید بدونید مانند «کار با کلیدهای اصلی و خارجی در EF Code first ». در اینجا با نحوه تعریف صحیح کلید خارجی به صورت یک خاصیت عددی آشنا خواهید شد. این مورد همان چیزی است که باید از یک drop down list دریافت شود. فقط primary key یک رکورد مهم است نه تمام خواص و فیلدهای مرتبط با آن.
- مرحله بعد ارسال اطلاعات به View هست به این ترتیب:
public ActionResult Index()
{
    var query = db.Users.Select(c => new SelectListItem
                                              {
                                                  Value = c.UserId, 
                                                  Text = c.UserName,
                                                  Selected = c.UserId.Equals(3)
                                              });
    var model = new MyFormViewModel
                    {
                        List = query.ToList()
                    };
    return View(model);
}
در اینجا بر اساس اطلاعات بانک اطلاعاتی SelectListItem‌ها تشکیل شده و به ViewModel فرم جاری ارسال می‌شود (به علاوه باید با ViewModel کار کنید نه مدل جداول بانک اطلاعاتی).
public class MyFormViewModel
{
    public int UserId { get; set; }
    public IList<SelectListItem> List { get; set; }
}
در مورد نگاشت‌ها هم مباحث auto-mapper در سایت هست.
همچنین خاصیت Selected هم در اینجا بر اساس یک شرط تعیین شده.
- قسمت View برنامه هم به این شکل خواهد بود:
@model MyFormViewModel

@Html.DropDownListFor(m => m.UserId, Model.List, "--Select One--")

مطالب
چگونگی رسیدگی به Null property در AutoMapper

AutoMapper کتابخانه‌ای برای نگاشت اطلاعات یک شیء به شی‌ءایی دیگر به صورت خودکار می‌باشد.

در این مقاله چگونگی رسیدگی به Null property را در AutoMapper   بررسی خواهیم کرد. فرض کنید شیء منبع دارای یک خاصیت Null  است و می‌خواهید به وسیله Automaper شیء منبع را به مقصد نگاشت نمایید. اما می‌خواهید در صورت Null بودن شیء مبدا، یک مقدار پیش فرض برای شیء مقصد در نظر گرفته شود .

برای نمونه کلاسuser   را که در آن از کلاس Address یک خاصیت تعریف شده، در نظر بگیرید. اگر مقدار آدرس در شیء منبع خالی بود شاید شما بخواهید مقدار آن را به صورت empty string و یا با یک مقدار پیش فرض در مقصد مقدار دهی کنید.

همانند مثال زیر : 

public class UserSource
{
  public Address Address{get;set;}
}
 
public class UserDestination
{
  public string Address{get;set;}
}
ابتدا نگاشت‌ها را تعریف می‌کنیم:
AutoMapper.Mapper.CreateMap<UserSource, UserDestination>()
          .ForMember(dest => dest.Address
          , opt => opt.NullSubstitute("Address not found")
          );
کد بالا نشان دهنده تبدیل Address به Address است ولی دارای متد اختیاری NullSubstitute می‌باشد و بیانگر این است که اگر آدرس شیء منبع Null بود، مقدار پیش فرضی را برای شیء مقصد در نظر بگیرد. در انتها  می‌توان نگاشت را در برنامه متناسب با نیاز خود انجام داد:
var model = AutoMapper.Mapper.Map<UserSource, UserDestination>(user);
var models = AutoMapper.Mapper.Map<IEnumerable<UserSource>, IEnumerable<UserDestination>>(users);