مطالب
مرتب سازی رکوردها به صورت اتفاقی در Entity framework
یکی از انواع روش‌هایی که در SQL Server و مشتقات آن برای نمایش رکوردها به صورت اتفاقی مورد استفاده قرار می‌گیرد، استفاده از کوئری زیر است:
SELECT * FROM table
ORDER BY NEWID()
سؤال: ترجمه و معادل کوئری فوق در Entity framework به چه صورتی است؟
پاسخ:
یک مثال کامل را در این زمینه در ادامه ملاحظه می‌کنید:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;

namespace Sample
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            context.Users.Add(new User { Name = "User 1", Age = 20 });
            context.Users.Add(new User { Name = "User 2", Age = 25 });
            context.Users.Add(new User { Name = "User 3", Age = 30 });
            context.Users.Add(new User { Name = "User 4", Age = 35 });
            context.Users.Add(new User { Name = "User 5", Age = 40 });
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());

            using (var context = new MyContext())
            {
               var randomListOfUsers =
                        context.Users
                               .Where(person => person.Age >= 25 && person.Age < 40)
                               .OrderBy(person => Guid.NewGuid())
                               .ToList();

               foreach (var person in randomListOfUsers)
                   Console.WriteLine("{0}:{1}", person.Name, person.Age);
            }
        }
    }
}
تنها نکته مهم آن سطر ذیل است که برای مرتب سازی اتفاقی استفاده شده است:
.OrderBy(person => Guid.NewGuid())
که معادل
ORDER BY NEWID()
در SQL Server است.

خروجی SQL تولیدی کوئری LINQ فوق را نیز در ادامه مشاهده می‌کنید:
SELECT 
[Project1].[Id] AS [Id], 
[Project1].[Name] AS [Name], 
[Project1].[Age] AS [Age]
FROM ( SELECT 
NEWID() AS [C1], ------ Guid created here
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Age] AS [Age]
FROM [dbo].[Users] AS [Extent1]
WHERE ([Extent1].[Age] >= 25) AND ([Extent1].[Age] < 40)
)  AS [Project1]
ORDER BY [Project1].[C1] ASC  ------ Used for sorting here
مطالب
طراحی یک ماژول IpBlocker در ASP.NET MVC
همانطور که میدانید وب سایت‌های اینترنتی در معرض انواع و اقسام حملات قرار دارند و یکی از این حملات Dos است. در این نوشتار میخواهیم تکه کدی را ارائه دهیم، تا این نوع حملات را دفع نماید. همانطور که میدانید یک درخواست Http باید از ماژول‌های مختلفی عبور نماید تا به یک Http Handler برسد.
ابتدا باید یک Enum تعریف کنیم تا نوع درخواست کاربر را مشخص کند. مثلا 100 درخواست ابتدایی را به عنوان FirstVisite در نظر گرفته و اگر تعداد درخواستها از 100 گذشت، در دسته Revisit قرار میگیرند و ... . البته این بستگی به شما دارد که مقادیر را چقدر در نظر بگیرید؛ اما دقت کنید.
private enum VisiteType
        {
            FirstVisite = 2,
            Reviste = 4,
        }
در مرحله بعدی باید آدرس Ip بیننده سایت را بدست آورده وچک کنیم که این آدرس Ip در کش موجود هست یا خیر. اگر موجود بود مقدار متغیر hits را افزایش میدهیم و چک میکنیم که متغیر با کدام یک از مقادیر Enum برابری میکند و آن را در کش سیستم ذخیره میکنیم.
if (httpContext.Cache[ipAddress] != null)
                {
                    hits++;

                    if (hits == (int)VisiteType.FirstVisite)
                    {
                        httpContext.Cache.Insert(key: ipAddress,
                           value: hits,
                           dependencies: null,
                           absoluteExpiration: DateTime.UtcNow.AddSeconds(1),
                           slidingExpiration: Cache.NoSlidingExpiration);
                        return;
                    }
در تکه کد بالا چون درخواست به حد معین و معقولی رسیده است، آدرس بعد از یک ثانیه از کش حذف میشود. اما اگر درخواستهای غیرمعقولی به سرور ارسال شود، باید پاسخ را قطع و آدرس Ip را در کش ذخیره و این آدرس Ip باید به مدت معینی در کش مانده و بلاک شود.
using System;
using System.Net;
using System.Web;
using System.Web.Caching;

namespace IpBlocker
{
    public class IpBlocker : IHttpModule
    {
        private int hits = 0;
        /// <summary>
        /// Define enum to specify visite type
        /// </summary>
        private enum VisiteType
        {
            FirstVisite = 2,
            Reviste = 4,
        }
        public void Init(HttpApplication context)
        {
            context.BeginRequest += OnBeginRequest;
        }
        public void Dispose()
        {

        }

        public void OnBeginRequest(object sender, EventArgs e)
        {
            var httpApplication = sender as HttpApplication;
            var httpContext = httpApplication?.Context;
            ProcessRequest(httpApplication, httpContext);
        }

        private void ProcessRequest(HttpApplication application, HttpContext httpContext)
        {
            //Checke if browser is a search engine web crawler
            if (httpContext.Request.Browser.Crawler)
                return;

            var ipAddress = application.Context.Request.UserHostAddress;

            if (httpContext.Cache[ipAddress] == null)
            {
                // Reset hits for new request after blocking ip
                if (hits > 0)
                    hits = 0;

                hits++;

                httpContext.Cache.Insert(key: ipAddress,
                   value: hits,
                   dependencies: null,
                   absoluteExpiration: DateTime.UtcNow.AddSeconds(1),
                   slidingExpiration: Cache.NoSlidingExpiration);

                return;
            }          

            else
            {
                if (httpContext.Cache[ipAddress] != null)
                {
                    hits++;

                    if (hits == (int)VisiteType.FirstVisite)
                    {
                        httpContext.Cache.Insert(key: ipAddress,
                           value: hits,
                           dependencies: null,
                           absoluteExpiration: DateTime.UtcNow.AddSeconds(1),
                           slidingExpiration: Cache.NoSlidingExpiration);
                        return;
                    }

                    if (hits == (int)VisiteType.Reviste)
                    {

                        httpContext.Cache.Insert(key: ipAddress,
                           value: hits,
                           dependencies: null,
                           absoluteExpiration: DateTime.UtcNow.AddSeconds(1),
                           slidingExpiration: Cache.NoSlidingExpiration);
                        return;
                    }
                    if (hits > (int)VisiteType.Reviste)
                    {
                        httpContext.Cache.Insert(key: ipAddress,
                            value: hits,
                            dependencies: null,
                            absoluteExpiration: DateTime.UtcNow.AddMinutes(1),
                            slidingExpiration: Cache.NoSlidingExpiration);

                        httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                        httpContext.Response.SuppressContent = true;
                        httpContext.Response.End();
                    }
                }
            }
        }
    }
}
در کد بالا محدودیت زمانی یک دقیقه در نظر گرفته شده است.

نظرات مطالب
پیاده سازی Option یا Maybe در #C
با تشکر از شما
لزوما با پیاده سازی ارائه شده در مطلب جاری، از شر بررسی Null بودن یا نبودن خلاص نشده ایم (از دید استفاده کننده) چرا که خروجی متد همچنان می‌تواند Nullable باشد (کلاس Option یک نوع ارجاعی می‌باشد). چرا که استفاده کننده از آن لازم است برروی خروجی خود متد که یک وهله از Option می‌باشد بررسی Null بودن یا عدم آن را انجام دهد. برای رهایی از این موضوع استفاده از struct راه حل معقولی می‌باشد؛ یک پیاده سازی از آن به صورت زیر می‌باشد:
    public struct Maybe<T> : IEquatable<Maybe<T>>
        where T : class
    {
        private readonly T _value;

        private Maybe(T value)
        {
            _value = value;
        }

        public bool HasValue => _value != null;
        public T Value => _value ?? throw new InvalidOperationException();
        public static Maybe<T> None => new Maybe<T>();


        public static implicit operator Maybe<T>(T value)
        {
            return new Maybe<T>(value);
        }

        public static bool operator ==(Maybe<T> maybe, T value)
        {
            return maybe.HasValue && maybe.Value.Equals(value);
        }

        public static bool operator !=(Maybe<T> maybe, T value)
        {
            return !(maybe == value);
        }

        public static bool operator ==(Maybe<T> left, Maybe<T> right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Maybe<T> left, Maybe<T> right)
        {
            return !(left == right);
        }

        /// <inheritdoc />
        /// <summary>
        ///     Avoid boxing and Give type safety
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(Maybe<T> other)
        {
            if (!HasValue && !other.HasValue)
                return true;

            if (!HasValue || !other.HasValue)
                return false;

            return _value.Equals(other.Value);
        }

        /// <summary>
        ///     Avoid reflection
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj is T typed)
            {
                obj = new Maybe<T>(typed);
            }

            if (!(obj is Maybe<T> other)) return false;

            return Equals(other);
        }

        /// <summary>
        ///     Good practice when overriding Equals method.
        ///     If x.Equals(y) then we must have x.GetHashCode()==y.GetHashCode()
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return HasValue ? _value.GetHashCode() : 0;
        }

        public override string ToString()
        {
            return HasValue ? _value.ToString() : "NO VALUE";
        }
    }

 این بار می‌توان به امضای متد مذکور اعتماد کرد که قطعا خروجی null ارائه نخواهد داد؛ مگر اینکه به صورت صریح مشخص شود.
