مطالب
ساده سازی و بالا بردن سرعت عملیات Reflection با استفاده از Dynamic Proxy
فرض کنید یک چنین کلاسی طراحی شده‌است:
public class NestedClass
{
    private int _field2;
    public NestedClass()
    {
        _field2 = 12;
    }
}
 
public class MyClass
{
    private int _field1;
    private NestedClass _nestedClass;
 
    public MyClass()
    {
        _field1 = 1;
        _nestedClass = new NestedClass();
    }
 
    private string GetData()
    {
        return "Test";
    }
}
می‌خواهیم از طریق Reflection مقادیر فیلدها و متدهای مخفی آن‌را بخوانیم.
حالت متداول دسترسی به فیلد خصوصی آن از طریق Reflection، یک چنین شکلی را دارد:
var myClass = new MyClass();
 
var field1Obj = myClass.GetType().GetField("_field1", BindingFlags.NonPublic | BindingFlags.Instance);
if (field1Obj != null)
{
    Console.WriteLine(Convert.ToInt32(field1Obj.GetValue(myClass)));
}
و یا دسترسی به مقدار خروجی متد خصوصی آن، به نحو زیر است:
var getDataMethod = myClass.GetType().GetMethod("GetData", BindingFlags.NonPublic | BindingFlags.Instance);
if (getDataMethod != null)
{
    Console.WriteLine(getDataMethod.Invoke(myClass, null));
}
در اینجا دسترسی به مقدار فیلد مخفی NestedClass، شامل مراحل زیر است:
var nestedClassObj = myClass.GetType().GetField("_nestedClass", BindingFlags.NonPublic | BindingFlags.Instance);
if (nestedClassObj != null)
{
    var nestedClassFieldValue = nestedClassObj.GetValue(myClass);
    var field2Obj = nestedClassFieldValue.GetType()
        .GetField("_field2", BindingFlags.NonPublic | BindingFlags.Instance);
    if (field2Obj != null)
    {
        Console.WriteLine(Convert.ToInt32(field2Obj.GetValue(nestedClassFieldValue)));
    }
}
البته این مقدار کد فقط برای دسترسی به دو سطح تو در تو بود.

چقدر خوب بود اگر می‌شد بجای این همه کد، نوشت:
myClass._field1
myClass._nestedClass._field2
myClass.GetData()
نه؟!
برای این مشکل راه حلی معرفی شده‌است به نام Dynamic Proxy که در ادامه به معرفی آن خواهیم پرداخت.


معرفی Dynamic Proxy

Dynamic Proxy یکی از مفاهیم AOP است. به این معنا که توسط آن یک محصور کننده‌ی نامرئی، اطراف یک شیء تشکیل خواهد شد. از این غشای نامرئی عموما جهت مباحث ردیابی اطلاعات، مانند پروکسی‌های Entity framework، همانجایی که تشخیص می‌دهد کدام خاصیت به روز شده‌است یا خیر، استفاده می‌شود و یا این غشای نامرئی کمک می‌کند که در حین دسترسی به خاصیت یا متدی، بتوان منطق خاصی را در این بین تزریق کرد. برای مثال فرآیند تکراری logging سیستم را به این غشای نامرئی منتقل کرد و به این ترتیب می‌توان به کدهای تمیزتری رسید.
یکی دیگر از کاربردهای این محصور کننده یا غشای نامرئی، ساده سازی مباحث Reflection است که نمونه‌ای از آن در پروژه‌ی EntityFramework.Extended بکار رفته‌است.
در اینجا، کار با محصور سازی نمونه‌ای از کلاس مورد نظر با Dynamic Proxy شروع می‌شود. سپس کل عملیات Reflection فوق در همین چند سطر ذیل به نحوی کاملا عادی و طبیعی قابل انجام است:
 // Accessing a private field
dynamic myClassProxy = new DynamicProxy(myClass);
dynamic field1 = myClassProxy._field1;
Console.WriteLine((int)field1);
 
// Accessing a nested private field
dynamic field2 = myClassProxy._nestedClass._field2;
Console.WriteLine((int)field2);
 
// Accessing a private method
dynamic data = myClassProxy.GetData();
Console.WriteLine((string)data);
خروجی Dynamic Proxy از نوع dynamic دات نت 4 است. پس از آن می‌توان در اینجا هر نوع خاصیت یا متد دلخواهی را به شکل dynamic تعریف کرد و سپس به مقادیر آن‌ها دسترسی داشت.

بنابراین با استفاده از Dynamic Proxy فوق می‌توان به دو مهم دست یافت:
 1) ساده سازی و زیبا سازی کدهای کار با Reflection
 2) استفاده‌ی ضمنی از مباحث Fast Reflection. در کتابخانه‌ی Dynamic Proxy معرفی شده، دسترسی به خواص و متدها، توسط کدهای IL بهینه سازی شده‌است و در دفعات آتی کار با آن‌ها، دیگر شاهد سربار بالای Reflection نخواهیم بود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
DynamicProxyTests.zip
مطالب
LINQ to JSON به کمک JSON.NET
عموما از امکانات LINQ to JSON کتابخانه‌ی JSON.NET زمانی استفاده می‌شود که ورودی JSON تو در توی حجیمی را دریافت کرده‌اید اما قصد ندارید به ازای تمام موجودیت‌های آن یک کلاس معادل را جهت نگاشت به آن‌ها تهیه کنید و صرفا یک یا چند مقدار تو در توی آن جهت عملیات استخراج نهایی مدنظر است. به علاوه در اینجا LINQ to JSON واژه‌ی کلیدی dynamic را نیز پشتیبانی می‌کند.


همانطور که در تصویر مشخص است، خروجی‌های JSON عموما ترکیبی هستند از مقادیر، آرایه‌ها و اشیاء. هر کدام از این‌ها در LINQ to JSON به اشیاء JValue، JArray و JObject نگاشت می‌شوند. البته در حالت JObject هر عضو به یک JProperty و JValue تجزیه خواهد شد.
برای مثال آرایه [1,2] تشکیل شده‌است از یک JArray به همراه دو JValue که مقادیر آن‌را تشکیل می‌دهند. اگر مستقیما بخواهیم یک JArray را تشکیل دهیم می‌توان از شیء JArray استفاده کرد:
 var array = new JArray(1, 2, 3);
var arrayToJson = array.ToString();
و اگر یک JSON رشته‌ای دریافتی را داریم می‌توان از متد Parse مربوط به JArray کمک گرفت:
 var json = "[1,2,3]";
var jArray= JArray.Parse(json);
var val = (int)jArray[0];
خروجی JArray یک لیست از JTokenها است و با آن می‌توان مانند لیست‌های معمولی کار کرد.

در حالت کار با اشیاء، شیء JObject امکان تهیه اشیاء JSON ایی را دارا است که می‌تواند مجموعه‌ای از JPropertyها باشد:
 var jObject = new JObject(
new JProperty("prop1", "value1"),
new JProperty("prop2", "value2")
);
var jObjectToJson = jObject.ToString();
با JObject به صورت dynamic نیز می‌توان کار کرد:
 dynamic jObj = new JObject();
jObj.Prop1 = "value1";
jObj.Prop2 = "value2";
jObj.Roles = new[] {"Admin", "User"};
این روش بسیار شبیه است به حالتی که با اشیاء جاوا اسکریپتی در سمت کلاینت می‌توان کار کرد.
و حالت عکس آن توسط متد JObject.Parse قابل انجام است:
 var json = "{ 'prop1': 'value1', 'prop2': 'value2'}";
var jObj = JObject.Parse(json);
var val1 = (string)jObj["prop1"];

اکنون که با اجزای تشکیل دهنده‌ی LINQ to JSON آشنا شدیم، مثال ذیل را درنظر بگیرید:
 var array = @"[
{
  'prop1': 'value1',
  'prop2': 'value2'
},
{
  'prop1': 'test1',
  'prop2': 'test2'
}
]";
var objects = JArray.Parse(array);
var obj1 = objects.FirstOrDefault(token => (string) token["prop1"] == "value1");
خروجی JArray یا JObject از نوع IEnumerable است و بر روی آن‌ها می‌توان کلیه متدهای LINQ را فراخوانی کرد. برای مثال در اینجا اولین شیءایی که مقدار خاصیت prop1 آن مساوی value1 است، یافت می‌شود و یا می‌توان اشیاء را بر اساس مقدار خاصیتی مرتب کرده و سپس آن‌‌ها را بازگشت داد:
 var values = objects.OrderBy(token => (string) token["prop1"])
