مطالب
کش کردن کوئری ها با استفاده از کتابخانه MediatR
در سری مقالات پیاده سازی CQRS توسط کتابخانه MediatR، مطالبی جهت آشنایی و نحوه استفاده از این کتابخانه بیان شده‌است که در بخش چهارم، به رفتار‌ها (Behavior‌)‌ها جهت اعمالی از جمله اعتبارسنجی، ثبت وقایع و ... پرداخته شده‌است. در این مقاله قصد داریم با استفاده از رفتارها، اقدام به پیاده سازی کش، برای خروجی حاصل از کوئری‌ها نماییم.
با استفاده از رفتارها، تمامی کدهای لازم برای خواندن و ثبت داده‌ها از کش، در Behavior‌  مربوطه پیاده سازی خواهد شد و نیازی نیست در تمامی هندلرهای مربوط به درخواست‌های از نوع Query، کدهای مربوط به Caching تکرار شود.

ابتدا یک interface همانند زیر ایجاد خواهیم کرد و هدف از آن، محدود کردن Caching برای درخواست‌هایی خواهد بود که از این اینترفیس ارث بری کرده‌اند و نه تمامی درخواست‌ها:
public interface ICachableQuery
  {
        public string Key { get;}
  }
با توجه به اینکه برای کش کردن داده‌ها، نیاز به یک کلید منحصر بفرد داریم، اینترفیس فوق، داری یک خصوصیت رشته‌ای Key می‌باشد و تمامی درخواست‌هایی که نیاز به کش کردن دارند، باید Key را با یک مقدار منحصر بفرد مقدار دهی کنند. در کدهای پایین، یک نمونه از یک درخواست را مشاهده میکنید که مقدار Key را برابر با نام کلاس، به همراه شناسه رکورد مورد نظر مقدار دهی کرده‌است:
public class GetBookmarkQuery : IRequest<BookmarkDto>, ICachableQuer
    {
        public int Id { get; init; }
        public string Key => $"{nameof(GetBookmarkQuery)}-{Id}";

        public GetBookmarkQuery(int id)
        {
            Id = id;
        }
    }

و در ادامه، یک Behavior‌  به اسم Caching Behavior‌ ایجاد و کدهای لازم برای خواندن و ثبت داده‌ها از کش را پیاده سازی کرده‌ایم:
public class CachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : ICachableQuer
    {
        private readonly IDistributedCache _cache;


        public CachingBehavior(IDistributedCache distributedCache)
        {
            _cache = distributedCache;
        }


        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
        {
            TResponse response;
            var cachedResponse = await _cache.GetAsync(request.Key, cancellationToken);
            if (cachedResponse != null)
                response = JsonConvert.DeserializeObject<TResponse>(Encoding.Default.GetString(cachedResponse));
            else
            {
                response = await next();
                var serialized = Encoding.Default.GetBytes(JsonConvert.SerializeObject(response));
                await _cache.SetAsync(request.Key, serialized, cancellationToken);
            }
            return response;
        }
    }
همانطور که قابل مشاهده‌است، ابتدا کش با مقدار کلید درخواست جاری بررسی می‌شود و اگر مقداری در کش موجود باشد همان را بر میگرداند، در غیر این صورت هندلر مربوطه اجرا خواهد شد و سپس مقدار دریافت شده از خروجی هندلر را کش خواهد کرد.
و در پایان نیاز هست  این Behavior را به صورت زیر  ثبت نماییم :
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(CachingBehavior <,>));

با توجه با اینکه ممکن است بعد از کش کردن خروجی یک کوئری، تغییراتی بر روی رکورد مورد نظر که کش شده است انجام شود، بهتر است  تاریخ انقضا برای کش‌ها ثبت شود و یا بعد از به روزرسانی یک رکورد، کش مربوطه را به روزرسانی کنید.
نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش تهیه‌ی ویژگی‌های سفارشی اعتبارسنجی، در برنامه‌های Blazor

اگر ویژگی‌های پیش‌فرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، می‌توان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations;

namespace CustomValidators
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
    public class EmailDomainValidator : ValidationAttribute
    {
        public string AllowedDomain { get; set; }

        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            string[] strings = value.ToString().Split('@');
            if (strings[1].ToUpper() == AllowedDomain.ToUpper())
            {
                return null;
            }

            return new ValidationResult($"Domain must be {AllowedDomain}",
            new[] { validationContext.MemberName });
        }
    }
}
توضیحات:
- کار با ارث بری از کلاس پایه‌ی ValidationAttribute شروع می‌شود و باید متد IsValid آن‌را بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه می‌دهد.
- در اینجا متد ()ValidationContext.GetService نال را بر می‌گرداند؛ یعنی فعلا از تزریق وابستگی‌ها در آن پشتیبانی نمی‌شود.

و در آخر روش استفاده‌ی از آن، همانند سایر ویژگی‌های اعتبارسنجی است:
public class Employee
{
    [EmailDomainValidator(AllowedDomain = "site.com")]
    public string Email { get; set; }
}
نظرات مطالب
Blazor 5x - قسمت 33 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 3- بهبود تجربه‌ی کاربری عدم دسترسی‌ها
یک نکته‌ی تکمیلی: کار با Policy‌ها در برنامه‌های Blazor WASM

در این مطلب، روشی را برای برقراری دسترسی نقش Admin، به تمام قسمت‌های محافظت شده‌ی برنامه، با معرفی نقش آن به یک ویژگی Authorize سفارشی شده، مشاهده کردید. هرچند این روش کار می‌کند، اما روش جدیدتر برقراری یک چنین دسترسی‌های ترکیبی در برنامه‌های ASP.NET Core و سایر فناوری‌های مشتق شده‌ی از آن، کار با Policyها است که برای نمونه در مثال فوق، به صورت زیر قابل پیاده سازی است:

الف) تعریف Policyهای مشترک بین برنامه‌های Web API و WASM
Policyهای تعریف شده، باید قابلیت اعمال به اکشن متدهای کنترلرها و همچنین کامپوننت‌های WASM را داشته باشند. به همین جهت آن‌ها را در پروژه‌ی اشتراکی BlazorServer.Common که در هر دو پروژه استفاده می‌شود، قرار می‌دهیم:
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; // dotnet add package Microsoft.AspNetCore.Authorization

namespace BlazorServer.Common
{
    public static class PolicyTypes
    {
        public const string RequireAdmin = nameof(RequireAdmin);
        public const string RequireCustomer = nameof(RequireCustomer);
        public const string RequireEmployee = nameof(RequireEmployee);
        public const string RequireEmployeeOrCustomer = nameof(RequireEmployeeOrCustomer);

        public static AuthorizationOptions AddAppPolicies(this AuthorizationOptions options)
        {
            options.AddPolicy(RequireAdmin, policy => policy.RequireRole(ConstantRoles.Admin));
            options.AddPolicy(RequireCustomer, policy =>
                    policy.RequireAssertion(context =>
                        context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                            && (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Customer))
                    ));
            options.AddPolicy(RequireEmployee, policy =>
                    policy.RequireAssertion(context =>
                        context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                            && (claim.Value == ConstantRoles.Admin || claim.Value == ConstantRoles.Employee))
                    ));

            options.AddPolicy(RequireEmployeeOrCustomer, policy =>
                                policy.RequireAssertion(context =>
                                    context.User.HasClaim(claim => claim.Type == ClaimTypes.Role
                                        && (claim.Value == ConstantRoles.Admin ||
                                            claim.Value == ConstantRoles.Employee ||
                                            claim.Value == ConstantRoles.Customer))
                                ));
            return options;
        }
    }
}
در اینجا یکسری Policy جدید را مشاهده می‌کنید که در آن‌ها همواره نقش Admin حضور دارد و همچین روش or آن‌ها را توسط policy.RequireAssertion مشاهده می‌کنید. این تعاریف، نیاز به نصب بسته‌ی Microsoft.AspNetCore.Authorization را نیز دارند. با کمک Policyها می‌توان ترکیب‌های پیچیده‌ای از دسترسی‌های موردنیاز را ساخت؛ بدون اینکه نیاز باشد مدام AuthorizeAttribute سفارشی را طراحی کرد.

ب) افزودن Policyهای تعریف شده به پروژه‌های Web API و WASM
پس از تعریف Policyهای مورد نیاز، اکنون نوبت به افزودن آن‌ها به برنامه‌های Web API:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            services.AddAuthorization(options => options.AddAppPolicies());

            // ...
و همچنین WASM است:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            // ...

            builder.Services.AddAuthorizationCore(options => options.AddAppPolicies());

            // ...
        }
    }
}
به این ترتیب Policyهای یک‌دستی را بین برنامه‌های کلاینت و سرور، به اشتراک گذاشته‌ایم.

ج) استفاده از Policyهای تعریف شده در برنامه‌ی WASM
اکنون که برنامه قابلیت کار با Policyها را پیدا کرده، می‌توان فیلتر Roles سفارشی را حذف و با فیلتر Authorize پالیسی دار جایگزین کرد:
@page "/hotel-room-details/{Id:int}"

// ...

@*
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
*@

@attribute [Authorize(Policy = PolicyTypes.RequireEmployeeOrCustomer)]

حتی می‌توان از پالیسی‌ها در حین تعریف AuthorizeViewها نیز استفاده کرد:
<AuthorizeView  Policy="@PolicyTypes.RequireEmployeeOrCustomer">
    <p>You can only see this if you're an admin or an employee or a customer.</p>
</AuthorizeView>
نظرات مطالب
بررسی تغییرات Blazor 8x - قسمت دوازدهم - قالب جدید پیاده سازی اعتبارسنجی و احراز هویت - بخش دوم
چند مطلب و پروژه‌ی تکمیلی، اگر علاقمند به پیاده سازی سفارشی اعتبارسنجی و احراز هویت Blazor 8x در حالت SSR هستید:
- Custom cookie authentication in Blazor SSR  (اگر موفق به خواندن کامل آن نشدید، از این آدرس استفاده کنید)
- «SparkPoc» یک پیاده سازی سفارشی مبتنی بر کوکی مخصوص SSR
که اصل و اساس آن‌ها این مطلب است: «اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» و در Blazor SSR هم قابل استفاده‌است.
مطالب
مقدمه ای بر CQRS و Event Sourcing
به صورت عام، functionality اکثر پروژه‌های نرم افزاری تجاری خلاصه میشود به مخفف معروفی به نام CRUD، که object‌ها را میسازیم، آن‌ها را میخوانیم و تغییر میدهیم.
اپلیکیشن‌های طراحی شده بدین صورت، قابلیت خوانایی بالایی خواهند داشت و دیاگرام طراحی آنها چیزی شبیه به تصویر زیر میباشد

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

زمانیکه نیاز‌های پروژه روز به روز افزورده و پیچیده‌تر میشود، مدل CRUD بصورت پیوسته از ارزشش کاسته میشود و از آن سادگی اولیه‌ی در درک و خوانایی آن دور خواهد شد.

ذات CQRS بر آن است که شما مدل‌های مختلفی را برای خواندن و نوشتن دیتا داشته باشید. الگوی آن چیزی شبیه به تصویر زیر است