نکته: پیاده سازی صحیحی از واسط IEquatable برای Value Typeها در پیاده سازی struct بالا در نظر گرفته شده است.
استفاده از آن
public virtual async Task<Maybe<TModel>> GetByIdAsync(long id)
{
    Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

    var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
        .ProjectTo<TModel>(_mapper.ConfigurationProvider).SingleOrDefaultAsync();

    return entity;
}
ساختار داده Maybe تعریف شده در بالا شبیه است با ساختار داده Nullable با این تفاوت که برای انواع ارجاعی مورد استفاده می‌باشد.
Maybe<T> = Nullable<T>

مطالب
تهیه‌ی گزارش از آخرین ارائه‌های یک پروژه در GitHub
چندی قبل مطلب «اطلاع از بروز رسانی نرم افزار ساخته شده» را در سایت جاری مطالعه کردید. در این روش بسیار متداول، شماره نگارش‌های جدید برنامه در یک فایل XML و مانند آن قرار می‌گیرند و برنامه هربار این فایل را جهت یافتن شماره‌های مندرج در آن اسکن می‌کند. اگر پروژه‌ی شما سورس باز است و در GitHub هاست شده، روش دیگری نیز برای یافتن این اطلاعات وجود دارد. در GitHub می‌توان از طریق آدرسی به شکل https://api.github.com/repos/user_name/project_name/releases به اطلاعات آخرین ارائه‌های یک پروژه (قرار گرفته در برگه‌ی releases آن) با فرمت JSON دسترسی یافت (یک مثال). در ادامه قصد داریم روش استفاده‌ی از آن‌را بررسی کنیم.


ساختار JSON ارائه‌های یک پروژه در GitHub

ساختار کلی اطلاعات ارائه‌های یک پروژه در GitHub چنین شکلی را دارد:
[
  {
    release_info,
    "assets": [
      {
      }
    ]
  }
]
در اینجا آرایه‌ای از اطلاعات ارائه‌ی یک پروژه ارسال می‌شود. هر ارائه نیز دارای دو قسمت است: لینکی به صفحه‌ی اصلی release در GitHub و سپس آرایه‌ای به نام assets که در آن اطلاعات فایل‌های پیوستی مانند نام فایل، آدرس، اندازه و امثال آن قرار گرفته‌اند.



تهیه‌ی کلاس‌های معادل فرمت JSON ارائه‌های برنامه در GitHub

اگر بخواهیم قسمت‌های مهم خروجی JSON فوق را تبدیل به کلاس‌های معادل دات نتی کنیم، به دو کلاس ذیل خواهیم رسید:
using Newtonsoft.Json;
using System;

namespace ApplicationAnnouncements
{
    public class GitHubProjectRelease
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "assets_url")]
        public string AssetsUrl { get; set; }

        [JsonProperty(PropertyName = "upload_url")]
        public string UploadUrl { get; set; }

        [JsonProperty(PropertyName = "html_url")]
        public string HtmlUrl { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "tag_name")]
        public string TagName { get; set; }

        [JsonProperty(PropertyName = "target_commitish")]
        public string TargetCommitish { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "body")]
        public string Body { get; set; }

        [JsonProperty(PropertyName = "draft")]
        public bool Draft { get; set; }

        [JsonProperty(PropertyName = "prerelease")]
        public bool PreRelease { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "published_at")]
        public DateTime PublishedAt { get; set; }

        [JsonProperty(PropertyName = "assets")]
        public Asset[] Assets { get; set; }
    }

    public class Asset
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "label")]
        public string Label { get; set; }

        [JsonProperty(PropertyName = "content_type")]
        public string ContentType { get; set; }

        [JsonProperty(PropertyName = "state")]
        public string State { get; set; }

        [JsonProperty(PropertyName = "size")]
        public int Size { get; set; }

        [JsonProperty(PropertyName = "download_count")]
        public int DownloadCount { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "updated_at")]
        public DateTime UpdatedAt { get; set; }
    }
}
در اینجا از ویژگی JsonProperty جهت معرفی نام‌های واقعی خواص ارائه شده‌ی توسط GitHub استفاده کرده‌ایم.
پس از تشکیل این کلاس‌ها، مرحله‌ی بعد، دریافت اطلاعات JSON از آدرس API ارائه‌های پروژه در GitHub و سپس نگاشت آن‌ها می‌باشد:
using (var webClient = new WebClient())
{
    webClient.Headers.Add("user-agent", "DNTProfiler");
    var jsonData = webClient.DownloadString(url);
    var gitHubProjectReleases = JsonConvert.DeserializeObject<GitHubProjectRelease[]>(jsonData);

    foreach (var release in gitHubProjectReleases)
    {
       foreach (var asset in release.Assets)
       {
           // …
       }
    }
}
حین کار با WebClient یا هر روش دیگری که برای دریافت اطلاعات از وب استفاده می‌کنید، حتما نیاز است user-agent را ذکر کرد. در غیر اینصورت GitHub درخواست شما را برگشت خواهد زد. پس از دریافت اطلاعات JSON، با استفاده از متد JsonConvert.DeserializeObject کتابخانه‌ی JSON.NET، می‌توان آن‌ها را تبدیل به آرایه‌ای از GitHubProjectRelease کرد.