.Select(token => new {Value = (string) token["prop2"]})
.ToList();
امکان انجام sub queries نیز در اینجا پیش بینی شده‌است:
 var array = @"[
{
  'prop1': 'value1',
  'prop2': [1,2]
},
{
  'prop1': 'test1',
  'prop2': [1,2,3]
}
]";
var objects = JArray.Parse(array);
var objectContaining3 = objects.Where(token => token["prop2"].Any(v => (int)v == 3)).ToList();
در این مثال، خواص prop2 از نوع آرایه‌ای از اعداد صحیح هستند. با کوئری نوشته شده، اشیایی که خاصیت prop2 آن‌ها دارای عضو 3 است، یافت می‌شوند.
مطالب
OpenCVSharp #10
محاسبه و ترسیم Histogram تصاویر

هیستوگرام یک تصویر، توزیع میزان روشنایی آن تصویر را نمایش می‌دهد و در آن تعداد نقاط قسمت‌های روشن تصویر، ترسیم می‌شوند. محاسبه‌ی هیستوگرام تصاویر در حین دیباگ الگوریتم‌های پردازش تصویر، کاربرد زیادی دارند.
OpenCV به همراه متد توکاری است به نام cv::calcHist که قادر است هیستوگرام تعدادی آرایه را محاسبه کند و در C++ API آن قرار دارد. البته هدف اصلی این متد، انجام محاسبات مرتبط است و در اینجا قصد داریم این محاسبات را نمایش دهیم.


تغییر میزان روشنایی و وضوح تصاویر در OpenCV

همانطور که عنوان شد، کار هیستوگرام تصاویر، نمایش توزیع میزان روشنایی نقاط و اجزای آن‌ها است. بنابراین می‌توان جهت مشاهده‌ی تغییر هیستوگرام محاسبه شده با تغییر میزان روشنایی و وضوح تصویر، از متد ذیل کمک گرفت:
private static void updateBrightnessContrast(Mat src, Mat modifiedSrc, int brightness, int contrast)
{
    brightness = brightness - 100;
    contrast = contrast - 100;
 
    double alpha, beta;
    if (contrast > 0)
    {
        double delta = 127f * contrast / 100f;
        alpha = 255f / (255f - delta * 2);
        beta = alpha * (brightness - delta);
    }
    else
    {
        double delta = -128f * contrast / 100;
        alpha = (256f - delta * 2) / 255f;
        beta = alpha * brightness + delta;
    }
    src.ConvertTo(modifiedSrc, MatType.CV_8UC3, alpha, beta);
}
در اینجا src تصویر اصلی است. brightness و contrast، مقادیر میزان روشنایی و وضوح دریافتی از کاربر هستند. این مقادیر را می‌توان به متد ConvertTo ارسال کرد تا src را تبدیل به modifiedSrc نماید و وضوح و روشنایی آن‌را تغییر دهد.

پس از اینکه متد تغییر وضوح تصویر اصلی را تهیه کردیم، می‌توان به پنجره‌ی نمایش تصویر اصلی، دو tracker جهت دریافت brightness و contrast اضافه کرد و به این ترتیب امکان نمایش پویای تغییرات را مهیا نمود:
using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var sourceWindow = new Window("Source", image: src,
           flags: WindowMode.AutoSize | WindowMode.FreeRatio))
    {
        using (var histogramWindow = new Window("Histogram",
               flags: WindowMode.AutoSize | WindowMode.FreeRatio))
        {
            var brightness = 100;
            var contrast = 100;
 
            var brightnessTrackbar = sourceWindow.CreateTrackbar(
                    name: "Brightness", value: brightness, max: 200,
                    callback: pos =>
                    {
                        brightness = pos;
                        updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                    });
 
            var contrastTrackbar = sourceWindow.CreateTrackbar(
                name: "Contrast", value: contrast, max: 200,
                callback: pos =>
                {
                    contrast = pos;
                    updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                });
 
 
            brightnessTrackbar.Callback.DynamicInvoke(brightness);
            contrastTrackbar.Callback.DynamicInvoke(contrast);
 
            Cv2.WaitKey();
        }
    }
}
در اینجا src تصویر اصلی است. پنجره‌ی Source کار نمایش تصویر اصلی را به عهده دارد. همچنین به این پنجره، دو tracker اضافه شده‌اند تا کار دریافت مقادیر روشنایی و وضوح را از کاربر، مدیریت کنند.
پنجره‌ی دومی نیز به نام هیستوگرام در اینجا تعریف شده‌است. در این پنجره قصد داریم هیستوگرام تغییرات پویای تصویر اصلی را نمایش دهیم.



روش محاسبه‌ی هیستوگرام تصاویر و نمایش آن‌ها در OpenCVSharp

کدهای کامل محاسبه‌ی هیستوگرام تصویر اصلی تغییر یافته (modifiedSrc) و سپس نمایش آن‌را در پنجره‌ی histogramWindow، در ادامه ملاحظه می‌کنید:
private static void calculateHistogram1(Window histogramWindow, Mat src, Mat modifiedSrc)
{
    const int histogramSize = 64;
    int[] dimensions = { histogramSize }; // Histogram size for each dimension
    Rangef[] ranges = { new Rangef(0, histogramSize) }; // min/max
 
    using (var histogram = new Mat())
    {
        Cv2.CalcHist(
            images: new[] { modifiedSrc },
            channels: new[] { 0 },
            mask: null,
            hist: histogram,
            dims: 1,
            histSize: dimensions,
            ranges: ranges);
 
        using (var histogramImage = (Mat)(Mat.Ones(rows: src.Rows, cols: src.Cols, type: MatType.CV_8U) * 255))
        {
            // Scales and draws histogram
 
            Cv2.Normalize(histogram, histogram, 0, histogramImage.Rows, NormType.MinMax);
            var binW = Cv.Round((double)histogramImage.Cols / histogramSize);
 
            var color = Scalar.All(100);
 
            for (var i = 0; i < histogramSize; i++)
            {
                Cv2.Rectangle(histogramImage,
                    new Point(i * binW, histogramImage.Rows),
                    new Point((i + 1) * binW, histogramImage.Rows - Cv.Round(histogram.Get<float>(i))),
                    color,
                    -1);
            }
 
            histogramWindow.Image = histogramImage;
        }
    }
}
معادل متد cv::calcHist، متد Cv2.CalcHist در OpenCVSharp است. این متد آرایه‌ای از تصاویر را قبول می‌کند که در اینجا تنها قصد داریم با یک تصویر کار کنیم. به همین جهت آرایه‌های images، اندازه‌های آن‌ها و بازه‌های min/max این تصاویر تنها یک عضو دارند. خروجی این متد پارامتر hist آن است که توسط یک new Mat تامین شده‌است. مقدار dims به یک تنظیم شده‌است؛ زیرا در اینجا تنها قصد داریم شدت نقاط را اندازه گیری کنیم. پارامتر ranges مشخص می‌کند که مقادیر اندازه گیری شده باید در چه بازه‌ایی جمع آوری شوند.
پس از محاسبه‌ی هیستوگرام، یک تصویر خالی پر شده‌ی با عدد یک را توسط متد Mat.Ones ایجاد می‌کنیم. این تصویر به عنوان منبع تصویر هیستوگرام نمایش داده شده، مورد استفاده قرار می‌گیرد. سپس نیاز است اطلاعات محاسبه شده، در مقیاسی قرار گیرند که قابل نمایش باشد. به همین جهت با استفاده از متد Normalize، آن‌ها را در مقیاس و بازه‌ی ارتفاع تصویر، تغییر اندازه خواهیم داد. سپس به کمک متد مستطیل، خروجی آرایه هیستوگرام را در صفحه، با رنگ خاکستری مشخص شده توسط متد Scalar.All ترسیم خواهیم کرد.


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


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
کار با چندین نوع بانک اطلاعاتی متفاوت در Entity Framework Core
یکی از مزایای کار با ORMها، امکان تعویض نوع بانک اطلاعاتی برنامه، بدون نیازی به تغییری در کدهای برنامه است. برای مثال فرض کنید می‌خواهید با تغییر رشته‌ی اتصالی برنامه، یکبار از بانک اطلاعاتی SQL Server و بار دیگر از بانک اطلاعاتی کاملا متفاوتی مانند SQLite استفاده کنید. در این مطلب نکات استفاده‌ی از چندین نوع بانک اطلاعاتی متفاوت را در برنامه‌های مبتنی بر EF Core بررسی خواهیم کرد.