چیزی که در این روش مشهود است این میباشد که برنامه نویسان باید قسمت‌های Command و Query را به صورت جداگانه طراحی نمایند.
CQRS این قابلیت را به شما میدهد که interface و Datastore و حتی بطور کامل Technology مجزایی در قسمت‌های CQ داشته باشید.

Event Sourcing
قسمت دوم و متمایز، معماری Event Sourcing یا ES میباشد که بصورت کوتاه، ES یک روش متفاوت برای Data storage میباشد.
اکثرا ما از Datastore‌هایی که مدلی از دیتا را انعکاس می‌دهند استفاده میکنیم. به مثال ساده‌ی زیر توجه کنید

ES از برنامه نویسان میخواهد که مدل سنتی CRUD را فراموش کرده و بجای آن تغییراتی را که روی دیتا صورت گرفته، نیز درج نمایند. اینکار به وسیله‌ی یک دیتابیس Append-only انجام میشود که به نام Event Store شناخته میشود.

در این معماری ما همه‌ی تغییرات روی دیتا را به صورت Serialize Event ذخیره میکنیم که میتواند دوباره در هر زمانی اجرا شده و current state هر objectی را در اختیار بگذارد.

این روش به ما کمک بزرگی میکند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن میتوان به غیر از فوایدی که دارد، به عنوان یک Logger نیز استفاده نمود. به دلیل اینکه جزء به جزء تغییرات بر روی state سیستم، در آن ثبت شده است. از آنجاییکه دیتا بصورت serialize ذخیره میشود، بارگزاری آن نیز با سرعت بالایی انجام خواهد شد.

پس بصورت خلاصه در معماری Command Query Responsibility Segregation ابتدا باید به این موضوع توجه داشت که قسمت‌های Read و Write نرم افزار به صورت مجزایی طراحی میشوند و Event Sourcing شامل تغییراتی که روی data انجام شده است، میباشد و به‌صورت Serialize شده ذخیره میشود. ما تنها به یک دیتابیس و یک جدول برای نمایش event store نیازمندیم (بستگی به نیازتان میتوان تعداد آن را نیز بیشتر نمود و همچنین حتما لزومی ندارد که از دیتابیس‌های رابطه‌ای استفاده شود؛ بصورت مثال پیاده سازی این قسمت را میتوان با استفاده از Redis که دیتابیسی غیر رابطه‌ای و باسرعت میباشد استفاده نمود).
برای شروع کار (نه پیاده سازی کامل) باید با قسمت‌های مختلف طراحی در این معماری آشنا شویم:
Domain Object
نکته: SimpleCqrs فریم ورکی برای پیاده سازی معماری CQRS , ES میباشد که برای ساده‌تر شدن کار، از آن استفاده شده است (شما حتی میتوانید پیاده سازی خود را داشته باشید)
مدل Movie از کلاسی به نام AggregateRoot ارث بری کرده‌است که توسط SimpleCQRS پیاده سازی شده‌است و یک guid key در آن تعبیه شده است (Aggregate root از مباحث Domain Driven برگرفته شده است و آشنایی با آن کمک شایانی به درک عمیق‌تر روی این مباحث مینماید).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie() { }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        //پیاده سازی خواهد شد
    }
}
توجه: SimpleCQRS فقط پیاده سازی guid برای کلید مربوط به هر مدل را پیاده سازی نموده است؛ بنابراین کلید مدل نمیتواند integer باشد.