یک نکته: اگر به صفحه‌ی اصلی ارائه‌های یک پروژه در GitHub دقت کنید، شماره‌ی تعداد بار دریافت یک ارائه مشخص نشده‌است. در این API، عدد DownloadCount، بیانگر تعداد بار دریافت پروژه‌ی شما است.
مطالب
تنظیمات کش توزیع شده‌ی مبتنی بر SQL Server در ASP.NET Core
ASP.NET Core به همراه زیر ساختی‌است جهت خارج کردن امکانات Caching درون حافظه‌ای آن از سرور جاری و انتقال آن به سرورهای دیگر جهت کاهش بار سرور و برنامه. این کش توزیع شده را می‌توان به عنوان زیرساختی برای مدیریت سشن‌ها، مدیریت اطلاعات کش و همچنین مدیریت کوکی‌های حجیم ASP.NET Core Identity نیز بکار گرفت. برای مثال بجای ارسال یک کوکی حجیم بالای 5 کیلوبایت به کلاینت، فقط ID رمزنگاری شده‌ی آن‌را ارسال کرد و اصل کوکی را در داخل دیتابیس ذخیره و بازیابی نمود. این مساله هم مقیاس پذیری برنامه را افزایش خواهد داد و هم امنیت آن‌را با عدم ارسال اصل محتوای کوکی به سمت کلاینت‌ها و یا ذخیره سازی اطلاعات سشن‌ها در بانک اطلاعاتی، مشکلات راه اندازی مجدد برنامه را به طور کامل برطرف می‌کنند و در این حالت بازیابی Application pool و یا کرش برنامه و یا ری استارت شدن کل سرور، سبب از بین رفتن سشن‌های کاربران نخواهند شد. بنابراین آشنایی با نحوه‌ی راه اندازی این امکانات، حداقل از بعد امنیتی بسیار مفید هستند؛ حتی اگر سرور ذخیره کننده‌ی این اطلاعات، همان سرور و بانک اطلاعاتی اصلی برنامه باشند.


پیشنیازهای کار با کش توزیع شده‌ی مبتنی بر SQL Server

برای کار با کش توزیع شده‌ی با قابلیت ذخیره سازی در یک بانک اطلاعاتی SQL Server، نیاز است دو بسته‌ی ذیل را به فایل project.json برنامه اضافه کرد:
{
    "dependencies": {
        "Microsoft.Extensions.Caching.SqlServer": "1.1.0"
    },
    "tools": {
        "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final"
    }
}
وابستگی که در قسمت dependencies ذکر شده‌است، کلاس‌های اصلی کار با کش توزیع شده را به برنامه اضافه می‌کند. ذکر وابستگی قسمت tools، اختیاری است و کار آن، ایجاد جدول مورد نیاز برای ذخیره سازی اطلاعات، در یک بانک اطلاعاتی SQL Server می‌باشد.


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار sql-cache

پس از افزودن و بازیابی ارجاعات فوق، با استفاده از خط فرمان، به پوشه‌ی جاری برنامه وارد شده و دستور ذیل را صادر کنید:
 dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;" "dbo" "AppSqlCache"
توضیحات:
- در اینجا می‌توان هر نوع رشته‌ی اتصالی معتبری را به انواع و اقسام بانک‌های SQL Server ذکر کرد. برای نمونه در مثال فوق این رشته‌ی اتصالی به یک بانک اطلاعاتی از پیش ایجاد شده‌ی LocalDB اشاره می‌کند. نام دلخواه این بانک اطلاعاتی در اینجا sql_cache ذکر گردیده و نام دلخواه جدولی که قرار است این اطلاعات را ثبت کند AppSqlCache تنظیم شده‌است و dbo، نام اسکیمای جدول است:


در اینجا تصویر ساختار جدولی را که توسط ابزار dotnet sql-cache ایجاد شده‌است، مشاهده می‌کنید. اگر خواستید این جدول را خودتان دستی ایجاد کنید، یک چنین کوئری را باید بر روی دیتابیس مدنظرتان اجرا نمائید:
CREATE TABLE AppSqlCache (
    Id                         NVARCHAR (449)  COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
    Value                      VARBINARY (MAX) NOT NULL,
    ExpiresAtTime              DATETIMEOFFSET  NOT NULL,
    SlidingExpirationInSeconds BIGINT          NULL,
    AbsoluteExpiration         DATETIMEOFFSET  NULL,
    CONSTRAINT pk_Id PRIMARY KEY (Id)
);

CREATE NONCLUSTERED INDEX Index_ExpiresAtTime
    ON AppSqlCache(ExpiresAtTime);


ایجاد جدول ذخیره سازی اطلاعات کش توزیع شده به کمک ابزار Migrations در EF Core

زیر ساخت کش توزیع شده‌ی مبتنی بر SQL Server هیچگونه وابستگی به EF Core ندارد و تمام اجزای آن توسط Async ADO.NET نوشته شده‌اند. اما اگر خواستید قسمت ایجاد جدول مورد نیاز آن‌را به ابزار Migrations در EF Core واگذار کنید، روش کار به صورت زیر است:
- ابتدا یک کلاس دلخواه جدید را با محتوای ذیل ایجاد کنید:
    public class AppSqlCache
    {
        public string Id { get; set; }
        public byte[] Value { get; set; }
        public DateTimeOffset ExpiresAtTime { get; set; }
        public long? SlidingExpirationInSeconds { get; set; }
        public DateTimeOffset? AbsoluteExpiration { get; set; }
    }
- سپس تنظیمات ایجاد جدول متناظر با آن را به نحو ذیل تنظیم نمائید:
    public class MyDBDataContext : DbContext
    {
        public virtual DbSet<AppSqlCache> AppSqlCache { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<AppSqlCache>(entity =>
            {
                entity.ToTable(name: "AppSqlCache", schema: "dbo");
                entity.HasIndex(e => e.ExpiresAtTime).HasName("Index_ExpiresAtTime");
                entity.Property(e => e.Id).HasMaxLength(449);
                entity.Property(e => e.Value).IsRequired();
            });
        }
    }
به این ترتیب این جدول جدید به صورت خودکار در کنار سایر جداول برنامه ایجاد خواهند شد.
البته این مورد به شرطی است که بخواهید از یک دیتابیس، هم برای برنامه و هم برای ذخیره سازی اطلاعات کش استفاده کنید.


معرفی تنظیمات رشته‌ی اتصالی و نام جدول ذخیره سازی اطلاعات کش به برنامه