هر بانک اطلاعاتی باید Migration و Context خاص خودش را داشته باشد

تامین کننده‌ی بانک‌های اطلاعاتی مختلف، عموما تنظیمات خاص خودشان را داشته و همچنین دستورات SQL متفاوتی را نیز تولید می‌کنند. به همین جهت نمی‌توان از یک تک Context، هم برای SQLite و هم SQL Server استفاده کرد. به علاوه قصد داریم اطلاعات Migrations هر کدام را نیز در یک اسمبلی جداگانه قرار دهیم. در یک چنین حالتی EF نمی‌پذیرد که Context تولید کننده‌ی Migration، در اسمبلی دیگری قرار داشته باشد و باید حتما در همان اسمبلی Migration قرار گیرد. بنابراین ساختار پوشه بندی مثال جاری به صورت زیر خواهد بود:


- در پوشه‌ی EFCoreMultipleDb.DataLayer فقط اینترفیس IUnitOfWork را قرار می‌دهیم. از این جهت که وقتی قرار شد در برنامه چندین Context تعریف شوند، لایه‌ی سرویس برنامه قرار نیست بداند در حال حاضر با کدام Context کار می‌کند. به همین جهت است که تغییر بانک اطلاعاتی برنامه، تغییری را در کدهای اصلی آن ایجاد نخواهد کرد.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQLite را قرار می‌دهیم.
- در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQL Server را قرار می‌دهیم.

برای نمونه ابتدای Context مخصوص SQLite چنین شکلی را دارد:
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
        public SQLiteDbContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<User> Users { set; get; }
و IDesignTimeDbContextFactory مخصوص آن که برای Migrations از آن استفاده می‌شود، به صورت زیر تهیه خواهد شد:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContextFactory : IDesignTimeDbContextFactory<SQLiteDbContext>
    {
        public SQLiteDbContext CreateDbContext(string[] args)
        {
            var basePath = Directory.GetCurrentDirectory();
            Console.WriteLine($"Using `{basePath}` as the BasePath");
            var configuration = new ConfigurationBuilder()
                                    .SetBasePath(basePath)
                                    .AddJsonFile("appsettings.json")
                                    .Build();
            var builder = new DbContextOptionsBuilder<SQLiteDbContext>();
            var connectionString = configuration.GetConnectionString("SqliteConnection")
                                                .Replace("|DataDirectory|", Path.Combine(basePath, "wwwroot", "app_data"));
            builder.UseSqlite(connectionString);
            return new SQLiteDbContext(builder.Options);
        }
    }
}
هدف از این فایل، ساده سازی کار تولید اطلاعات Migrations برای EF Core است. به این صورت ساخت new SQLiteDbContext توسط ما صورت خواهد گرفت و دیگر EF Core درگیر جزئیات وهله سازی آن نمی‌شود.


تنظیمات رشته‌های اتصالی بانک‌های اطلاعاتی مختلف

در اینجا محتویات فایل appsettings.json را که در آن تنظیمات رشته‌های اتصالی دو بانک SQL Server LocalDB و همچنین SQLite در آن ذکر شده‌اند، مشاهده می‌کنید:
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SqlServerConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCoreSqlDB;AttachDbFilename=|DataDirectory|\\ASPNETCoreSqlDB.mdf;Integrated Security=True;MultipleActiveResultSets=True;",
    "SqliteConnection": "Data Source=|DataDirectory|\\ASPNETCoreSqliteDB.sqlite",
    "InUseKey": "SqliteConnection"
  }
}
همین رشته‌ی اتصالی است که در SQLiteDbContextFactory مورد استفاده قرار می‌گیرد.
یک کلید InUseKey را هم در اینجا تعریف کرده‌ایم تا مشخص باشد در ابتدای کار برنامه، کلید کدام رشته‌ی اتصالی مورد استفاده قرار گیرد. برای مثال در اینجا کلید رشته‌ی اتصالی SQLite تنظیم شده‌است.
در این تنظیمات یک DataDirectory را نیز مشاهده می‌کنید. مقدار آن در فایل Startup.cs برنامه به صورت زیر بر اساس پوشه‌ی جاری تعیین می‌شود و در نهایت به wwwroot\app_data اشاره خواهد کرد:
var connectionStringKey = Configuration.GetConnectionString("InUseKey");
var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));


دستورات تولید Migrations و به روز رسانی بانک اطلاعاتی

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

برای تولید Migrations، از طریق خط فرمان، به پوشه‌ی اسمبلی مدنظر وارد شده و دستور زیر را اجرا کنید:
For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b)
For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b)
dotnet build
dotnet ef migrations --startup-project ../EFCoreMultipleDb.Web/ add V%mydate%_%mytime% --context SQLiteDbContext
در اینجا ذکر startup-project و همچنین context برای پروژه‌هایی که context آن‌ها خارج از startup-project است و همچنین بیش از یک context دارند، ضروری‌است. بدیهی است این دستورات را باید یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLite و یکبار در پوشه‌ی EFCoreMultipleDb.DataLayer.SQLServer اجرا کنید.
دو سطر اول آن، زمان اجرای دستورات را به عنوان نام فایل‌ها تولید می‌کنند.

پس از تولید Migrations، اکنون نوبت به تولید بانک اطلاعاتی و یا به روز رسانی بانک اطلاعاتی موجود است:
dotnet build
dotnet ef --startup-project ../EFCoreMultipleDb.Web/ database update --context SQLServerDbContext
در این مورد نیز ذکر startup-project و همچنین context مدنظر ضروری است.


بدیهی است این رویه را پس از هربار تغییراتی در موجودیت‌های برنامه و یا تنظیمات آن‌ها در Contextهای متناظر، نیاز است مجددا اجرا کنید. البته اجرای اولین دستور اجباری است؛ اما می‌توان دومین دستور را به صورت زیر نیز اجرا کرد:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            applyPendingMigrations(app);
// ...
        }

        private static void applyPendingMigrations(IApplicationBuilder app)
        {
            var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                var uow = scope.ServiceProvider.GetService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
}
متد applyPendingMigrations، کار وهله سازی IUnitOfWork را انجام می‌دهد. سپس متد Migrate آن‌را اجرا می‌کند، تا تمام Migrations تولید شده، اما اعمال نشده‌ی به بانک اطلاعاتی، به صورت خودکار به آن اعمال شوند. متد Migrate نیز به صورت زیر تعریف می‌شود:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
    // ... 

        public void Migrate()
        {
            this.Database.Migrate();
        }
    }
}

مرحله‌ی آخر: انتخاب بانک اطلاعاتی در برنامه‌ی آغازین

پس از این تنظیمات، قسمتی که کار تعریف IUnitOfWork و همچنین DbContext جاری برنامه را انجام می‌دهد، به صورت زیر پیاده سازی می‌شود:
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUsersService, UsersService>();

            var connectionStringKey = Configuration.GetConnectionString("InUseKey");
            var connectionString = Configuration.GetConnectionString(connectionStringKey)
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
            switch (connectionStringKey)
            {
                case "SqlServerConnection":
                    services.AddScoped<IUnitOfWork, SQLServerDbContext>();
                    services.AddDbContext<SQLServerDbContext>(options =>
                    {
                        options.UseSqlServer(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                    dbOptions.EnableRetryOnFailure();
                                });
                    });
                    break;
                case "SqliteConnection":
                    services.AddScoped<IUnitOfWork, SQLiteDbContext>();
                    services.AddDbContext<SQLiteDbContext>(options =>
                    {
                        options.UseSqlite(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                });
                    });
                    break;
                default:
                    throw new NotImplementedException($"`{connectionStringKey}` is not defined in `appsettings.json` file.");
            }

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }
در اینجا ابتدا مقدار InUseKey از فایل تنظیمات برنامه دریافت می‌شود. بر اساس مقدار آن، رشته‌ی اتصالی مدنظر دریافت شده و سپس یکی از دو حالت SQLite و یا SQLServer انتخاب می‌شوند. برای مثال اگر Sqlite انتخاب شده باشد، IUnitOfWork به SQLiteDbContext تنظیم می‌شود. به این ترتیب لایه‌ی سرویس برنامه که با IUnitOfWork کار می‌کند، به صورت خودکار وهله‌ای از SQLiteDbContext را دریافت خواهد کرد.