Commands
command دستوراتی است که توسط end user فراخوانی میشود که باعث تغییرات خواهد شد. وقتی اپلیکیشن یک command را دریافت مینماید، command handler به پردازش آن برای فهمیدن خواسته کاربر میپردازد و پس از آن event مربوطه را برای اجرای آن وظیفه‌ی خاص صدا میزند.
همه‌ی command‌ها تغییراتی بر روی state جاری خواهند داشت. در نتیجه دیتا‌های ذخیره شده درون دیتابیس تغییرات خواهند کرد. هر commandی که تغییری بر روی State سیستم نداشته باشد، یک دستور غلط محسوب شده و باید در سمت query‌ها آن را پیاده سازی نمود.
در نتیجه Commnad‌ها دستوراتی هستند که از طرف کاربر برای تغییرات بر روی دیتا‌های ذخیره شده، ارسال میشوند.
فرض کنید Domain Objectی برای Movie تعریف کرده‌ایم و میخواهیم دستور اضافه کردن فیلم را پیاده سازی نماییم
public class CreateMovieCommand : ICommand  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }
    public CreateMovieCommand(string title, DateTime releaseDate, int runningTime)
    {
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
توجه: ICommand از طریق SimpleCQRS اضافه شده‌است.

Command Handler
بعد از اینکه Command مورد نیاز نوشته شد، حال احتیاج به پیاده سازی CommandHandler مربوطه که دستور متناظر را پردازش میکند، داریم.
public class CreateMovieCommandHandler : CommandHandler<CreateMovieCommand>  
{
    protected IDomainRepository _repository;

    public CreateMovieCommandHandler(IDomainRepository repository)
    {
        _repository = repository;
    }

    public override void Handle(CreateMovieCommand command)  
    {
        var movie = new Domain.Movie(Guid.NewGuid(), command.Title, 
    command.ReleaseDate, command.RunningTimeMinutes);

        _repository.Save(movie);
    }
}
Command Handler باید از کلاس جنریک <CommandHandler<T ارث بری نماید و T باید از نوع Command در نظر گرفته شود و همچنین IDomainRepository اینترفیسی است که توسط SimpleCQRS تعریف شده‌است و ما احتیاجی به پیاده سازی آن نداریم (در قسمت‌های بعدی پیکربندی آن را انجام میدهیم).
برای رسیدگی کردن به دستور مربوطه احتیاج به override کردن متد Handle میباشد.
کار اساسی توسط متد Save انجام میشود که همه‌ی event‌های pending شده توسط Domain Object را گرفته و آنها را به Event Store میفرستد.

Events
event‌ها تغییراتی هستند بر روی State جاری سیستم که توسط کاربر به وسیله‌ی Commandها فراخوانی میشوند.
رویداد‌ها serialize میشوند و درون Event Store ذخیره میشوند؛ بنابراین میتوان فراخوانی آنها را در هر لحظه انجام داد.
هر تعداد Event میتواند توسط یک دستور raise شود.
ساخت یک Event:
قبلا دستوری را برای ساخت یک movie نوشتیم و حال احتیاج به event مربوطه را داریم:
public class MovieCreatedEvent : DomainEvent  
{
    public Guid MovieId
    {
        get { return AggregateRootId; }
        set { AggregateRootId = value;}
    }

    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public MovieCreatedEvent(Guid movieId, string title, DateTime releaseDate, int runningTime)
    {
        MovieId = movieId;
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
فراموش نکنید که این کلاس آبجکتی خواهد بود که Serialize شده و در دیتابیس ذخیره خواهد شد. باید همه‌ی پراپرتی‌های لازم که با استفاده از این Event ممکن است تغییر کنند را شامل شود (بدیهی است که این پراپرتی‌ها از Domain Object گرفته میشود).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        Apply(new MovieCreatedEvent(Guid.NewGuid(), title, releaseDate, runningTimeMinutes));
    }
}
به Aggregate فوق که در اوایل بحث صحبت شده‌است دقت کنید. حال متد Apply باعث میشود که event مربوطه درون بخش لوکال aggregate root ذخیره شود. بنابراین بعدا میتواند به صورت فیزیکی درون Event Store ذخیره شود.

Event Handler
هر Event Handler  میتواند تعداد زیادی از IHandleDomainEvents ‌ها را پیاده سازی نماید. حال متد Handle این اینترفیس را پیاده سازی نمودیم. 
public class MovieEventHandler : IHandleDomainEvents<MovieCreatedEvent>  
{
    public void Handle(MovieCreatedEvent createdEvent)
    {
        using (MoviesContext entities = new MoviesContext())
        {
            entities.Movies.Add(new Movie()
            {
                Id = createdEvent.AggregateRootId,
                Title = createdEvent.Title,
                ReleaseDate = createdEvent.ReleaseDate,
                RunningTimeMinutes = createdEvent.RunningTimeMinutes
            });

            entities.SaveChanges();
        }
    }
}
مثلا در این قسمت با استفاده از ORM، شیء مورد نظر به صورت فیزیکی درون دیتابیس ذخیره میشود.
در قسمت آخر نیازمندیم که تغییرات زیر را به Movie اضافه نماییم.
درون Doamin Objectی که قبلا تعریف کرده بودیم متدی را به صورت زیر پیاده سازی مینماییم
protected void OnMovieCreated(MovieCreatedEvent domainEvent)
        {
            Id = domainEvent.AggregateRootId;
            Title = domainEvent.Title;
            ReleaseDate = domainEvent.ReleaseDate;
            RunningTimeMinutes = domainEvent.RunningTimeMinutes;
        }
باعث میشود پس از فراخوانی شدن Event، تغییرات صورت گرفته‌ی بر state سیستم، بر روی Domain Object اعمال شود و آن را بروزرسانی نماید. این متد دقیقا بصورت اتوماتیک وقتی که event مربوطه raise میشود، فراخوانی میشود.
پس از ترکیب CQRS و ES معماری اولیه‌ی سیستم چیزی شبیه به دیاگرام زیر خواهد بود (بسته به سناریوهای خاص میتواند سفارشی سازی شود)

خلاصه:
کاربر دستوری را از طریق برنامه به سیستم ارسال مینماید.
command مربوطه دریافت میشود و به روی Command Bus قرار داده میشود.
Command Handler وظیفه‌ی تفسیر کردن Command مربوطه را به عهده میگیرد و به وسیله‌ی Domain object آن event مورد نظر فراخوانی خواهد شد و باعث میشود domain object بروزرسانی گردد.
Event همان objectی است که باید به صورت serialize شده درون append only database ذخیره شود.
Event handler رویداد مربوطه را گرفته و بصورت فیزیکی مقادیر مورد نظر را در دیتابیس ذخیره مینماید.

Query
از آنجاییکه قسمت Read، در سیستم به صورت CQRS طراحی میشود، به راحتی میتوان query‌ها را optimize کرده و به صورت مثال به جای استفاده از ORM‌های معمول بطور مستقیم Stored Procedure فراخوانی کرده، تا جای ممکن کیفیت query‌ها بهترین حالت ممکن باشند. در حالیکه در مدل CRUD بهینه کردن بخش read بسیار پیچیده و بعضا غیر ممکن میباشد.

مزایای استفاده از این مدل
Distributed Systems Capabilities
یکی از مهمترین مزیت‌های این مدل تسهیم گسترش پذیری سیستم بر روی ماشین‌های فیزیکی مختلف از طریق messaging pattern میباشد.
High Availability
از آنجایی که سیستم توزیع پذیر طراحی شده‌است، هر قسمت از آن میتواند بدون توجه به fail شدن قسمت‌های دیگر به کار خود ادامه دهد.
Reduce Complexity
در domain‌های پیچیده طراحی و پیاده سازی objectهایی که مسئول دو قسمت read و write هستند، میتواند کار را بیش از حد پیچیده کرده و در این صورت چون business logic و read logic در هم ترکیب میشوند، مدیریت کردن موارد multiple user, shared data, performance, transactions, consistency سخت و سخت‌تر میشود.
Facilitates Building Task-based UI
وقتی شما به پیاده سازی الگوی CQRS میپردازید، اصولا هر عملی که توسط End user از طریق ui ارسال میشود، معادل command مربوط به آن وجود دارد. به همین جهت میتوان عملیات لازم برای اجرای یک پروسه را بصورت واضحی درک کرد.
 Maintenance And Flexibility
هر چند پیاده سازی این مدل سخت خواهد بود، اما در ابعاد وسیع‌تر به دلیل اینکه هر قسمت به صورت مجزایی طراحی شده و اینکه دستورات و رویداد‌ها به صورت تفکیک شده پیاده سازی شده‌اند، همچنین وجود ES، قابلیت زیادی به debug سیستم می‌دهد.
نکته: ES مدل مورد قبولی برای اکثر معماری‌های نوین سیستم‌های نرم افزاری امروزی میباشد و فقط مختص به CQRS نمیباشد. بطور مثال در معماری Microservices به وفور از Event Sourcing استفاده میشود.

مشکلات استفاده از این مدل
  • ذاتا پیاده سازی این مدل سخت و دشوار است و از آنجاییکه سادگی در پیاده سازی سیستم‌های نرم افزاری، یک اصل مهم محسوب میشود، بنابراین استفاده از این مدل محدود میشود به سیستم‌های نرم افزاری که مزیت‌های گفته شده در قسمت فوق برایشان حیاتی محسوب شود.
  • برای پیاده سازی سیستمی با این مدل احتیاج به تیم توسعه‌ای است که با مفاهیم آن کاملا آشنا باشد.
  • هر چند امروزه فضای فیزیکی برای ذخیره سازی دیتا ارزان محسوب میشود، اما به هر حال استفاده از این مدل به همراه ES، حجم زیادی از Disk space را خواهد گرفت.
  • همانطور که دیدید برای پیاده سازی یک Insert ساده، حجم زیادی کد نوشته شده‌است. بنابراین تولید اینگونه نرم افزار‌ها به زمان بیشتری نیاز دارد.
بنابراین باید در انتخاب معماری سیستم بسیار دقت شود؛ هر چند که این مدل برای سیستم‌های بزرگ و پیچیده خیلی کارآمد محسوب میشود و باعث یک Domain object غنی ، History Tracking، شفافیت در مشکلات Concurrency و همچنین Scalability و غیره خواهد شد، اما پیدا کردن برنامه نویسانی با داشتن درک عمیق روی این مباحث کمی سخت به نظر میرسد.
در قسمت بعدی بصورت کامل به پیاده سازی این الگو در یک اپلیکشن دات نتی خواهیم پرداخت.
نظرات مطالب
امکان تغییر رشته‌ی اتصالی به بانک اطلاعاتی در EF Core در زمان اجرای برنامه
روش دیگری برای تغییر رشته‌های اتصالی در زمان اجرا

به همراه مثال‌های رسمی EF-Core 7x، مثال LazyConnectionStringSample.cs به این مورد پرداخته؛ برای اینکار یک DbConnectionInterceptor سفارشی را طراحی کرده که در متد ConnectionOpeningAsync بازنویسی شده‌ی آن، امکان تغییر رشته‌ی اتصالی جاری به صورت پویا وجود دارد:
    public class ConnectionStringInitializationInterceptor : DbConnectionInterceptor
    {
        public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
            DbConnection connection, ConnectionEventData eventData, InterceptionResult result,
            CancellationToken cancellationToken = new())
        {
            if (string.IsNullOrEmpty(connection.ConnectionString))
            {
                connection.ConnectionString =  "new data ..." ;
            }

            return result;
        }
    }
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت چهارم - User Claims
در پروژه DNTIdentity یک claim سفارشی برای ClaimTypes.GivenName ایجاد شده‌است (^ و ^). شبیه به همان claim سفارشی را در پروژه‌های دیگر هم باید ایجاد کنید تا متد GetUserFirstName کار کند.
مطالب
بررسی چند نکته در مورد ارث بری کلاس‌ها در #C
مقدمه
وراثت، بین کلاس‌های والد (Parent) و فرزند (Child) ارتباط ایجاد می‌کند. در این مطلب، با یک مثال ساده، نکات مختلفی را بررسی خواهیم کرد.