پس از ایجاد جدول مورد نیاز جهت ذخیره سازی اطلاعات کش، اکنون نیاز است این اطلاعات را به برنامه معرفی کرد. برای این منظور به کلاس آغازین برنامه مراجعه کرده و متد الحاقی AddDistributedSqlServerCache را بر روی مجموعه‌ی سرویس‌های موجود فراخوانی کنید؛ تا سرویس‌های این کش توزیع شده نیز به برنامه معرفی شوند:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=sql_cache;Integrated Security=True;";
        options.SchemaName = "dbo";
        options.TableName = "AppSqlCache";
    });
باتوجه به توزیع شده بودن این کش، هیچ الزامی ندارد که ConnectionString ذکر شده‌ی در اینجا با رشته‌ی اتصالی مورد استفاده‌ی توسط EF Core یکی باشد (هرچند مشکلی هم ندارد).


آزمایش کش توزیع شده‌ی تنظیمی با فعال سازی سشن‌ها

سشن‌ها را همانند نکات ذکر شده‌ی در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions» فعال کنید و سپس مقداری را در آن بنویسید:
public IActionResult Index()
{
   HttpContext.Session.SetString("User", "VahidN");
   return Json(true);
}

public IActionResult About()
{
   var userContent = HttpContext.Session.GetString("User");
   return Json(userContent);
}
اکنون از جدول AppSqlCache کوئری بگیرید:


همانطور که مشاهده می‌کنید، سیستم سشن اینبار بجای حافظه، به صورت خودکار از جدول بانک اطلاعاتی SQL Server تنظیم شده‌، برای ذخیره سازی اطلاعات خود استفاده کرده‌است.


کار با کش توزیع شده از طریق برنامه نویسی

همانطور که در مقدمه‌ی بحث نیز عنوان شد، استفاده‌ی از زیر ساخت کش توزیع شده منحصر به استفاده‌ی از آن جهت ذخیره سازی اطلاعات سشن‌ها نیست و از آن می‌توان جهت انواع و اقسام سناریوهای مختلف مورد نیاز استفاده کرد. در این حالت روش دسترسی به این زیر ساخت، از طریق اینترفیس IDistributedCache است. زمانیکه متد AddDistributedSqlServerCache را فراخوانی می‌کنیم، در حقیقت کار ثبت یک چنین سرویسی به صورت خودکار انجام خواهد شد:
 services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
به عبارتی کلاس SqlServerCache به صورت singleton به مجموعه‌ی سرویس‌های برنامه اضافه شده‌است و برای دسترسی به آن تنها کافی است اینترفیس IDistributedCache را به کنترلرها و یا سرویس‌های برنامه تزریق و از امکانات آن استفاده کنیم.

در اینجا یک نمونه از این تزریق وابستگی و سپس استفاده‌ی از متدهای Set و Get اینترفیس IDistributedCache را مشاهده می‌کنید:
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class CacheTestController : Controller
    {
        readonly IDistributedCache _cache;
        public CacheTestController(IDistributedCache cache)
        {
            _cache = cache;
        }
 
        public IActionResult SetCacheData()
        {
            var time = DateTime.Now.ToLocalTime().ToString();
            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTime.Now.AddYears(1)
 
            };
            _cache.Set("Time", Encoding.UTF8.GetBytes(time), cacheOptions);
            return View();
        }
 
        public IActionResult GetCacheData()
        {
            var time = Encoding.UTF8.GetString(_cache.Get("Time"));
            ViewBag.data = time;
            return View();
        }
 
        public bool RemoveCacheData()
        {
            _cache.Remove("Time");
            return true;
        }
    }
}
در ابتدای بحث که ساختار جدول ذخیره سازی اطلاعات کش را بررسی کردیم، فیلد value آن یک چنین نوعی را دارد:
  Value  VARBINARY (MAX) NOT NULL,
که در سمت کدهای دات نتی، به شکل آرایه‌ای از بایت‌ها قابل بیان است.
  public byte[] Value { get; set; }
به همین جهت متد Set آن مقدار مدنظر را به صورت آرایه‌ای از بایت‌ها قبول می‌کند.
در این حالت اگر برنامه را اجرا و مسیر http://localhost:7742/CacheTest/SetCacheData را فراخوانی کنیم، اطلاعات ذخیره شده‌ی با کلید Test را می‌توان در بانک اطلاعاتی مشاهده کرد:



Tag helper مخصوص کش توزیع شده

در ASP.NET Core، می‌توان از یک Tag Helper جدید به نام distributed-cache برای کش سمت سرور توزیع شده‌ی محتوای قسمتی از یک View به نحو ذیل استفاده کرد:
<distributed-cache name="MyCacheItem2" expires-sliding="TimeSpan.FromMinutes(30)">
    <p>From distributed-cache</p>
    @DateTime.Now.ToString()
</distributed-cache>
که اطلاعات آن در بانک اطلاعاتی به نحو ذیل ذخیره می‌شود:


در اینجا name به صورت هش شده به صورت کلید کش مورد استفاده قرار می‌گیرد. سپس محتوای تگ distributed-cache رندر شده، تبدیل به آرایه‌ای از بایت‌ها گردیده و در بانک اطلاعاتی ذخیره می‌گردد.
ذکر name در اینجا اجباری است و باید دقت داشت که چون به عنوان کلید بازیابی کش مورد استفاده قرار خواهد گرفت، نباید به اشتباه در قسمت‌های دیگر برنامه با همین نام وارد شود. در غیر اینصورت دو قسمتی که name یکسانی داشته باشند، یک محتوا را نمایش خواهند داد.
نظرات مطالب
ASP.NET MVC #12
سلام آقای نصیری
با تشکر از توضیحاتتون
من کد زیر رو که خودتون واسه شمسی کردن تاریخ گذاشته بودین، به نام datetime.cshtml و صورت PartialView داخل پوشه DisplayTemplates قرار دادم. اما همچنان DateTime‌ها رو به فرمت میلادی نمایش میده. میشه بگین اشکال کارم کجاست ؟
@using System.Globalization
@model Nullable<DateTime>
           
@helper ShamsiDateTime(DateTime info, string separator = "/", bool includeHourMinute = true)
{
    int ym = info.Year;
    int mm = info.Month;
    int dm = info.Day;
    var sss = new PersianCalendar();
    int ys = sss.GetYear(new DateTime(ym, mm, dm, new GregorianCalendar()));
    int ms = sss.GetMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));
    int ds = sss.GetDayOfMonth(new DateTime(ym, mm, dm, new GregorianCalendar()));    
    if (includeHourMinute)
    {
        @(ys + separator + ms.ToString("00") + separator + ds.ToString("00") + " " + info.Hour + ":" + info.Minute)
    }
    else
    {
        @(ys + separator + ms.ToString("00") + separator + ds.ToString("00"))
    }
}

@if (@Model.HasValue)
{
  @ShamsiDateTime(@Model.Value , separator: "/", includeHourMinute: false)
}

مطالب
CheckBoxList در ASP.NET MVC