آزمایش برنامه

ابتدا کدهای کامل این مطلب را از اینجا دریافت کنید: EFCoreMultipleDb.zip
سپس آن‌را اجرا نمائید. چنین تصویری را مشاهده خواهید کرد:


اکنون برنامه را بسته و سپس فایل appsettings.json را جهت تغییر مقدار InUseKey به کلید SqlServerConnection ویرایش کنید:
{
  "ConnectionStrings": {
    // …
    "InUseKey": "SqlServerConnection"
  }
}
اینبار اگر مجددا برنامه را اجرا کنید، چنین خروجی قابل مشاهده‌است:


مقدار username، در contextهای هر کدام از این بانک‌های اطلاعاتی، با مقدار متفاوتی به عنوان اطلاعات اولیه‌ی آن ثبت شده‌است. سرویسی هم که اطلاعات آن‌را تامین می‌کند، به صورت زیر تعریف شده‌است:
namespace EFCoreMultipleDb.Services
{
    public interface IUsersService
    {
        Task<User> FindUserAsync(int userId);
    }

    public class UsersService : IUsersService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<User> _users;

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

        public Task<User> FindUserAsync(int userId)
        {
            return _users.FindAsync(userId);
        }
    }
}
همانطور که مشاهده می‌کنید، با تغییر context برنامه، هیچ نیازی به تغییر کدهای UsersService نیست؛ چون اساسا این سرویس نمی‌داند که IUnitOfWork چگونه تامین می‌شود.
مطالب
خواندن اطلاعات از فایل اکسل با استفاده از LinqToExcel
در این مقاله مروری سریع و کاربردی خواهیم داشت بر توانایی‌های مقدماتی LinqToExcel
در ابتدا می‌بایست LinqToExcel را از طریق NuGet به پروژه افزود.
PM> Install-Package LinqToExcel
و یا از طریق solution Explorer گزینه Manage NuGet Packages 

اکنون فایل اکسل ذیل را در نظر بگیرید.

روش خواندن اطلاعات از فایل اکسل فوق تحت فرامین Linq و با مشخص کردن نام sheet مورد نظر  توسط شئ ExcelQueryFactory  بصورت زیر است.

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            var persons = from a in excel.Worksheet(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a["Name"]+" "+a["Family"]);
            }


در صورتیکه بخواهیم انتقال اطلاعات فایل اکسل به جداول بانک اطلاعاتی مانند Sql Server بطور مثال با روش EF Entity Framework را انجام دهیم کلاس زیر با نام person را فرض نمایید.

 public class Person
        {
            public string Name { get; set; }
            public string Family { get; set; }
        }
باید بدانید که بصورت پیشفرض سطر اول از فایل اکسل به عنوان نام ستون انتخاب می‌شود و می‌بایست جهت نگاشت با نام property‌های کلاس ما دقیقاً همنام باشد.

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
            var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            var persons = from a in excel.Worksheet<Person>(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a.Name+" "+a.Family);
            }
  اگر فایل اکسل ما ستون‌های بیشتری داشته باشد تنها ستونهای همنام با propertyهای کلاس ما به کلاس نگاشت پیدا می‌کند و سایر ستونها نادیده گرفته می‌شود.
در صورتیکه نام ستونهای فایل اکسل(سطر اول) با نام property‌های کلاس یکسان نباشد جهت نگاشت آنها در کلاس می‌توان از متد AddMapping استفاده نمود.
 

 string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
            var excel = new ExcelQueryFactory(pathToExcelFile);
            string sheetName = "Sheet1";
            excel.AddMapping("Name","نام");
            excel.AddMapping("Family", "نام خانوادگی");
            var persons = from a in excel.Worksheet<Person>(sheetName) select a;
            foreach (var a in persons)
            {
                MessageBox.Show(a.Name+" "+a.Family);
            }

در کدهای بالا در صورتی که sheetName قید نشود بصورت پیشفرض Sheet1 از فایل اکسل  انتخاب می‌شود.

var persons = from a in excel.Worksheet<Person>() select a;
همچنین می‌توان از اندیس جهت مشخص نمودن Sheet مورد نظر استفاده نمود که اندیس‌ها از صفر شروع می‌شوند.

var persons = from a in excel.Worksheet<Person>(0) select a;
توسط متد GetWorksheetNames می توان نام sheet‌ها را بدست آورد.

public IEnumerable<string> getWorkSheets()
{
string pathToExcelFile = @"C:\Users\MASOUD\Desktop\ExcelFile.xlsx";
    
    var excel = new ExcelQueryFactory(pathToExcelFile);

    return excel.GetWorksheetNames();
}
و توسط متد GetColumnNames   می توان نام ستونها را بدست آورد.  

var SheetColumnNames = excel.GetColumnNames(sheetName);
همانطور که می‌بینید با روش توضیح داده شده در این مقاله به راحتی از فرامین Linq مانند where می‌توان در انتخاب اطلاعات از فایل اکسل استفاده نمود و سپس نتیجه را به جداول مورد نظر انتقال داد.
مطالب
ASP.NET MVC #10

آشنایی با روش‌های مختلف ارسال اطلاعات یک درخواست به کنترلر

تا اینجا با روش‌های مختلف ارسال اطلاعات از یک کنترلر به View متناظر آن آشنا شدیم. اما حالت عکس آن چطور؟ مثلا در ASP.NET Web forms، دوبار بر روی یک دکمه کلیک می‌کردیم و در روال رویدادگردان کلیک آن، همانند برنامه‌های ویندوزی، دسترسی به اطلاعات اشیاء قرار گرفته بر روی فرم را داشتیم. در ASP.NET MVC که کلا مفهوم Events را حذف کرده و وب را همانگونه که هست ارائه می‌دهد و به علاوه کنترلرهای آن، ارجاع مستقیمی را به هیچکدام از اشیاء بصری در خود ندارند (برای مثال کنترلر و متدی در آن نمی‌دانند که الان بر روی View آن، یک گرید قرار دارد یا یک دکمه یا اصلا هیچی)، چگونه می‌توان اطلاعاتی را از کاربر دریافت کرد؟
در اینجا حداقل سه روش برای دریافت اطلاعات از کاربر وجود دارد:
الف) استفاده از اشیاء Context مانند HttpContext، Request، RouteData و غیره
ب) به کمک پارامترهای اکشن متدها
ج) با استفاده از ویژگی جدیدی به نام Data Model Binding

یک مثال کاربردی
قصد داریم یک صفحه لاگین ساده را طراحی کنیم تا بتوانیم هر سه حالت ذکر شده فوق را در عمل بررسی نمائیم. بحث HTML Helpers استاندارد ASP.NET MVC را هم که در قسمت قبل شروع کردیم، لابلای توضیحات قسمت جاری و قسمت‌های بعدی با مثال‌های کاربردی دنبال خواهند شد.
بنابراین یک پروژه جدید خالی ASP.NET MVC را شروع کرده و مدلی را به نام Account با محتوای زیر به پوشه Models برنامه اضافه کنید:

namespace MvcApplication6.Models
{
public class Account
{
public string Name { get; set; }
public string Password { get; set; }
}
}

یک کنترلر جدید را هم به نام LoginController به پوشه کنترلرهای برنامه اضافه کنید. بر روی متد Index پیش فرض آن کلیک راست نمائید و یک View خالی را اضافه نمائید.
در ادامه به فایل Global.asax.cs مراجعه کرده و نام کنترلر پیش‌فرض را به Login تغییر دهید تا به محض شروع برنامه در VS.NET، صفحه لاگین ظاهر شود.
کدهای کامل کنترلر لاگین را در ادامه ملاحظه می‌کنید:

using System.Web.Mvc;
using MvcApplication6.Models;

namespace MvcApplication6.Controllers
{
public class LoginController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(); //Shows the login page
}