در ابتدا کلاس‌هایی را با نام parent و child، به شکل زیر ایجاد می‌کنیم:
public class Parent
{
  public Parent()
  {
            Console.WriteLine("Parent Constructor");
  }
  public void Print()
  {
            Console.WriteLine("Parent Print");
  }
} public class Child : Parent { public Child() { Console.WriteLine("Child Constructor"); } public void Print() { Console.WriteLine("Child Print"); } }
با کامپایل کد فوق، هشدار (نه خطا) زیر توسط ویژوال استودیو صادر خواهد شد:

هشدارفوق این نکته را تذکر می‌دهد که متد Print تعریف شده در کلاس Child، پیاده سازی متد Print را در کلاس والد، مخفی (Hide) می‌کند. به همین خاطر پیشنهاد می‌کند که اگر واقعا قصد چنین کاری را داریم (نادیده گرفتن پیاده سازی print کلاس والد) از کلمه کلیدی (keyword) new استفاده کنیم. بدین شکل:
 public new void Print()
  {
     Console.WriteLine("Child Print");
  }
حال با نمونه سازی کلاس‌های فوق، رفتار سازنده و متد Print را بررسی می‌کنیم:
Console.WriteLine("====Parent====");
Parent parent = new Parent();
parent.Print();

Console.WriteLine("====Child====");
Child child = new Child();
child.Print();