ASP.NET MVC به همراه HtmlHelper توکاری جهت نمایش یک ChekBoxList نیست؛ اما سیستم Model binder آن، این نوع کنترل‌ها را به خوبی پشتیبانی می‌کند. برای مثال، یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. سپس یک کنترلر Home جدید را نیز به آن اضافه کنید. در ادامه، برای متد Index آن، یک View خالی را ایجاد نمائید. سپس محتوای این View را به نحو زیر تغییر دهید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{
<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

و کنترلر Home را نیز مطابق کدهای زیر ویرایش کنید:
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

یک breakpoint را در تابع Index دوم که آرایه‌ای را دریافت می‌کند، قرار دهید. سپس برنامه را اجرا کرده، تعدادی از checkboxها را انتخاب و فرم نمایش داده شده را به سرور ارسال کنید:


بله. همانطور که ملاحظه می‌کنید، تمام عناصر ارسالی انتخاب شده که دارای نامی مشابه بوده‌اند، به یک آرایه قابل بایند هستند و سیستم model binder می‌داند که چگونه باید این اطلاعات را دریافت و پردازش کند.
از این مقدمه می‌توان به عنوان پایه و اساس نوشتن یک HtmlHelper سفارشی CheckBoxList استفاده کرد.
برای این منظور یک پوشه جدید را به نام app_code، به ریشه پروژه اضافه نمائید. سپس یک فایل خالی را به نام Helpers.cshtml نیز به آن اضافه کنید. محتوای این فایل را به نحو زیر تغییر دهید:

@helper CheckBoxList(string name, List<System.Web.Mvc.SelectListItem> items)
{
<div class="checkboxList">
@foreach (var item in items)
{
@item.Text
<input type="checkbox" name="@name"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
< br />
}
</div>
}

و برای استفاده از آن، کنترلر Home را مطابق کدهای زیر ویرایش کنید:

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new List<SelectListItem>
{
new SelectListItem { Text = "Item1", Value = "Val1", Selected = false },
new SelectListItem { Text = "Item2", Value = "Val2", Selected = false },
new SelectListItem { Text = "Item3", Value = "Val3", Selected = true }
};
return View();
}

[HttpPost]
public ActionResult GetTags(string[] tags)
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

و در این حالت View برنامه به شکل زیر درخواهد آمد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{

<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

@using (Html.BeginForm(actionName: "GetTags", controllerName: "Home"))
{
@Helpers.CheckBoxList("Tags", (List<SelectListItem>)ViewBag.Tags)
<input type="submit" value="submit" />
}

با توجه به اینکه کدهای Razor قرار گرفته در پوشه خاص app_code در ریشه سایت، به صورت خودکار در حین اجرای برنامه کامپایل می‌شوند، متد Helpers.CheckBoxList در تمام Viewهای برنامه در دسترس خواهد بود. در این متد، یک نام و لیستی از SelectListItemها دریافت می‌گردد. سپس به صورت خودکار یک CheckboxList را تولید خواهد کرد. برای دریافت مقادیر ارسالی آن به سرور هم باید مطابق متد GetTags تعریف شده در کنترلر Home عمل کرد. در اینجا Value عناصر انتخابی به صورت آرایه‌ای از رشته‌ها در دسترس خواهد بود.

روشی جامع‌تر
در آدرس زیر می‌توانید یک HtmlHelper بسیار جامع را جهت تولید CheckBoxList در ASP.NET MVC بیابید. در همان صفحه روش استفاده از آن، به همراه چندین مثال ارائه شده است:
https://github.com/devnoob/MVC3-Html.CheckBoxList-custom-extension

مطالب
EF Code First #6

ادامه بررسی Fluent API جهت تعریف نگاشت کلاس‌ها به بانک اطلاعاتی

در قسمت‌های قبل با استفاده از متادیتا و data annotations جهت بررسی نحوه نگاشت اطلاعات کلاس‌ها به جداول بانک اطلاعاتی آشنا شدیم. اما این موارد تنها قسمتی از توانایی‌های Fluent API مهیا در EF Code first را ارائه می‌دهند. یکی از دلایل آن هم به محدود بودن توانایی‌های ذاتی Attributes بر می‌گردد. برای مثال حین کار با Attributes امکان استفاده از متغیرها یا lambda expressions و امثال آن وجود ندارد. به علاوه شاید عده‌ای علاقمند نباشند تا کلاس‌های خود را با data annotations شلوغ کنند.

در قسمت دوم این سری، مروری مقدماتی داشتیم بر Fluent API. در آنجا ذکر شد که امکان تعریف نگاشت‌ها به کمک توانایی‌های Fluent API به دو روش زیر میسر است:
الف) می‌توان از متد protected override void OnModelCreating در کلاس مشتق شده از DbContext کار را شروع کرد.
ب) و یا اگر بخواهیم کلاس Context برنامه را شلوغ نکنیم بهتر است به ازای هر کلاس مدل برنامه، یک کلاس mapping مشتق شده از EntityTypeConfiguration را تعریف نمائیم. سپس می‌توان این کلاس‌ها را در متد OnModelCreating یاد شده، توسط متد modelBuilder.Configurations.Add جهت استفاده و اعمال، معرفی کرد.

کلاس‌های مدلی را که در این قسمت بررسی خواهیم کرد، همان کلاس‌های User و Project قسمت سوم هستند و هدف این قسمت بیشتر تطابق Fluent API با اطلاعات ارائه شده در قسمت سوم است؛ برای مثال در اینجا چگونه باید از خاصیتی صرفنظر کرد، مسایل همزمانی را اعمال نمود و امثال آن.
بنابراین یک پروژه جدید کنسول را آغاز نمائید. سپس با کمک NuGet ارجاعات لازم را به اسمبلی‌های EF اضافه نمائید.
در پوشه Models این پروژه، سه کلاس تکمیل شده زیر، از قسمت سوم وجود دارند:
using System;
using System.Collections.Generic;

namespace EF_Sample03.Models
{
public class User
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public string LastName { set; get; }

public string FullName
{
get { return Name + " " + LastName; }
}

public string Email { set; get; }
public string Description { set; get; }
public byte[] Photo { set; get; }
public IList<Project> Projects { set; get; }
public byte[] RowVersion { set; get; }
public InterestComponent Interests { set; get; }

public User()
{
Interests = new InterestComponent();
}
}
}

using System;

namespace EF_Sample03.Models
{
public class Project
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Title { set; get; }
public string Description { set; get; }
public virtual User User { set; get; }
public byte[] RowVesrion { set; get; }
}
}

namespace EF_Sample03.Models
{
public class InterestComponent
{
public string Interest1 { get; set; }
public string Interest2 { get; set; }
}
}


سپس یک پوشه جدید به نام Mappings را به پروژه اضافه نمائید. به ازای هر کلاس فوق، یک کلاس جدید را جهت تعاریف اطلاعات نگاشت‌ها به کمک Fluent API اضافه خواهیم کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;