[HttpPost]
public ActionResult LoginResult()
{
string name = Request.Form["name"];
string password = Request.Form["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
[ActionName("LoginResultWithParams")]
public ActionResult LoginResult(string name, string password)
{
if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

[HttpPost]
public ActionResult Login(Account account)
{
if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}
}
}

همچنین Viewهای متناظر با این کنترلر هم به شرح زیر هستند:
فایل index.cshtml به نحو زیر تعریف خواهد شد:

@model MvcApplication6.Models.Account
@{
ViewBag.Title = "Index";
}
<h2>
Login</h2>
@using (Html.BeginForm(actionName: "LoginResult", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "LoginResultWithParams", controllerName: "Login"))
{
<fieldset>
<legend>Test LoginResult(string name, string password)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}
@using (Html.BeginForm(actionName: "Login", controllerName: "Login"))
{
<fieldset>
<legend>Test Login(Account acc)</legend>
<p>
Name: @Html.TextBoxFor(m => m.Name)</p>
<p>
Password: @Html.PasswordFor(m => m.Password)</p>
<input type="submit" value="Login" />
</fieldset>
}

و فایل result.cshtml هم محتوای زیر را دارد:

@{
ViewBag.Title = "Result";
}
<fieldset>
<legend>Login Result</legend>
<p>
@ViewBag.Message</p>
</fieldset>

توضیحاتی در مورد View لاگین برنامه:
در View صفحه لاگین سه فرم را مشاهده می‌کنید. در برنامه‌های ASP.NET Web forms در هر صفحه، تنها یک فرم را می‌توان تعریف کرد؛ اما در ASP.NET MVC این محدودیت برداشته شده است.
تعریف یک فرم هم با متد کمکی Html.BeginForm انجام می‌شود. در اینجا برای مثال می‌شود یک فرم را به کنترلری خاص و متدی مشخص در آن نگاشت نمائیم.
از عبارت using هم برای درج خودکار تگ بسته شدن فرم، در حین dispose شیء MvcForm کمک گرفته شده است.
برای نمونه خروجی HTML اولین فرم تعریف شده به صورت زیر است:

<form action="/Login/LoginResult" method="post">   
<fieldset>
<legend>Test LoginResult()</legend>
<p>
Name: <input id="Name" name="Name" type="text" value="" /></p>
<p>
Password: <input id="Password" name="Password" type="password" /></p>
<input type="submit" value="Login" />
</fieldset>
</form>

توسط متدهای کمکی Html.TextBoxFor و Html.PasswordFor یک TextBox و یک PasswordBox به صفحه اضافه می‌شوند، اما این For آن‌ها و همچنین lambda expression ایی که بکارگرفته شده برای چیست؟
متدهای کمکی Html.TextBox و Html.Password از نگارش‌های اولیه ASP.NET MVC وجود داشتند. این متدها نام خاصیت‌ها و پارامترهایی را که قرار است به آن‌ها بایند شوند، به صورت رشته می‌پذیرند. اما با توجه به اینکه در اینجا می‌توان یک strongly typed view را تعریف کرد،‌ تیم ASP.NET MVC بهتر دیده است که این رشته‌ها را حذف کرده و از قابلیتی به نام Static reflection استفاده کند (^ و ^).

با این توضیحات، اطلاعات سه فرم تعریف شده در View لاگین برنامه، به سه متد متفاوت قرار گرفته در کنترلری به نام Login ارسال خواهند شد. همچنین با توجه به مشخص بودن نوع model که در ابتدای فایل تعریف شده، خاصیت‌هایی را که قرار است اطلاعات ارسالی به آن‌ها بایند شوند نیز به نحو strongly typed تعریف شده‌اند و تحت نظر کامپایلر خواهند بود.


توضیحاتی در مورد نحوه عملکرد کنترلر لاگین برنامه:

در این کنترلر صرفنظر از محتوای متدهای آن‌ها، دو نکته جدید را می‌توان مشاهده کرد. استفاده از ویژگی‌های HttpPost، HttpGet و ActionName. در اینجا به کمک ویژگی‌های HttpGet و HttpPost در مورد نحوه دسترسی به این متدها، محدودیت قائل شده‌ایم. به این معنا که تنها در حالت Post است که متد LoginResult در دسترس خواهد بود و اگر شخصی نام این متدها را مستقیما در مرورگر وارد کند (یا همان HttpGet پیش فرض که نیازی هم به ذکر صریح آن نیست)، با پیغام «یافت نشد» مواجه می‌گردد.
البته در نگارش‌های اولیه ASP.NET MVC از ویژگی دیگری به نام AcceptVerbs برای مشخص سازی نوع محدودیت فراخوانی یک اکشن متد استفاده می‌شد که هنوز هم معتبر است. برای مثال:

[AcceptVerbs(HttpVerbs.Get)]

یک نکته امنیتی:
همیشه متدهای Delete خود را به HttpPost محدود کنید. به این علت که ممکن است در طی مثلا یک ایمیل، آدرسی به شکل http://localhost/blog/delete/10 برای شما ارسال شود و همچنین سشن کار با قسمت مدیریتی بلاگ شما نیز در همان حال فعال باشد. URL ایی به این شکل، در حالت پیش فرض، محدودیت اجرایی HttpGet را دارد. بنابراین احتمال اجرا شدن آن بالا است. اما زمانیکه متد delete را به HttpPost محدود کردید، دیگر این نوع حملات جواب نخواهند داد و حتما نیاز خواهد بود تا اطلاعاتی به سرور Post شود و نه یک Get ساده (مثلا کلیک بر روی یک لینک معمولی)، کار حذف را انجام دهد.


توسط ActionName می‌توان نام دیگری را صرفنظر از نام متد تعریف شده در کنترلر، به آن متد انتساب داد که توسط فریم ورک در حین پردازش نهایی مورد استفاده قرار خواهد گرفت. برای مثال در اینجا به متد LoginResult دوم، نام LoginResultWithParams را انتساب داده‌ایم که در فرم دوم تعریف شده در View لاگین برنامه مورد استفاده قرار گرفته است.
وجود این ActionName هم در مثال فوق ضروری است. از آنجائیکه دو متد هم نام را معرفی کرده‌ایم و فریم ورک نمی‌داند که کدامیک را باید پردازش کند. در این حالت (بدون وجود ActionName معرفی شده)، برنامه با خطای زیر مواجه می‌گردد:

The current request for action 'LoginResult' on controller type 'LoginController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult LoginResult() on type MvcApplication6.Controllers.LoginController
System.Web.Mvc.ActionResult LoginResult(System.String, System.String) on type MvcApplication6.Controllers.LoginController

برای اینکه بتوانید نحوه نگاشت فرم‌ها به متدها را بهتر درک کنید، بر روی چهار return View موجود در کنترلر لاگین برنامه، چهار breakpoint را تعریف کنید. سپس برنامه را در حالت دیباگ اجرا نمائید و تک تک فرم‌ها را یکبار با کلیک بر روی دکمه لاگین، به سرور ارسال نمائید.


بررسی سه روش دریافت اطلاعات از کاربر در ASP.NET MVC

الف) استفاده از اشیاء Context

در ویژوال استودیو، در کنترلر لاگین برنامه، بر روی کلمه Controller کلیک راست کرده و گزینه Go to definition را انتخاب کنید. در اینجا بهتر می‌توان به خواصی که در یک کنترلر به آن‌ها دسترسی داریم، نگاهی انداخت:

public HttpContextBase HttpContext { get; }
public HttpRequestBase Request { get; }
public HttpResponseBase Response { get; }
public RouteData RouteData { get; }

در بین این خواص و اشیاء مهیا، Request و RouteData بیشتر مد نظر ما هستند. در مورد RouteData در قسمت ششم این سری، توضیحاتی ارائه شد. اگر مجددا Go to definition مربوط به HttpRequestBase خاصیت Request را بررسی کنیم، موارد ذیل جالب توجه خواهند بود:

public virtual NameValueCollection QueryString { get; } // GET variables
public NameValueCollection Form { get; } // POST variables
public HttpCookieCollection Cookies { get; }
public NameValueCollection Headers { get; }
public string HttpMethod { get; }