Console.WriteLine("====Parent Via Child====");
Parent pc = new Child();
pc.Print();
در قسمت اول نمونه سازی از والد، نکته خاصی وجود ندارد. در ابتدا سازنده و سپس فراخوانی متد Print اتفاق خواهد افتاد.
در قسمت دوم نمونه سازی از فرزند، ابتدا سازنده والد و سپس سازنده فرزند فراخوانی خواهند.
در بخش سوم، یک نمونه فرزند را از نوع والد، ایجاد کرد‌هایم .( () Parent pc=new Child). در این بخش ابتدا سازنده والد و بعد از آن سازنده فرزند، فراخوانی می‌شود و با فراخوانی متد Print، متد والد اجرا خواهد شد.


استفاده از Virtual  و Override
اگر بدنبال این باشیم که در قسمت سوم متد Print فرزند فراخوانی شود، مفاهیم virtual و override به کمک ما خواهند آمد:
  public class Parent
{
  public Parent()
  {
Console.WriteLine("Parent Constructor");
  }

  public virtual void  Print()
  {
Console.WriteLine("Parent Print");
  }
}

public class Child : Parent
{
  public Child()
  {
Console.WriteLine("Child Constructor");
  }

  public override void Print()
  {
Console.WriteLine("Child Print");
  }
}
با تعریف متد از نوع virtual، امکان تحریف رفتار پیش فرض متد را توسط فرزند‌ها، مهیا خواهیم کرد. فرزندان نیز با override کردن متد والد، پیاده سازی خود را اعمال می‌کنند.
اگر خروجی کد بالا را با قسمت قبل مقایسه کنید، متوجه خواهید شد که در قسمت سوم فرزند، رفتار متد والد را تحریف/بازنویسی (override) کرده است ( پیاده سازی فرزند اجرا شده است).


سازنده‌های استاتیک (Static Constructor)
سازنده‌های استاتیک برای مقدار دهی به داده‌های استاتیک و یا انجام عملیاتی که تنها قرار است یکبار انجام شوند مورد استفاده قرار میگیرند. این سازنده‌ها بصورت اتوماتیک قبل از ساخت نمونه و مقداردهی اعضای استاتیک و قبل از سازنده‌های غیر استاتیک اجرا می‌شوند.
   public class Parent
{
  static Parent()
  {
Console.WriteLine("Parent static Constructor");
  }
  public Parent()
  {
Console.WriteLine("Parent Constructor");
  }

  public virtual void  Print()
  {
Console.WriteLine("Parent Print");
  }
}

public class Child : Parent
{
  static Child()
  {
Console.WriteLine("Child static Constructor");
  }
  public Child()
  {
Console.WriteLine("Child Constructor");
  }

  public override void Print()
  {
Console.WriteLine("Child Print");
  }
در بخش سوم در ابتدا سازنده استاتیک فرزند و سپس سازنده استاتیک والد فراخوانی خواهند شد و ترتیب اجرای سایر متد‌ها و سازنده‌ها مثل قبل است.




  جمع بندی
* اگر نمونه‌ای از یک فرزند را ایجاد کنیم، ابتدا سازنده‌ی والد فراخوانی خواهد شد و پس از آن سازنده‌ی کلاس فرزند.
* اگر قصد تحریف رفتار متد والد را در فرزندان داریم، می‌توانیم این متد‌ها را در کلاس والد بصورت virtual تعریف کنیم.
مطالب
MVC Scaffolding #1
پیشنیازها
کل سری ASP.NET MVC
به همراه کل سری EF Code First


MVC Scaffolding چیست؟

MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامه‌های ASP.NET MVC مبتنی بر EF Code First.


بررسی مقدماتی MVC Scaffolding

امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژه‌های ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندی‌ها و قابلیت‌های سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

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

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [DisplayName("Is Complete")]
        public bool IsComplete { set; get; }

        [StringLength(450)]
        public string Description { set; get; }
    }
}
سپس بر روی پوشه Controllers کلیک راست کرده و گزینه Add controller را انتخاب کنید. تنظیمات صفحه ظاهر شده را مطابق شکل زیر تغییر دهید:


همانطور که ملاحظه می‌کنید در قسمت قالب‌ها، تولید کنترلرهایی با اکشن متد‌های ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آن‌را پذیرفته‌ایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:


الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدل‌های برنامه ایجاد می‌گردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه می‌کند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلی‌های دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)

معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شده‌ای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی می‌تواند شامل فیلدهای بیشتری باشد و نباید آن‌را مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).

مزیت‌ها:
قسمت عمده‌ای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شده‌اند. کارهای اولیه‌ای که با هر روش و الگوی شناخته شده‌ای قصد پیاده سازی آن‌ها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمده‌ای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرم‌های آن‌را بخواهید با jQuery Ajax پیاده سازی کنید یا کنترل‌های پیش فرض را با افزونه‌های jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.


نصب بسته اصلی MVC Scaffolding توسط NuGet

بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console می‌توان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
اگر به مراحل نصب آن دقت کنید یک سری وابستگی را نیز به صورت خودکار دریافت کرده و نصب می‌کند:
Attempting to resolve dependency 'T4Scaffolding'.
Attempting to resolve dependency 'T4Scaffolding.Core'.
Attempting to resolve dependency 'EntityFramework'.
Successfully installed 'T4Scaffolding.Core 1.0.0'.
Successfully installed 'T4Scaffolding 1.0.8'.
Successfully installed 'MvcScaffolding 1.0.9'.
Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1.
Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1.
Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
از مواردی که با T4 آغاز شده‌اند در قسمت‌های بعدی برای سفارشی سازی کدهای تولیدی استفاده خواهیم کرد.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز می‌توان پیاده سازی کرد:
Scaffold Controller Task
نوشتن این دستور نیز ساده است. حروف sca را تایپ کرده و دکمه tab را فشار دهید. منویی ظاهر خواهد شد که امکان انتخاب دستور Scaffold را می‌دهد. یا برای نوشتن Controller نیز به همین نحو می‌توان عمل کرد.
نکته و مزیت مهم دیگری که در اینجا در دسترس می‌باشد، سوئیچ‌های خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچ‌های قابل استفاده است.


برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
اگر این دستور را اجرا کنیم به همان نتیجه حاصل از مراحل توضیح داده شده قبل خواهیم رسید؛ البته یا یک تفاوت: یک Partial View اضافه‌تر نیز به نام CreateOrEdit در پوشه Views\Tasks ایجاد شده است. این Partial View بر اساس بازخورد برنامه نویس‌ها مبنی بر اینکه Viewهای Edit و Create بسیار شبیه به هم هستند، ایجاد شده است.


بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding

در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
البته اگر دستور فوق را به همین نحو اجرا کنید با یک سری خطای Skipping مواجه خواهید شد مبنی بر اینکه فایل‌های قبلی موجود هستند و این دستور قصد بازنویسی آن‌ها را ندارد. برای اجبار به تولید مجدد کدهای موجود می‌توان از سوئیچ Force استفاده کرد:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
اتفاقی که در اینجا رخ خواهد داد، بازنویسی کد بی‌کیفت ابتدایی همراه با وهله سازی مستقیم DbContext در کنترلر، به نمونه بهتری که از الگوی مخزن استفاده می‌کند می‌باشد:
    public class TasksController : Controller
    {
        private readonly ITaskRepository taskRepository;

        // If you are using Dependency Injection, you can delete the following constructor
        public TasksController()
            : this(new TaskRepository())
        {
        }

        public TasksController(ITaskRepository taskRepository)
        {
            this.taskRepository = taskRepository;
        }
کیفیت کد تولیدی جدید مبتنی بر الگوی مخزن بد نیست؛ دقیقا همانی است که در هزاران سایت اینترنتی تبلیغ می‌شود؛ اما ... آنچنان مناسب هم نیست و اشکالات زیر را به همراه دارد:
public interface ITaskRepository : IDisposable
{
  IQueryable<Task> All { get; }
  IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties);
  Task Find(int id);
  void InsertOrUpdate(Task task);
  void Delete(int id);
  void Save();
}
اگر به ITaskRepository تولیدی دقت کنیم دارای خروجی IQueryable است؛ به این حالت leaky abstraction گفته می‌شود. زیرا امکان تغییر کلی یک خروجی IQueryable در لایه‌های دیگر برنامه وجود دارد و حد و مرز سیستم توسط آن مشخص نخواهد شد. بهتر است خروجی‌های لایه سرویس یا لایه مخزن در اینجا از نوع‌های IList یا IEnumerable باشند که درون آن‌ها از IQueryable‌ها برای پیاده سازی منطق مورد نظر کمک گرفته شده است.
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.
مطالب
پیاده سازی CQRS توسط MediatR - قسمت دوم
در این مطلب قصد داریم به بررسی امکانات داخلی فریمورک MediatR بپردازیم. سورس این قسمت مقاله در این ریپازیتوری قابل دسترسی است.

نصب و راه اندازی


در ابتدا یک پروژه جدید ASP.NET Core از نوع API را ایجاد میکنیم و با استفاده از Nuget Package Manager ، پکیج MediatR را داخل پروژه نصب میکنیم:
Install-Package MediatR

بعد از نصب نیاز داریم تا نیازمندی‌های این فریمورک را داخل DI Container خود Register کنیم. اگر از DI Container پیشفرض ASP.NET Core استفاده کنیم ، کافیست پکیج متناسب آن با Microsoft.Extensions.DependencyInjection را نصب کرده و به‌راحتی نیازمندی‌های MediatR را فراهم سازیم:
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
بعد از نصب کافیست این کد را به متد ConfigureServices فایل Startup.cs پروژه خود اضافه کنید تا نیازمندی‌های MediatR داخل DI Container شما Register شوند:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddMediatR();
}

* اگر از DI Container‌های دیگری استفاده میکنید، میتوانید با استفاده از توضیحات این لینک MediatR را داخل Container مورد نظرتان Register کنید.

IRequest

همانطور که در مطلب قبل گفتیم، در CQRS متدهای برنامه به 2 قسمت Command و Query تقسیم میشوند. در MediatR اینترفیسی بنام IRequest ایجاد شده‌است و تمامی Class‌های Command/Query ما که درخواست انجام کاری را میدهند، از این interface ارث بری خواهند کرد.

دلیل نامگذاری این interface به IRequest این است که ما درخواست افزودن یک مشتری جدید را ایجاد میکنیم و قسمت دیگری از برنامه، وظیفه پاسخگویی به این درخواست را برعهده خواهد داشت.

IRequest دارای 2 Overload از نوع Generic و Non-Generic است.
پیاده سازی Non-Generic آن برای درخواست‌هایی است که Response برگشتی ندارند ( معمولا Command‌ها ) و منتظر جوابی از سمت آن‌ها نیستیم و پیاده سازی Generic آن، نوع Response ای را که بعد از پردازش درخواست برگشت داده میشود، مشخص میسازد.

برای مثال قصد داریم مشتری جدیدی را در برنامه خود ایجاد کنیم. کلاس Customer به این صورت تعریف شده است:
public class Customer
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime RegistrationDate { get; set; }
}