namespace EF_Sample03.Mappings
{
public class InterestComponentConfig : ComplexTypeConfiguration<InterestComponent>
{
public InterestComponentConfig()
{
this.Property(x => x.Interest1).HasMaxLength(450);
this.Property(x => x.Interest2).HasMaxLength(450);
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;

namespace EF_Sample03.Mappings
{
public class ProjectConfig : EntityTypeConfiguration<Project>
{
public ProjectConfig()
{
this.Property(x => x.Description).IsMaxLength();
this.Property(x => x.RowVesrion).IsRowVersion();
}
}
}

using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample03.Mappings
{
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.ToTable("tblUser", schemaName: "guest");
this.Property(p => p.AddDate).HasColumnName("CreateDate").HasColumnType("date").IsRequired();
this.Property(x => x.Name).HasMaxLength(450);
this.Property(x => x.LastName).IsMaxLength().IsConcurrencyToken();
this.Property(x => x.Email).IsFixedLength().HasMaxLength(255); //nchar(128)
this.Property(x => x.Photo).IsOptional();
this.Property(x => x.RowVersion).IsRowVersion();
this.Ignore(x => x.FullName);
}
}
}

توضیحاتی در مورد کلاس‌های تنظیمات نگاشت‌های خواص به جداول و فیلدهای بانک اطلاعاتی

نظم بخشیدن به تعاریف نگاشت‌ها
همانطور که ملاحظه می‌کنید، جهت نظم بیشتر پروژه و شلوغ نشدن متد OnModelCreating کلاس Context برنامه، که در ادامه کدهای آن معرفی خواهد شد، به ازای هر کلاس مدل، یک کلاس تنظیمات نگاشت‌ها را اضافه کرده‌ایم.
کلاس‌های معمولی نگاشت‌ها ازکلاس EntityTypeConfiguration مشتق خواهند شد و جهت تعریف کلاس InterestComponent به عنوان Complex Type، اینبار از کلاس ComplexTypeConfiguration ارث بری شده است.

تعیین طول فیلدها
در کلاس InterestComponentConfig، به کمک متد HasMaxLength، همان کار ویژگی MaxLength را می‌توان شبیه سازی کرد که در نهایت، طول فیلد nvarchar تشکیل شده در بانک اطلاعاتی را مشخص می‌کند. اگر نیاز است این فیلد nvarchar از نوع max باشد، نیازی به تنظیم خاصی نداشته و حالت پیش فرض است یا اینکه می‌توان صریحا از متد IsMaxLength نیز برای معرفی nvarchar max استفاده کرد.

تعیین مسایل همزمانی
در قسمت سوم با ویژگی‌های ConcurrencyCheck و Timestamp آشنا شدیم. در اینجا اگر نوع خاصیت byte array بود و نیاز به تعریف آن به صورت timestamp وجود داشت، می‌توان از متد IsRowVersion استفاده کرد. معادل ویژگی ConcurrencyCheck در اینجا، متد IsConcurrencyToken است.

تعیین کلید اصلی جدول
اگر پیش فرض‌های EF Code first مانند وجود خاصیتی به نام Id یا ClassName+Id رعایت شود، نیازی به کار خاصی نخواهد بود. اما اگر این قراردادها رعایت نشوند،‌ می‌توان از متد HasKey (که نمونه‌ای از آن‌را در کلاس UserConfig فوق مشاهده می‌کنید)، استفاده کرد.

تعیین فیلدهای تولید شده توسط بانک اطلاعاتی
به کمک متد HasDatabaseGeneratedOption،‌ می‌توان مشخص کرد که آیا یک فیلد Identity است و یا یک فیلد محاسباتی ویژه و یا هیچکدام.

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

تعیین نام و نوع سفارشی فیلدها
همچنین اگر نام فیلدها نیز باید از قراردادهای دیگری پیروی کنند، می‌توان آن‌ها را به صورت صریح توسط متد HasColumnName معرفی کرد. اگر نیاز است این خاصیت به نوع خاصی در بانک اطلاعاتی نگاشت شود، باید از متد HasColumnType کمک گرفت. برای مثال در اینجا بجای نوع datetime، از نوع ویژه date استفاده شده است.

معرفی فیلدها به صورت nchar بجای nvarchar
برای نمونه اگر قرار است هش کلمه عبور در بانک اطلاعاتی ذخیره شود، چون طول آن ثابت می‌باشد، توصیه شده‌است که بجای nvarchar از nchar برای تعریف آن استفاده شود. برای این منظور تنها کافی است از متد IsFixedLength استفاده شود. در این حالت طول پیش فرض 128 برای فیلد درنظر گرفته خواهد شد. بنابراین اگر نیاز است از طول دیگری استفاده شود، می‌توان همانند سابق از متد HasMaxLength کمک گرفت.
ضمنا این فیلدها همگی یونیکد هستند و با n شروع شده‌اند. اگر می‌خواهید از varchar یا char استفاده کنید، می‌توان از متد IsUnicode با پارامتر false استفاده کرد.

معرفی یک فیلد به صورت null پذیر در سمت بانک اطلاعاتی
استفاده از متد IsOptional، فیلد را در سمت بانک اطلاعاتی به صورت فیلدی با امکان پذیرش مقادیر null معرفی می‌کند.
البته در اینجا به صورت پیش فرض byte arrayها به همین نحو معرفی می‌شوند و تنظیم فوق صرفا جهت ارائه توضیحات بیشتر در نظر گرفته شد.

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


معرفی کلاس‌های تعاریف نگاشت‌ها به برنامه

استفاده از کلاس‌های Config فوق خودکار نیست و نیاز است توسط متد modelBuilder.Configurations.Add معرفی شوند:

using System.Data.Entity;
using System.Data.Entity.Migrations;
using EF_Sample03.Mappings;
using EF_Sample03.Models;

namespace EF_Sample03.DataLayer
{
public class Sample03Context : DbContext
{
public DbSet<User> Users { set; get; }
public DbSet<Project> Projects { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new InterestComponentConfig());
modelBuilder.Configurations.Add(new ProjectConfig());
modelBuilder.Configurations.Add(new UserConfig());

//modelBuilder.ComplexType<InterestComponent>();
//modelBuilder.Ignore<InterestComponent>();

base.OnModelCreating(modelBuilder);
}
}

public class Configuration : DbMigrationsConfiguration<Sample03Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample03Context context)
{
base.Seed(context);
}
}
}

در اینجا کلاس Context برنامه مثال جاری را ملاحظه می‌کنید؛ به همراه کلاس Configuration مهاجرت خودکار که در قسمت‌های قبل بررسی شد.
در متد OnModelCreating نیز می‌توان یک کلاس را از نوع Complex معرفی کرد تا برای آن در بانک اطلاعاتی جدول جداگانه‌ای تعریف نشود. اما باید دقت داشت که اینکار را فقط یکبار می‌توان انجام داد؛ یا توسط کلاس InterestComponentConfig و یا توسط متد modelBuilder.ComplexType. اگر هر دو با هم فراخوانی شوند، EF یک استثناء را صادر خواهد کرد.