توسط خاصیت Form شیء Request می‌توان به مقادیر ارسالی به سرور در یک کنترلر دسترسی یافت که نمونه‌ای از آن‌را در اولین متد LoginResult می‌توانید مشاهده کنید. این روش در ASP.NET Web forms هم کار می‌کند. جهت اطلاع این روش با ASP کلاسیک دهه نود هم سازگار است!
البته این روش آنچنان مرسوم نیست؛ چون NameValueCollection مورد استفاده، ایندکسی عددی یا رشته‌ای را می‌پذیرد که هر دو با پیشرفت‌هایی که در زبان‌های دات نتی صورت گرفته‌اند، دیگر آنچنان مطلوب و روش مرجح به حساب نمی‌آیند. اما ... هنوز هم قابل استفاده است.
به علاوه اگر دقت کرده باشید در اینجا HttpContextBase داریم بجای HttpContext. تمام این کلاس‌های پایه هم به جهت سهولت انجام آزمون‌های واحد در ASP.NET MVC ایجاد شده‌اند. کار کردن مستقیم با HttpContext مشکل بوده و نیاز به شبیه سازی فرآیندهای رخ داده در یک وب سرور را دارد. اما این کلاس‌های پایه جدید، مشکلات یاد شده را به همراه ندارند.


ب) استفاده از پارامترهای اکشن متدها

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

Request.Form
Request.QueryString
Request.Files
RouteData.Values

برای نمونه در متدی که با نام LoginResultWithParams مشخص شده، چون نام‌های دو پارامتر آن، با نام‌های بکارگرفته شده در Html.TextBoxFor و Html.PasswordFor یکی هستند، با مقادیر ارسالی آن‌ها مقدار دهی شده و سپس در متد قابل استفاده خواهند بود. در پشت صحنه هم از همان رکوردهای موجود در Request.Form (یا سایر موارد ذکر شده)، استفاده می‌شود. در اینجا هر رکورد مثلا مجموعه Request.Form، کلیدی مساوی نام ارسالی به سرور را داشته و مقدار آن هم، مقداری است که کاربر وارد کرده است.
اگر همانندی یافت نشد، آن پارامتر با نال مقدار دهی می‌گردد. بنابراین اگر برای مثال یک پارامتر از نوع int را معرفی کرده باشید و چون نوع int، نال نمی‌پذیرد، یک استثناء بروز خواهد کرد. برای حل این مشکل هم می‌توان از Nullable types استفاده نمود (مثلا بجای int id نوشت int? id تا مشکلی جهت انتساب مقدار نال وجود نداشته باشد).
همچنین باید دقت داشت که این بررسی تطابق‌های بین نام عناصر HTML و نام پارامترهای متدها، case insensitive است و به کوچکی و بزرگی حروف حساس نیست. برای مثال، پارامتر معرفی شده در متد LoginResult مساوی string name است، اما نام خاصیت تعریف شده در کلاس Account مساوی Name بود.


ج) استفاده از ویژگی جدیدی به نام Data Model Binding

در ASP.NET MVC چون می‌توان با یک Strongly typed view کار کرد، خود فریم ورک این قابلیت را دارد که اطلاعات ارسالی یکی فرم را به صورت خودکار به یک وهله از یک شیء نگاشت کند. در اینجا model binder وارد عمل می‌شود، مقادیر ارسالی را استخراج کرده (اطلاعات دریافتی از Form یا کوئری استرینگ‌ها یا اطلاعات مسیریابی و غیره) و به خاصیت‌های یک شیء نگاشت می‌کند. بدیهی است در اینجا این خواص باید عمومی باشند و هم نام عناصر HTML ارسالی به سرور. همچنین model binder پیش فرض ASP.NET MVC را نیز می‌توان کاملا تعویض کرد و محدود به استفاده از model binder توکار آن نیستیم.
وجود این Model binder، کار با ORMها را بسیار لذت بخش می‌کند؛ از آنجائیکه خود فریم ورک ASP.NET MVC می‌تواند عناصر شیءایی را که قرار است به بانک اطلاعاتی اضافه شود، یا در آن به روز شود، به صورت خودکار ایجاد کرده یا به روز رسانی نماید.
نحوه کار با model binder را در متد Login کنترلر فوق می‌توانید مشاهده کنید. بر روی return View آن یک breakpoint قرار دهید. فرم سوم را به سرور ارسال کنید و سپس در VS.NET خواص شیء ساخته شده را در حین دیباگ برنامه، بررسی نمائید.
بنابراین تفاوتی نمی‌کند که از چندین پارامتر استفاده کنید یا اینکه کلا یک شیء را به عنوان پارامتر معرفی نمائید. فریم ورک سعی می‌کند اندکی هوش به خرج داده و مقادیر ارسالی به سرور را به پارامترهای تعریفی، حتی به خواص اشیاء این پارامترهای تعریف شده، نگاشت کند.

در ASP.NET MVC سه نوع Model binder وجود دارند:
1) Model binder پیش فرض که توضیحات آن به همراه مثالی ارائه شد.
2) Form collection model binder که در ادامه توضیحات آن‌را مشاهده خواهید نمود.
3) HTTP posted file base model binder که توضیحات آن به قسمت بعدی موکول می‌شود.