و Dto متناسب با آن نیز به این صورت تعریف شده است :
public class CustomerDto
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string RegistrationDate { get; set; }
}

افزودن مشتری، یک Command است؛ زیرا باعث افزودن رکوردی جدیدی به دیتابیس و تغییر State برنامه میشود. کلاس جدیدی به اسم CreateCustomerCommand ایجاد کرده و از IRequest ارث بری میکنیم و نوع Response برگشتی آن را CustomerDto قرار میدهیم:
public class CreateCustomerCommand : IRequest<CustomerDto>
{
    public CreateCustomerCommand(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public string FirstName { get; }

    public string LastName { get; }
}

کلاس CreateCustomerCommand نیازمندی‌های خود را از طریق Constructor مشخص میسازد. برای ایجاد کردن یک مشتری حداقل چیزی که لازم است، Firstname و Lastname آن است و بعد از ارسال مقادیر مورد نیاز به سازنده این کلاس، مقادیر بدلیل get-only بودن قابل تغییر نیستند.
در اینجا مفهوم immutability بطور کامل رعایت شده است.

Immutability


IRequestHandler


هر Request نیاز به یک Handler دارد تا آن را پردازش کند. در MediatR کلاس‌هایی که وظیفه پردازش یک IRequest را دارند، از اینترفیس IRequestHandler ارث بری کرده و متد Handle آن را پیاده سازی میکنند. اگر متد شما Synchronous است میتوانید از کلاس RequestHandler بطور مستقیم ارث بری کنید.

در ادامه مثال قبلی، کلاسی به اسم CreateCustomerCommandHandler ایجاد و از IRequestHandler ارث بری میکنیم و منطق افزودن مشتری به دیتابیس را پیاده سازی میکنیم:
public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, CustomerDto>
{
    readonly ApplicationDbContext _context;
    readonly IMapper _mapper;

    public CreateCustomerCommandHandler(ApplicationDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<CustomerDto> Handle(CreateCustomerCommand createCustomerCommand, CancellationToken cancellationToken)
    {
        Customer customer = _mapper.Map<Customer>(createCustomerCommand);

        await _context.Customers.AddAsync(customer, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);

        return _mapper.Map<CustomerDto>(customer);
    }
}

ورودی اول IRequestHandler، کلاسی است که درخواست، آن را پردازش خواهد کرد و پارامتر ورودی دوم، کلاسی است که در نتیجه پردازش بعنوان Response برگشت داده خواهد شد.

همانطور که میبینید در این Handler از DbContext مربوط به Entity Framework برای ثبت اطلاعات داخل دیتابیس و IMapper مربوط به AutoMapper برای نگاشت CreateCustomerCommand به Customer استفاده شده است.

تنظیمات Profile مربوط به AutoMapper ما به این صورت است تا در هنگام نگاشت CreateCustomerCommand ، مقدار RegistrationDate مربوط به Customer برابر با زمان فعلی قرار داده شود و برای نگاشت Customer به CustomerDto نیز ، تاریخ RegistrationDate با فرمتی قابل فهم به کاربران نمایش داده شود :
public class DomainProfile : Profile
{
    public DomainProfile()
    {
        CreateMap<CreateCustomerCommand, Customer>()
            .ForMember(c => c.RegistrationDate, opt =>
                opt.MapFrom(_ => DateTime.Now));

        CreateMap<Customer, CustomerDto>()
            .ForMember(cd => cd.RegistrationDate, opt =>
                opt.MapFrom(c => c.RegistrationDate.ToShortDateString()));
    }
}

در نهایت با inject کردن اینترفیس IMediator به کنترلر خود و فرستادن یک درخواست POST به این اکشن، درخواست ایجاد مشتری را توسط متد Send میدهیم :
[HttpPost]
public async Task<IActionResult> CreateCustomer([FromBody] CreateCustomerCommand createCustomerCommand)
{
    CustomerDto customer = await _mediator.Send(createCustomerCommand);
    return CreatedAtAction(nameof(GetCustomerById), new { customerId = customer.Id }, customer);
}

همانطور که میبینید ما در اینجا فقط درخواست، فرستاده‌ایم و وظیفه پیدا کردن Handler این درخواست را فریمورک MediatR برعهده گرفته‌است و ما هیچ جایی بطور مستقیم Handler خود را صدا نزده ایم. ( Hollywood Principle: Don't Call Us, We Call You )


روند پیاده سازی Query‌ها نیز دقیقا شبیه به Command است و نمونه‌ای از آن داخل ریپازیتوری ذکر شده‌ی در ابتدای مطلب وجود دارد.
اینترفیس IMediator علاوه بر متد Send ، دارای متد دیگری بنام Publish نیز هست که وظیفه Raise کردن Event‌ها را برعهده دارد که در مقالات بعدی از آن استفاده خواهیم کرد.

چند نکته :
1- در نامگذاری Command‌ها، کلمه Command در انتهای نام آن‌ها آورده میشود؛ مثال: CreateCustomerCommand
2- در نامگذاری Query‌ها، کلمه Query در انتهای نام آن‌ها آورده میشود؛ مثال : GetCustomerByIdQuery
3- در نامگذاری Handler‌ها، از ترکیب Command/Query + Handler استفاده میکنیم؛ مثال : CreateCustomerCommandHandler, GetCustomerByIdQueryHandler
4- در این قسمت Request‌های ما بدون هیچ Validation ای وارد Handler هایشان میشدند که این نیاز اکثر برنامه‌ها نیست. در قسمت بعدی با استفاده از Fluent Validation پارامترهای Request هایمان را بطور خودکار اعتبارسنجی میکنیم.