و در نهایت، قسمت آغازین برنامه اینبار به شکل زیر خواهد بود که از آغاز کننده MigrateDatabaseToLatestVersion (قسمت چهارم این سری) نیز استفاده کرده است:

using System;
using System.Data.Entity;
using EF_Sample03.DataLayer;

namespace EF_Sample03
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample03Context, Configuration>());

using (var db = new Sample03Context())
{
var project1 = db.Projects.Find(1);
if (project1 != null)
{
Console.WriteLine(project1.Title);
}
}
}
}
}

ضمنا رشته اتصالی مورد استفاده تعریف شده در فایل کانفیک برنامه نیز به صورت زیر تعریف شده است:

<connectionStrings>
<clear/>
<add
name="Sample03Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
/connectionStrings>


در قسمت‌های بعد مباحث پیشرفته‌تری از تنظیمات نگاشت‌ها را به کمک Fluent API، بررسی خواهیم کرد. برای مثال روابط ارث بری، many-to-many و ... چگونه تعریف می‌شوند.


مطالب
پیاده سازی پروژه‌ای مبتنی بر CQRS و ES
در قسمت قبلی با معماری CQRS و Event Sourcing بصورت مختصر آشنا شدیم. برای درک بیشتر مطلب پیشین، احتیاج به پیاده سازی آن به صورت عملیاتی و نه فقط تئوری محض میباشد و در این مرحله قصد پیاده سازی این مدل را به ساده‌ترین صورت ممکن داریم.
برای مطالعه‌ی ادامه‌ی این مقاله، نیاز به آشنایی با مباحث مطرح شده در قسمت قبل وجود دارد. پس از توضیحات اضافه بر روی قسمت‌های زیر گذشته و فرض بر آن است که آشنایی با این قسمت‌ها وجود دارد.
از این مدل میتوان در زبان‌های مختلف برنامه نویسی و همچنین سیستم‌های مختلف اعم از وب اپلیکیشن و ... استفاده نمود. همچنین برای استفاده از این مدل نیاز قطعی به استفاده از فریم ورک خاصی نیست. در صورت نیاز میتوانید پیاده سازی سفارشی خاص خود را داشته باشید. اما برای ساده‌تر شدن و هرچه سریعتر شدن مراحل از فریمورک SimpleCqrs استفاده میکنیم. هر چند بر خلاف نامش امکانات فراوانی را در اختیار برنامه نویسان قرار میدهد و حتی در پروژه‌های واقعی نیز میتوان از آن استفاده نمود.
برای سریعتر شدن کار میخواهیم پیاده سازی این مدل را در یک پروژه‌ی Console انجام دهیم و همچنین پس از ایجاد، پکیج‌های زیر را نصب مینماییم:
Unity, SimpleCqrs, SimpleCqrs.Unity
میخواهیم طبق مراحل گفته شده‌ی در قسمت قبل، به پیاده سازی این مدل بپردازیم و هدف، اضافه کردن یک Account به سیستم خواهد بود.
ابتدا باید DomainObject مورد نظر نوشته شود:
using System;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class Account : AggregateRoot
    {
        public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }

        public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }

        public void OnAccountCreated(AccountCreatedEvent evt)
        {
            Id = evt.AggregateRootId;
        }
    }
}
نکته: میخواهیم عملیات اضافه کردن یک Account، با استفاده از دو event مربوطه به نام AccountCreatedEvent و مقدار دهی آن با استفاده از AccountNameSetEvent انجام شود.
eventهای فوق را در ادامه اضافه خواهیم داد (از توضیحات بیشتر صرفنظر شده و به مقاله‌ی قسمت قبل رجوع شود).
حال احتیاج به پیاده سازی Command مربوطه برای انجام وظیفه‌ی خود داریم که هدف آن، اضافه کردن یک Account  به سیستم مورد نظر میباشد.
فرض کنید برای اضافه شدن Account، پراپرتی‌های FirstName و LastName باید مقدار دهی شوند:
using SimpleCqrs.Commanding;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommand : ICommand
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

حال CommandHandler که وظیفه‌ی تفسیر کردن Command مربوطه را به عهده دارد، پیاده سازی خواهد شد:
using System;
using SimpleCqrs.Commanding;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommandHandler : CommandHandler<CreateAccountCommand>
    {
        private readonly IDomainRepository repository;

        public CreateAccountCommandHandler(IDomainRepository repository)
        {
            this.repository = repository;
        }

        public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid());
            account.SetName(command.FirstName, command.LastName);

            repository.Save(account);
        }
    }
}
نکته: از طریق account.SetName فراخوانی Event مربوطه انجام شده‌است و همچنین repository.Save به raise کردن EventHandler میپردازد.
event مربوط به اضافه شدن Account را به صورت زیر پیاده سازی مینماییم:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountCreatedEvent : DomainEvent { }
}
و همچنین event مربوط به مقدار دهی پراپرتی‌ها نیز به صورت زیر خواهد بود:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountNameSetEvent : DomainEvent
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
در این بخش، پیاده سازی EventHandler را خواهیم داشت. طبق مطلب پیشین هر Domain باید EventHnadler ی داشته باشد که از Event هایش ارث بری کرده و هر کدام از Event‌ها عملا در قسمت Handle مربوط به خودش پردازش خواهد شد.
using System.Linq;
using SimpleCqrs.Eventing;
using CqrsPattern.Cqrs.Db;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountEventHandler : IHandleDomainEvents<AccountCreatedEvent>,
                                             IHandleDomainEvents<AccountNameSetEvent>
    {
        private readonly FakeAccountTable accountTable;

        public AccountEventHandler(FakeAccountTable accountTable)
        {
            this.accountTable = accountTable;
        }

        public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
    }
}
نکته: از آنجاییکه پیاده سازی ذخیره کردن Account با استفاده از دو event فوق انجام شده، بعد از Raise شدن EventHandler هر دو متد Handle، وظیفه‌ی Command مربوطه را به عهده دارند (بنابراین وظیفه‌ی هر Command میتواند با استفاده از event‌های مختلفی انجام شود).
برای اینکه نخواهیم وارد فاز‌های مربوط به دیتابیس شویم، موقتا یک db به صورت fake شده را پیاده سازی مینماییم؛ به صورت زیر:
using System.Collections.Generic;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTable : List<FakeAccountTableRow>
    { }
}
using System;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTableRow
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

و همچنین نیاز به ServiceLocator برای نمونه گرفتن از RunTime ی که از آن ارث بری کرده است داریم (برای سادگی کار از الگوی ServiceLocator استفاده میکنیم، ServiceLocator جز Anti-Pattern  ها محسوب میشود و معمولا در پروژه‌های واقعی از آن استفاده نمیشود)
using SimpleCqrs;
using SimpleCqrs.Unity;