یک نکته:
اولین متد LoginResult کنترلر را به نحو زیر نیز می‌توان بازنویسی کرد:
[HttpPost]
[ActionName("LoginResultWithFormCollection")]
public ActionResult LoginResult(FormCollection collection)
{
string name = collection["name"];
string password = collection["password"];

if (name == "Vahid" && password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

در اینجا FormCollection به صورت خودکار بر اساس مقادیر ارسالی به سرور توسط فریم ورک تشکیل می‌شود (FormCollection هم یک نوع model binder ساده است) و اساسا یک NameValueCollection می‌باشد.
بدیهی است در این حالت باید نگاشت مقادیر دریافتی، به متغیرهای متناظر با آن‌ها، دستی انجام شود (مانند مثال فوق) یا اینکه می‌توان از متد UpdateModel کلاس Controller هم استفاده کرد:

[HttpPost]
public ActionResult LoginResultUpdateFormCollection(FormCollection collection)
{
var account = new Account();
this.UpdateModel(account, collection.ToValueProvider());

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

متد توکار UpdateModel، به صورت خودکار اطلاعات FormCollection دریافتی را به شیء مورد نظر، نگاشت می‌کند.
همچنین باید عنوان کرد که متد UpdateModel، در پشت صحنه از اطلاعات Model binder پیش فرض و هر نوع Model binder سفارشی که ایجاد کنیم استفاده می‌کند. به این ترتیب زمانیکه از این متد استفاده می‌کنیم، اصلا نیازی به استفاده از FormCollection نیست و متد بدون آرگومان زیر هم به خوبی کار خواهد کرد:

[HttpPost]
public ActionResult LoginResultUpdateModel()
{
var account = new Account();
this.UpdateModel(account);

if (account.Name == "Vahid" && account.Password == "123")
ViewBag.Message = "Succeeded";
else
ViewBag.Message = "Failed";

return View("Result");
}

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

مطالب
پیاده سازی الگوی طراحی Memento

Memento یک الگوی طراحی مفید و ساده است که برای ذخیره و بازیابی state یک object استفاده می‌شود. در بعضی از مقالات از آن به عنوان snapshot نیز یاد شده است! اگر با git  کار کرده باشید، این مفهوم را می‌توان در git بسیار یافت؛ هر commit به عنوان یک snapshot میباشد که میتوان به صورت مکرر آن را undo کرد و یا مثال خیلی ساده‌تر میتوان به ctrl+z در سیستم عامل اشاره کرد.

به مثال زیر توجه کنید:

Int temp;
Int a=1;
temp=a;
a=2;
.
.
a=temp;

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

int main()
{
  MyClass One = new MyClass();
  MyClass Temp = new MyClass();
  // Set an initial value.
  One.Value = 10;
  One.Name = "Ten";
  // Save the state of the value.
  Temp.Value = One.Value;
  Temp.Name = One.Name;
  // Change the value.
  One.Value = 99;
  One.Name = "Ninety Nine";
  // Undo and restore the state.
  One.Value = Temp.Value;
  One.Name = Temp.Name;
}

در کد بالا با استفاده از یک temp، شیء مورد نظر را ذخیره کرده و در آخر مجدد داده‌ها را درون شیء، restore  میکنیم.


 از مشکلات کد بالا میتوان گفت :

۱- برای هر object باید یک شیء temp ایجاد کنیم.

۲- ممکن است بخواهیم که حالات یک object را بر روی هارد ذخیره کنیم. با روش فوق کدها خیلی پیچیده‌تر خواهند شد.

۳- نوشتن کد به این سبک برای پروژه‌های بزرگ، پیچیده و مدیریت آن سخت‌تر می‌شود.


پیاده سازی memento

ما این مثال را در قالب یک پروژه NET Core  onsole. ایجاد میکنیم. برای این کار یک پوشه‌ی جدید را ایجاد و درون ترمینال دستور زیر را وارد کنید:

dotnet new console

روش‌های زیادی برای پیاده سازی memento وجود دارند. برای پیاده سازی memento ابتدا یک abstract class را به شکل زیر ایجاد میکنیم: 

abstract class MementoBase
{
  protected Guid mementoKey = Guid.NewGuid();
  abstract public void SaveMemento(Memento memento);
  abstract public void RestoreMemento(Memento memento);
}

اگر به کلاس بالا دقت کنید، این کلاس قرار است parent کلاس‌های دیگری باشد که داری دو متد SaveMemento و RestoreMemento برای ذخیره و بازیابی و همچنین یک Guid برای نگهداری state‌های مختلف میباشد.

ورودی متدها از نوع memento میباشد. پس کلاس memento را به شکل زیر ایجاد می‌کنیم:

class Memento
{
    private Dictionary<Guid, object> stateList = new Dictionary<Guid, object>();
    public object GetState(Guid key)
    {
        return stateList[key];
    }
    public void SetState(Guid key, object newState)
    {
        stateList[key] = newState;
    }
    public Memento()
    {
    }
}

در کد بالا با یک Dictionary می‌توان هر object را با کلیدش ذخیره کنیم. توجه کنید که value دیکشنری از نوع object میباشد و چون object پدر تمام object‌های دیگر است پس می‌توانیم هر نوع داده‌ای را در آن ذخیره کنیم. تا اینجا، Memento پیاده سازی شده است. میتوان این کار را با جنریک‌ها نیز پیاده سازی کرد.

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

class ConcreteOriginator : MementoBase
{
  private int value = 0;
  public ConcreteOriginator(int newValue)
  {
    SetData(newValue);
  }
  public void SetData(int newValue)
  {
    value = newValue;
  }
  public void Speak()
  {
    Console.WriteLine("My value is " + value.ToString());
  }
  public override void SaveMemento(Memento memento)
  {
    memento.SetState(mementoKey, value);
  }
  public override void RestoreMemento(Memento memento)
  {
    int restoredValue = (int)memento.GetState(mementoKey);
    SetData(restoredValue);
  }
}

کلاس ConcreteOriginator از کلاس MementoBase ارث بری کرده و دو متد RestoreMemento و SaveMemento را پیاده سازی میکند و همچنین دارای یک مشخصه value می‌باشد. برای خروجی گرفتن، متد main را به صورت زیر پیاده سازی می‌کنیم:

static void Main(string[] args)
{
  Memento memento = new Memento();
  // Create an originator, which will hold our state data.
  ConcreteOriginator myOriginator = new ConcreteOriginator("Hello World!", StateType.ONE);
  ConcreteOriginator anotherOriginator = new ConcreteOriginator("Hola!", StateType.ONE);
  ConcreteOriginator2 thirdOriginator = new ConcreteOriginator2(7);
  // Set some state data.
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  // Save the states into our memento.
  myOriginator.SaveMemento(memento);
  anotherOriginator.SaveMemento(memento);
  thirdOriginator.SaveMemento(memento);
  // Now change our originators' states.
  myOriginator.SetData("Goodbye!", StateType.TWO);
  anotherOriginator.SetData("Adios!", StateType.TWO);
  thirdOriginator.SetData(99);
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  // Restore our originator's state.
  myOriginator.RestoreMemento(memento);
  anotherOriginator.RestoreMemento(memento);
  thirdOriginator.RestoreMemento(memento);
  myOriginator.Speak();
  anotherOriginator.Speak();
  thirdOriginator.Speak();
  Console.ReadKey();
}
تا خط ۱۲، مراحل عادی کد نویسی را پیش رفته‌ایم. در خطوط ۱۳ تا ۱۵، داده را در Memento ذخیره میکنیم. در خطوط ۱۷ تا ۱۹، داده‌های اشیاء را با استفاده از متد SetData عوض میکنیم. در خطوط ۲۰ تا ۲۲ با متد Speak، مقدار value را نمایش میدهیم و در خطوط ۲۴ تا ۲۶، داده‌ها را Restore میکنیم و در آخر دوباره مقدار value را نمایش میدهیم.
برنامه را اجرا کنید .خروجی به شکل زیر خواهد بود:
Hello World! I'm in state ONE
Hola! I'm in state ONE
My value is 7
Goodbye! I'm in state TWO
Adios! I'm in state TWO
My value is 99
Hello World! I'm in state ONE
Hola! I'm in state ONE
My value is 7
نظرات اشتراک‌ها
روش‌های مقابله با مشکل امنیتی Mass Assignment در ASP.NET Core
راه حل دیگر: استفاده از روش Containment بجای Inheritance
public class UserModel
{
    [MaxLength(200)]
    [Display(Name = "Full name")]
    [Required]
    public string Name { get; set; }
}

public class UserModalViewModel
{
    public UserModel Model { get; set; }
    public bool IsAdmin { get; set; }
    public IReadonlyList<lookupitem> Roles { get; set; }
}
‌‌‌
اکشن متد متناظر با درخواست GET
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
    var user = await _service.FindAsync(id); //return Maybe<UserModel>
    if (!user.HasValue)
    {
        return NotFound();
    }

    // prepare model
    var model = new UserModalViewModel
    {
        Model = user.Value,
        IsAdmin = true,
        Roles = await _lookupService.ReadRolesAsync()
    };
    return View(model);
}

‌‌‌‌
اکشن متد متناظر با درخواست POST
[HttPost]
public async Task<IActionResult> Edit([Bind(Prefix = "Model")] UserModel model)
{
    //todo: check ModelState and save model
    await _service.EditAsync(model);
}

مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت چهارم

همانطور که قول داده بودم، به اصول GRASP می‌پردازیم.

اصول GRASP-General Responsibility Assignment Software Principles

این اصول به بررسی نحوه تقسیم وظایف بین کلاس‌ها و مشارکت اشیاء برای به انجام رساندن یک مسئولیت می‌پردازند. اینکه هر کلاس در ساختار نرم افزار چه وظیفه‌ای دارد و چگونه با کلاس‌های دیگر مشارکت میکند تا یک عملکرد به سیستم اضافه گردد. این اصول به چند بخش تقسیم می­شوند:

  • کنترلر ( Controller )
  • ایجاد کننده ( Creator )
  • انسجام قوی ( High Cohesion )
  • واسطه گری ( Indirection )
  • دانای اطلاعات ( Information Expert )
  • اتصال ضعیف ( Low Coupling )
  • چند ریختی ( Polymorphism )
  • حفاظت از تاثیر تغییرات ( Protected Variations )
  • مصنوع خالص ( Pure Fabrication )

 

Controller

این الگو بیان می‌کند که مسئولیت پاسخ به رویداد‌های (Events ) یک سناریوی محدود مانند یک مورد کاربردی ( Use Case ) باید به عهده یک کلاس غیر UI باشد. کنترلر باید کارهایی را که نیاز است در پاسخ رویداد انجام شود، به دیگران بسپرد و نتایج را طبق درخواست رویداد بازگرداند. در اصل، کنترلر دریافت کننده رویداد، راهنمای مسیر پردازش برای پاسخ به رویداد و در نهایت برگرداننده پاسخ به سمت مبداء رویداد است. در زیر مثالی را می‌بینیم که رویداد اتفاق افتاده توسط واسط گرافیکی به سمت یک handler (که متدی است با ورودیِ فرستنده و آرگمانهای مورد نیاز) در کنترلر فرستاده میشود. این روش event handling، در نمونه‌های وب فرم و ویندوز فرم دیده میشود. به صورتی خود کلاس‌های .Net وظیفه Event Raising از سمت UI با کلیک روی دکمه را انجام میدهد: 

 public class UserController
 {        
        protected void OnClickCreate(object sender, EventArgs e)
        {
           // call validation services
           // call create user services
        }
 }


در مثال بعد عملیات مربوط به User در یک WebApiController پاسخ داده میشود. در اینجا به جای استفاده از Event Raising برای کنترل کردن رویداد، از فراخوانی یک متد در کنترلر توسط درخواست HttpPost انجام میگیرد. در اینجا نیاز است که در سمت کلاینت درخواستی را ارسال کنیم:

    public class UserWebApiController
    {
        [HttpPost]
        public HttpResponseMessage Create(UserViewModel user)
        {
            // call validation services
            // call create user services
        }
    }



Creator :

  این اصل میگوید شیء ای میتواند یک شیء دیگر را بسازد ( instantiate ) که: (اگر کلاس B بخواهد کلاس A را instantiate کند)

  • کلاس B شیء از کلاس A را در خود داشته باشد؛
  • یا اطلاعات کافی برای instantiate کردن از A را داشته باشد؛
  • یا به صورت نزدیک با A در ارتباط باشد؛
  • یا بخواهد شیء A را ذخیره کند.

از آنجایی که این اصل بدیهی به نظر میرسد، با مثال نقض، درک بهتری را نسبت به آن میتوان پیدا کرد:

    // سازنده
    public class B
    {
        public static A CreateA(string name, string lastName, string job)
        {
            return new A() {
                Name =name,
                LastName = lastName,
                Job = job
            };
        }
    }
    // ایجاد شونده
    public class A
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public string Job { get; set; }
    }

    public class Context
    {
        public void Main()
        {
            var name = "Rasoul";
            var lastName = "Abbasi";
            var job = "Developer";            
            var obj = B.CreateA(name, lastName, job);
        }
    }


و اما چرا این مثال، اصل Creator را نقض میکند. در مثال میبینید که کلاس B، یک شیء از نوع A را در متد Main کلاس Context ایجاد میکند. کلاس B فقط یک متد برای تولید A دارد و در عملیات تولید A هیچ منطق خاصی را پیاده سازی نمیکند.کلاس B شیء ای را از کلاس A ، در خود ندارد، با آن ارتباط نزدیک ندارد و آنرا ذخیره نمیکند. با اینکه کلاس B اطلاعات کافی را برای تولید A از ورودی میگیرد، ولی این کلاس Context است که اطلاعات کافی را ارسال مینماید. اگر در کلاس B منطقی اضافه بر instance گیریِ ساده وجود داشت (مانند بررسی صحت و اعتبار سنجی)، میتوانستیم بگوییم کلاس B از یک مجموعه عملیات instance گیری با خبر است که کلاس Context  نباید از آن خبر داشته باشد. لذا اکنون هیچ دلیلی وجود ندارد که وظیفه تولید A را در Context انجام ندهیم و این مسئولیت را به کلاس B منتقل کنیم. این مورد ممکن است در ذهن شما با الگوی Factory تناقض داشته باشد. ولی نکته اصلی در الگو Factory انجام عملیات instance گیری با توجه به منطق برنامه است؛ یعنی وظیفه‌ای که کلاس Context نباید از آن خبر داشته باشد را به کلاس Factory منتقل میکنیم. در غیر اینصورت ایجاد کلاس Factory بی معنا خواهد بود (مگر به عنوان افزایش انعطاف پذیری معماری که بتوان به راحتی نوع پیاده سازی یک واسط را تغییر داد).


High Cohesion :

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

در مثال زیر نقض این اصل را مشاهده میکنیم:

    class Controller
    {
        public void CreateProduct(string name, int categoryId) { }
        public void EditProduct(int id, string name) { }
        public void DeleteProduct(int id) { }
        public void CreateCategory(string name) { }
        public void EditCategory(int id, string name) { }
        public void DeleteCategory(int id) { }
    }  

همانطور که میبینید، کلاس کنترلر ما، مسئولیت مدیریت Product و Category را بر عهده دارد. بزرگ شدن این کلاس، باعث سخت‌تر شدن خواندن کد و رفع اشکال میگردد. با جداسازی کنترلر مربوط به Product از Category میتوان انسجام را بالا برد.


Indirection :

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

الگوهای Adapter و Delegate و همچنین نقش کنترلر در الگوی معماری MVC از این اصل پیروی میکنند. 

    class SenderA
    {
        public Mediator mediator { get; }
        public SenderA() { mediator = new Mediator(); }
        public void Send(string message, string reciever) { mediator.Send(message, reciever); }
    }
    class SenderB
    {
        public Mediator mediator { get; }
        public SenderB() { mediator = new Mediator(); }
        public void Send(string message) { }
    }

    public class RecieverA
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "create":
                    break;
                case "delete":
                    break;
                default:
                    break;
            }
        }
    }
    public class RecieverB
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "edit":
                    break;
                case "rollback":
                    break;
                default:
                    break;
            }
        }
    }
    class Mediator
    {
        internal void Send(string message, string reciever)
        {
            switch (reciever)
            {
                case "A":
                    var recieverObjA = new RecieverA();
                    recieverObjA.DoAction(message);
                    break;
                case "B":
                    var recieverObjB = new RecieverB();
                    recieverObjB.DoAction(message);
                    break;

                default:
                    break;
            }
        }
    }
    class IndirectionContext
    {
        public void Main()
        {
            var senderA = new SenderA();
            senderA.Send("rollback", "B");
            var senderB = new SenderA();
            senderB.Send("create", "A");

        }
    }

در این مثال کلاس Mediator به عنوان واسط ارتباطی بین کلاس‌های Sender و Receiver قرار گرفته و نقش تحویل پیغام را دارد.

در مقاله بعدی، به بررسی سایر اصول GRASP خواهم پرداخت.

مطالب
MongoDb در سی شارپ (بخش هفتم)
در اینجا قصد داریم که دیتاهای استاتیک و دیتاهای پویا را بررسی کنیم. همانطور که میدانید مونگو تنها خواصی را که در کلاس وجود دارند ذخیره میکند و همان‌ها را هم در برگشت به کلاس انتساب میدهد. ولی ممکن است برای بعضی از اسناد هر بار فیلدهایی را تعریف کنیم که در کلاس اصلی پراپرتی برای آن وجود ندارد. فیلدهایی که ممکن است در زمان اجرا آن‌ها را بشناسیم. برای این کار دو روش متفاوت توسط تیم فنی مونگو پیشنهاد شده است.
اولین روش این است که یک پراپرتی را مثلا به عنوان متادیتا به کلاس اضافه و در قالب کلید و مقدار آن‌ها را مقدار دهی کنیم:
public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
 در مدل Book، یک فیلد را به نام ExtraField اضافه میکنیم و نوع آن را BsonDocument میگذاریم . آن را به شکل زیر مقدار دهی می‌کنیم:
     var book = new Book()
            {
      
                Title = "Gone With Wind",
                ISBN = "43442424",
                Price = 50000,
                Year = 1936,
                LastStock = DateTime.Now.AddDays(-13),
                Language = new Language()
                {
                    Name = "Persian"
                },
                Authors = new List<Author>()
               {
                   new Author()
                   {
                       Name = "Margaret Mitchell"
                   }
               },
                ExtraFields=new BsonDocument("Translator", "Ali Mahboobi")
            };
در اینجا ما یک فیلد را اضافه کرد‌ه‌ایم که نام آن Translator بوده و مقدارش را علی محبوبی گذاشتیم. اگر به سند ذخیره شده‌ی آن نگاهی بیندازیم می‌بینیم که این دیتا به شکل زیر ذخیره شده است:

همینطور که می‌بینید این فیلد جدید به عنوان یک شیء جدا یا یک سند توکار ذخیره شده‌است. ولی اگر میخواهید به عنوان یک فیلد، همانند دیگر فیلدها ذخیره شود، باید فیلد ExtraField را به ویژگی BsonExtraElement مزین کنید. پس مدل را به شکل زیر بازنویسی میکنیم:

public class Book:Entity
    {
        public string Title { get; set; }
        public string ISBN { get; set; }
        public int Price { get; set; }
        public List<Author> Authors { get; set; }
        [BsonExtraElements]
        public BsonDocument ExtraFields { get; set; }
        public Language Language { get; set; }
        public ObjectId Image { get; set; }
        public int Year { get; set; }
        public DateTime LastStock { get; set; }

    }
حال اگر مقادیر ذخیره شده را بررسی کنیم، باید شکل زیر را ببینید:


الان translator همانند دیگر فیلدها به یک شکل نمایش داده میشود.
در این روش فقط تیم مونگو اخطار داده است که مراقب باشید قبلا فیلدی به این نام نبوده باشد تا بعدا دچار مشکل و تصادم شود.