namespace CqrsPattern
{
    public class SampleRunTime : SimpleCqrsRuntime<UnityServiceLocator> { }
}
حال احتیاج به پیاده سازی قسمت Queryداریم به همراه ReadModel و سرویسی برای فراخوانی آن
using System;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReadModel
    {
        public string Name { get; set; }
        public Guid Id { get; set; }
    }
}
using CqrsPattern.Cqrs.Db;
using System.Collections.Generic;
using System.Linq;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReportReadService
    {
        private FakeAccountTable fakeAccountDb;

        public AccountReportReadService(FakeAccountTable fakeAccountDb)
        {
            this.fakeAccountDb = fakeAccountDb;
        }

        public IEnumerable<AccountReadModel> GetAccounts()
        {
            return from a in fakeAccountDb
                   select new AccountReadModel { Id = a.Id, Name = a.Name };
        }
    }
}

در قسمت Main نرم افزار نیاز به register کردن FakeTable خود داریم و همانطور که ملاحظه میکنید Command مورد نظر را نمونه سازی کرده و آن را روی CommandBus قرار میدهیم تا مراحل پیاده سازی شده در قسمت‌های فوق انجام شود و همچنین بعد از اتمام command ارسال شده از طریق Service مورد نظر اطلاعات ذخیره شده بازگردانی میشود
using System;
using SimpleCqrs.Commanding;
using CqrsPattern.Cqrs.Query;
using CqrsPattern.Cqrs.Command;

namespace CqrsPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var runtime = new SampleRunTime();

            runtime.Start();

            var fakeAccountTable = new FakeAccountTable();
            runtime.ServiceLocator.Register(fakeAccountTable);
            runtime.ServiceLocator.Register(new AccountReportReadService(fakeAccountTable));
            var commandBus = runtime.ServiceLocator.Resolve<ICommandBus>();

            var cmd = new CreateAccountCommand { FirstName = "Ali", LastName = "Kh" };

            commandBus.Send(cmd);

            var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();

            Console.WriteLine("Accounts in database");
            Console.WriteLine("####################");
            foreach (var account in accountReportReadModel.GetAccounts())
            {
                Console.WriteLine(" Id: {0} Name: {1}", account.Id, account.Name);
            }

            runtime.Shutdown();

            Console.ReadLine();
        }
    }
}
اینگونه کل عملیات‌های لازم انجام خواهد شد.

خلاصه:
1) Command مربوطه را نمونه سازی کرده و روی CommandBus قرار میدهیم.
2) CommandHandler فراخوانی شده و فانکشن Handle آن باعث نمونه سازی از AggregateRoot میشود.
public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid()); //line 1
            account.SetName(command.FirstName, command.LastName); //line 2
            repository.Save(account); //line 3
        }
در خط نخست Constructor کلاس Account باعث Apply شدن event مربوطه میشود.
public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }
و در خط دوم account.SetName  برای Apply شدن event مربوط به مقدار دهی property‌ها میباشد.
public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }
و همچنین در خط  سوم و پس از repository.Save باعث میشود event‌های pending شده Raise شده و توسط متد Handle مربوط به EventHandler پردازش شده و عملیات‌های زیر انجام شوند:
public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
رکورد مورد نظر ثبت شده و event بعدی، پراپرتی‌هایش را مقدار دهی مینماید  و بصورت InMemory درون FakeAccountTable ذخیره میشود (پر واضح است که در یک پروژه‌ی واقعی به جای ذخیره شدن در یک Collection باید درون دیتایس واقعی ذخیره سازی شود).
و پس از اتمام عملیات انجام شده، بصورت زیر در Main برنامه اطلاعات ذخیره شده بازگردانده خواهد شد:
var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();
var accounts = accountReportReadModel.GetAccounts();

در ادامه برای مطالعه بیشتر میتوان به Scale out کردن این سیستم و استفاده از فریمورک‌های  messaging چون Redis یا Kafka پرداخت و همچنین اعمال Load Balancing را در اینگونه سیستم‌ها انجام داد.
نکته: Cqrs-Pattern را میتوانید از اینجا clone نمایید
مطالب
عبارات باقاعده و نیاز به Timeout

یکبار سعی کنید مثال ساده زیر را اجرا کنید:

using System;
using System.Text.RegularExpressions;

namespace RegexLoop
{
class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"))
{
Console.WriteLine("Matched!");
}

var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (Regex.IsMatch(input, pattern))
{
Console.WriteLine("Matched!");
}
}
}
}


پس از اجرا، برنامه هنگ خواهد کرد یا به عبارتی برنامه در یک حلقه بی‌نهایت قرار می‌گیرد (در هر دو مثال؛ اطلاعات بیشتر و آنالیز کامل در اینجا). بنابراین نیاز به مکانیزمی امنیتی جهت محافظت در برابر این نوع ورودی‌ها وجود خواهد داشت؛ مثلا یک Timeout . اگر تا 2 ثانیه به جواب نرسیدیم، اجرای Regex متوقف شود. تا دات نت 4، چنین timeout ایی پیش بینی نشده؛ اما در دات نت 4 و نیم آرگومانی جهت تعریف حداکثر مدت زمان قابل قبول اجرای یک عبارت باقاعده در نظر گرفته شده است (^) و اگر در طی مدت زمان مشخص شده، کار انجام محاسبات به پایان نرسد، استثنای RegexMatchTimeoutException صادر خواهد شد.
خیلی هم خوب. به این ترتیب کسی نمی‌تونه با یک ورودی ویژه، CPU Usage سیستم رو تا مدت زمان نامحدودی به 100 درصد برساند و عملا استفاده از سیستم رو غیرممکن کنه.
اما تا قبل از دات نت 4 و نیم چکار باید کرد؟ روش کلی حل این مساله به این ترتیب است که باید اجرای Regex را به یک ترد دیگر منتقل کرد؛ اگر مدت اجرای عملیات، از زمان تعیین شده بیشتر گردید، آنگاه می‌شود ترد را Abort کرد و به عملیات خاتمه داد. روش پیاده سازی و نحوه استفاده از آن‌را در ادامه ملاحظه خواهید نمود:

using System;
using System.Text.RegularExpressions;
using System.Threading;

namespace RegexLoop
{
public static class TimedRunner
{
public static R RunWithTimeout<R>(Func<R> proc, TimeSpan duration)
{
using (var waitHandle = new AutoResetEvent(false))
{
var ret = default(R);
var thread = new Thread(() =>
{
ret = proc();
waitHandle.Set();
}) { IsBackground = true };
thread.Start();

bool timedOut = !waitHandle.WaitOne(duration, false);
waitHandle.Close();

if (timedOut)
{
try
{
thread.Abort();
}
catch { }
return default(R);
}
return ret;
}
}
}

class Program
{
static void Main(string[] args)
{
var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
if (TimedRunner.RunWithTimeout(
() => emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"),
TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}

var input = "The quick brown fox jumps";
var pattern = @"([a-z ]+)*!";
if (TimedRunner.RunWithTimeout(() => Regex.IsMatch(input, pattern), TimeSpan.FromSeconds(2)))
{
Console.WriteLine("Matched!");
}
}
}
}

اینبار به هر کدام از عبارات باقاعده 2 ثانیه زمان برای اتمام کار داده شده است. در غیراینصورت مقدار پیش فرض خروجی متد فراخوانی شده، بازگشت داده می‌شود که در اینجا false است.