مطالب
معماری لایه بندی نرم افزار #3

Service Layer

نقش لایه‌ی سرویس این است که به عنوان یک مدخل ورودی به برنامه کاربردی عمل کند. در برخی مواقع این لایه را به عنوان لایه‌ی Facade نیز می‌شناسند. این لایه، داده‌ها را در قالب یک نوع داده ای قوی (Strongly Typed) به نام View Model، برای لایه‌ی Presentation فراهم می‌کند. کلاس View Model یک Strongly Typed محسوب می‌شود که نماهای خاصی از داده‌ها را که متفاوت از دید یا نمای تجاری آن است، بصورت بهینه ارائه می‌نماید. در مورد الگوی View Model در مباحث بعدی بیشتر صحبت خواهم کرد.

الگوی Facade یک Interface ساده را به منظور کنترل دسترسی به مجموعه ای از Interface‌ها و زیر سیستم‌های پیچیده ارائه می‌کند. در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.

کلاسی با نام ProductViewModel را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductViewModel
{
    Public int ProductId {get; set;}
    public string Name { get; set; }
    public string Rrp { get; set; }
    public string SellingPrice { get; set; }
    public string Discount { get; set; }
    public string Savings { get; set; }
}

برای اینکه کلاینت با لایه‌ی سرویس در تعامل باشد باید از الگوی Request/Response Message استفاده کنیم. بخش Request توسط کلاینت تغذیه می‌شود و پارامترهای مورد نیاز را فراهم می‌کند. کلاسی با نام ProductListRequest را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.Service
{
    public class ProductListRequest
    {
        public CustomerType CustomerType { get; set; }
    }
}

در شی Response نیز بررسی می‌کنیم که درخواست به درستی انجام شده باشد، داده‌های مورد نیاز را برای کلاینت فراهم می‌کنیم و همچنین در صورت عدم اجرای صحیح درخواست، پیام مناسب را به کلاینت ارسال می‌نماییم. کلاسی با نام ProductListResponse را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductListResponse
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public IList<ProductViewModel> Products { get; set; }
}

به منظور تبدیل موجودیت Product به ProductViewModel، به دو متد نیاز داریم، یکی برای تبدیل یک Product و دیگری برای تبدیل لیستی از Product. شما می‌توانید این دو متد را به کلاس Product موجود در Domain Model اضافه نمایید، اما این متدها نیاز واقعی منطق تجاری نمی‌باشند. بنابراین بهترین انتخاب، استفاده از Extension Method‌ها می‌باشد که باید برای کلاس Product و در لایه‌ی سرویس ایجاد نمایید. کلاسی با نام ProductMapperExtensionMethods را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public static class ProductMapperExtensionMethods
{
    public static ProductViewModel ConvertToProductViewModel(this Model.Product product)
    {
        ProductViewModel productViewModel = new ProductViewModel();
        productViewModel.ProductId = product.Id;
        productViewModel.Name = product.Name;
        productViewModel.RRP = String.Format(“{0:C}”, product.Price.RRP);
        productViewModel.SellingPrice = String.Format(“{0:C}”, product.Price.SellingPrice);
        if (product.Price.Discount > 0)
            productViewModel.Discount = String.Format(“{0:C}”, product.Price.Discount);
        if (product.Price.Savings < 1 && product.Price.Savings > 0)
            productViewModel.Savings = product.Price.Savings.ToString(“#%”);
        return productViewModel;
    }
    public static IList<ProductViewModel> ConvertToProductListViewModel(
        this IList<Model.Product> products)
    {
        IList<ProductViewModel> productViewModels = new List<ProductViewModel>();
        foreach(Model.Product p in products)
        {
            productViewModels.Add(p.ConvertToProductViewModel());
        }
        return productViewModels;
    }
}

حال کلاس ProductService را جهت تعامل با کلاس سرویس موجود در Domain Model و به منظور برگرداندن لیستی از محصولات و تبدیل آن به لیستی از ProductViewModel، ایجاد می‌نماییم. کلاسی با نام ProductService را با کد زیر به پروژه SoCPatterns.Layered.Service اضافه کنید:

public class ProductService
{
    private Model.ProductService _productService;
    public ProductService(Model.ProductService ProductService)
    {
        _productService = ProductService;
    }
    public ProductListResponse GetAllProductsFor(
        ProductListRequest productListRequest)
    {
        ProductListResponse productListResponse = new ProductListResponse();
        try
        {
            IList<Model.Product> productEntities =
                _productService.GetAllProductsFor(productListRequest.CustomerType);
            productListResponse.Products = productEntities.ConvertToProductListViewModel();
            productListResponse.Success = true;
        }
        catch (Exception ex)
        {
            // Log the exception…
            productListResponse.Success = false;
            // Return a friendly error message
            productListResponse.Message = ex.Message;
        }
        return productListResponse;
    }
}

کلاس Service تمامی خطاها را دریافت نموده و پس از مدیریت خطا، پیغامی مناسب را به کلاینت ارسال می‌کند. همچنین این لایه محل مناسبی برای Log کردن خطاها می‌باشد. در اینجا کد نویسی لایه سرویس به پایان رسید و در ادامه به کدنویسی Data Layer می‌پردازیم.

Data Layer

برای ذخیره سازی محصولات، یک بانک اطلاعاتی با نام Shop01 ایجاد کنید که شامل جدولی به نام Product با ساختار زیر باشد:

برای اینکه کدهای بانک اطلاعاتی را سریعتر تولید کنیم از روش Linq to SQL در Data Layer استفاده می‌کنم. برای این منظور یک Data Context برای Linq to SQL به این لایه اضافه می‌کنیم. بر روی پروژه SoCPatterns.Layered.Repository کلیک راست نمایید و گزینه Add > New Item را انتخاب کنید. در پنجره ظاهر شده و از سمت چپ گزینه Data و سپس از سمت راست گزینه Linq to SQL Classes را انتخاب نموده و نام آن را Shop.dbml تعیین نمایید.

از طریق پنجره Server Explorer به پایگاه داده مورد نظر متصل شوید و با عمل Drag & Drop جدول Product را به بخش Design کشیده و رها نمایید.

اگر به یاد داشته باشید، در لایه Model برای برقراری ارتباط با پایگاه داده از یک Interface به نام IProductRepository استفاده نمودیم. حال باید این Interface را پیاده سازی نماییم. کلاسی با نام ProductRepository را با کد زیر به پروژه SoCPatterns.Layered.Repository اضافه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SoCPatterns.Layered.Model;

namespace SoCPatterns.Layered.Repository
{
    public class ProductRepository : IProductRepository
    {
        public IList<Model.Product> FindAll()
        {
            var products = from p in new ShopDataContext().Products
                                select new Model.Product
                                {
                                    Id = p.ProductId,
                                    Name = p.ProductName,
                                    Price = new Model.Price(p.Rrp, p.SellingPrice)
                                };
            return products.ToList();
        }
    }
}

در متد FindAll، با استفاده از دستورات Linq to SQL، لیست تمامی محصولات را برگرداندیم. کدنویسی لایه‌ی Data هم به پایان رسید و در ادامه به کدنویسی لایه‌ی Presentation و UI می‌پردازیم.

Presentation Layer

به منظور جداسازی منطق نمایش (Presentation) از رابط کاربری (User Interface)، از الگوی Model View Presenter یا همان MVP استفاده می‌کنیم که در مباحث بعدی با جزئیات بیشتری در مورد آن صحبت خواهم کرد. یک Interface با نام IProductListView را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:

using SoCPatterns.Layered.Service;

public interface IProductListView
{
    void Display(IList<ProductViewModel> Products);
    Model.CustomerType CustomerType { get; }
    string ErrorMessage { set; }
}

این Interface توسط Web Form‌های ASP.NET و یا Win Form‌ها باید پیاده سازی شوند. کار با Interface‌ها موجب می‌شود تا تست View‌ها به راحتی انجام شوند. کلاسی با نام ProductListPresenter را با کد زیر به پروژه SoCPatterns.Layered.Presentation اضافه کنید:

using SoCPatterns.Layered.Service;

namespace SoCPatterns.Layered.Presentation
{
    public class ProductListPresenter
    {
        private IProductListView _productListView;
        private Service.ProductService _productService;
        public ProductListPresenter(IProductListView ProductListView,
            Service.ProductService ProductService)
        {
            _productService = ProductService;
            _productListView = ProductListView;
        }
        public void Display()
        {
            ProductListRequest productListRequest = new ProductListRequest();
            productListRequest.CustomerType = _productListView.CustomerType;
            ProductListResponse productResponse =
                _productService.GetAllProductsFor(productListRequest);
            if (productResponse.Success)
            {
                _productListView.Display(productResponse.Products);
            }
            else
            {
                _productListView.ErrorMessage = productResponse.Message;
            }
        }
    }
}

کلاس Presenter وظیفه‌ی واکشی داده ها، مدیریت رویدادها و بروزرسانی UI را دارد. در اینجا کدنویسی لایه‌ی Presentation به پایان رسیده است. از مزایای وجود لایه‌ی Presentation این است که تست نویسی مربوط به نمایش داده‌ها و تعامل بین کاربر و سیستم به سهولت انجام می‌شود بدون آنکه نگران دشواری Unit Test نویسی Web Form‌ها باشید. حال می‌توانید کد نویسی مربوط به UI را انجام دهید که در ادامه به کد نویسی در Win Forms و Web Forms خواهیم پرداخت. 

مطالب
کامپایل پویای کد در دات نت

در دات نت فریم ورک امکان کامپایل پویای یک قطعه کد دریافت شده از یک رشته، توسط فضای نام CodeDom مهیا است که قدرت قابل توجهی را در اختیار برنامه نویس قرار می‌دهد.

مثال یک:
رشته زیر را کامپایل کرده و تبدیل به یک فایل exe کنید:

string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
روش انجام کار به همراه توضیحات مربوطه به صورت کامنت:

using System;
using System.Collections.Generic;
//دو فضای نامی که برای این منظور اضافه شده‌اند
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//تعیین اینکه خروجی یک فایل اجرایی است بعلاوه مشخص سازی محل ذخیره سازی فایل نهایی
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};

//عملیات کامپایل در اینجا صورت می‌گیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

//اگر خطایی وجود داشته باشد نمایش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}

static void Main(string[] args)
{
compileIt1();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
مثال 2:
کد مورد نظر را به صورت یک فایل dll کامپایل کنید.
برای این منظور تمامی مراحل مانند قبل است فقط GenerateExecutable ذکر شده به false تنظیم شده و نام خروجی نیز به foo.dll باید تنظیم شود.


مثال 3:
کد مورد نظر را در حافظه کامپایل کرده (خروجی dll یا exe نمی‌خواهیم)، سپس متد SayHello آن را به صورت پویا فراخوانی نموده و خروجی را نمایش دهید.
در این حالت روش کار همانند مثال 1 است با این تفاوت که GenerateInMemory = true و GenerateExecutable = false تنظیم می‌شوند. همچنین جهت دسترسی به متد کلاس ذکر شده،‌ از قابلیت‌های ریفلکشن موجود در دات نت فریم ورک استفاده خواهد شد.

using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";

//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);

//نحوه تعیین مشخص سازی کامپایل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};

//عملیات کامپایل در اینجا صورت می‌گیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);

// اگر خطایی در کامپایل وجود نداشت متد دلخواه را فراخوانی می‌کنیم
if (results.Errors.Count == 0)
{
//استفاده از ریفلکشن برای دسترسی به متد و فراخوانی آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}


static void Main(string[] args)
{
compileIt2();

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
نکته: نحوه‌ی استفاده از اسمبلی‌های دیگر در رشته سورس کد خود
مثال:
اگر رشته سورس ما به صورت زیر بوده و از اسمبلی System.Drawing.Dll نیز کمک گرفته باشد،‌

string source =
@"
namespace Foo
{

public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}

public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
هنگام کامپایل آن توسط روش مثال یک، با خطای زیر مواجه خواهیم شد.

Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)

برای رفع این مشکل و معرفی این اسمبلی،‌ سطر زیر باید پس از تعریف compilerParams اضافه شود.

compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
اکنون کد کامپایل شده و مشکلی نخواهد داشت.
نمونه‌ای دیگر از این دست، استفاده از LINQ می‌باشد. در این حالت اسمبلی System.Core.Dll نیز به روش ذکر شده باید معرفی گردد تا مشکلی در کامپایل کد رخ ندهد.


کاربردها:
1- استفاده در ابزارهای تولید کد (برای مثال در برنامه Linqer از این قابلیت استفاده می‌شود)
2- استفاده‌های امنیتی (ایجاد روش‌های تولید یک سریال به صورت پویا و کامپایل پویای کد مربوطه در حافظه‌ای محافظت شده)
3- استفاده جهت مقاصد محاسباتی پیشرفته
4- دادن اجازه‌ی کد نویسی به کاربران برنامه‌ی خود (شبیه به سیستم‌های ماکرو و اسکریپت نویسی موجود)
و ...

اشتراک‌ها
6.Visual Studio 2019 version 16.7 منتشر شد
حجم تقریبا آپدیت از نسخه قبلی (16.7.5) حدود 1.47G میشه.
6.Visual Studio 2019 version 16.7 منتشر شد
اشتراک‌ها
سیر تکاملی پایتون در سال های اخیر

According to several websites, Python is one of the most popular coding languages of 2015.

The programming language is currently being used by a number of high-traffic websites including Google, Yahoo Groups, Yahoo Maps, Linux Weekly News, Shopzilla and Web Therapy. Likewise, Python also finds great use for creating gaming, financial, scientific and educational applications 

سیر تکاملی پایتون در سال های اخیر
مطالب
نگاشت خودکار اشیاء توسط AutoMapper و Reflection - ایده شماره 1
آموزش کامل AutoMapper قبلا در سایت ارائه شده است. در این مقاله می‌خواهیم Mapping نوع‌های مختلف بین Dto و Entity‌های پروژه را توسط Reflection به صورت خودکار انجام دهیم. سورس کامل مثال را می‌توانید در این ریپازیتوری مشاهده کنید.
در این روش ما یک کلاس جنریک را به نام BaseDto داریم که تمام Dto‌های ما برای نگاشت خودکار باید از آن ارث بری کنند. در مثال زیر کلاس PostDto لازم است به کلاس Post نگاشت شود. پس خواهیم داشت :
public class PostDto : BaseDto<PostDto, Post, long>
{
    public string Title { get; set; }
    public string Text { get; set; }
    public int CategoryId { get; set; }

    public string CategoryName { get; set; } //=> Category.Name
}
  • کلاس PostDto خودش را به عنوان اولین پارامتر جنریک BaseDto معرفی می‌کند.
  • به عنوان پارامتر دوم، باید کلاس Entity ایی که قرار است به آن نگاشت شود (Post) را معرفی کنیم.
  • پارامتر سوم، نوع فیلد Id است که در اینجا خاصیت Id کلاس‌های Post و PostDto ما، از نوع long است.
  • نهایتا خواصی را که برای نگاشت لازم داریم، تعریف میکنیم مثل Title و...
  • همچنین می‌توانیم خواصی برای نگاشت با خواص Navigation Property‌های Post هم تعریف کنیم؛ مانند CategoryName که به خاصیت Name از Category پست مربوطه اشاره میکند و AutoMapper به صورت هوشمندانه آن‌ها را به هم نگاشت می‌کند.
تعریف کلاس جنریک BaseDto هم به نحو زیر است.
public abstract class BaseDto<TDto, TEntity, TKey>
        where TDto : class, new()
        where TEntity : BaseEntity<TKey>, new()
{
    [Display(Name = "ردیف")]
    public TKey Id { get; set; }

    public TEntity ToEntity()
    {
        return Mapper.Map<TEntity>(CastToDerivedClass(this));
    }

    public TEntity ToEntity(TEntity entity)
    {
        return Mapper.Map(CastToDerivedClass(this), entity);
    }

    public static TDto FromEntity(TEntity model)
    {
        return Mapper.Map<TDto>(model);
    }

    protected TDto CastToDerivedClass(BaseDto<TDto, TEntity, TKey> baseInstance)
    {
        return Mapper.Map<TDto>(baseInstance);
    }
}
  • نوع TDto به کلاس Dto ما اشاره میکند؛ مثلا PostDto
  • نوع TEntity به کلاس Entity ما اشاره میکند؛ مثلا Post
  • نوع TKey به نوع خاصیت Id اشاره میکند.
  • شرط لازم برای نوع TEntity این است که از <BaseEntity<TKey ارث بری کرده باشد (نوع پایه‌ای که تمام Entity‌های ما از آن ارث بری می‌کنند).
  • متد‌های کمکی ToEntity و FromEntity، کار نگاشت اشیاء را برای ما راحت‌تر می‌کنند.
پیاده سازی کلاس BaseEntity و Post نیز به شرح زیر است.
public abstract class BaseEntity<TKey>
{
    public TKey Id { get; set; }
}

public class Post : BaseEntity<long>
{
    public string Title { get; set; }
    public string Text { get; set; }
    public int CatgeoryId { get; set; }

    public Category Category { get; set; }
}

توضیح متد های ToEntity  و  FromEntity 
متد ToEntity شی Dto جاری را به Entity مربوطه نگاشت کرده و یک وهله از آن را باز میگرداند. پس بجای استفاده دستی از Api‌های AutoMapper مانند Mapper.Map<Post>(postDto)  کافی است متد ToEntity را فراخوانی کنیم؛ مثال:
var postDto = new PostDto();
var post = postDto.ToEntity();
متد بالا برای اکشن Create مناسب است؛ ولی برای اکشن Update خیر. چرا که برای Update نباید نگاشت بر روی وهله جدیدی از Post انجام شود؛ بلکه باید بر روی وهله‌ای از قبل موجود (همان post ایی که بر اساس id واکشی کرده‌ایم) نگاشت انجام شود، تا تغییرات لازم، بر روی همان وهله تاثیر کند. در غیر این صورت اگر وهله جدیدی از post ایجاد شود، چون توسط EF ChangeTracker ردیابی نمی‌شود، به‌روز رسانی هم انجام نخواهد شد.
بنابراین برای نگاشت postDto به یک شیء Post از پیش موجود (post یافت شده توسط id) خواهیم داشت:
var post = // finded by id
var updatePost = postDto.ToEntity(post);
همچنین برای نگاشت از یک Entity به Dto (عکس قضیه بالا: مثلا نگاشت یک postDto به post) کافی است متد ایستای FromEntity را خوانی کنیم. مثال :
var postDto = PostDto.FromEntity(post);

کانفیگ خودکار Mapping توسط Reflection
در ادامه می‌خواهیم کانفیگ Mapping بین Dto‌های پروژه به Entity‌های مربوطه (مثلا PostDto به Dto و برعکس) را به صورت خودکار توسط Reflection پیاده سازی و اعمال کنیم. این کار توسط کلاس AutoMapperConfiguration به نحو زیر انجام می‌شود.
public static class AutoMapperConfiguration
{
    public static void InitializeAutoMapper()
    {
        Mapper.Initialize(configuration =>
        {
            configuration.ConfigureAutoMapperForDto();
        });

        //Compile mapping after configuration to boost map speed
        Mapper.Configuration.CompileMappings();
    }

    public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config)
    {
        config.ConfigureAutoMapperForDto(Assembly.GetEntryAssembly());
    }

    public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config, params Assembly[] assemblies)
    {
        var dtoTypes = GetDtoTypes(assemblies);

        var mappingTypes = dtoTypes
            .Select(type =>
            {
                var arguments = type.BaseType.GetGenericArguments();
                return new
                {
                    DtoType = arguments[0],
                    EntityType = arguments[1]
                };
            }).ToList();

        foreach (var mappingType in mappingTypes)
            config.CreateMappingAndIgnoreUnmappedProperties(mappingType.EntityType, mappingType.DtoType);
    }

    public static void CreateMappingAndIgnoreUnmappedProperties(this IMapperConfigurationExpression config, Type entityType, Type dtoType)
    {
        var mappingExpression = config.CreateMap(entityType, dtoType).ReverseMap();

        //Ignore mapping to any property of source (like Post.Categroy) that dose not contains in destination (like PostDto)
        //To prevent from wrong mapping. for example in mapping of "PostDto -> Post", automapper create a new instance for Category (with null catgeoryName) because we have CategoryName property that has null value
        foreach (var property in entityType.GetProperties())
        {
            if (dtoType.GetProperty(property.Name) == null)
                mappingExpression.ForMember(property.Name, opt => opt.Ignore());
        }
    }

    public static IEnumerable<Type> GetDtoTypes(params Assembly[] assemblies)
    {
        var allTypes = assemblies.SelectMany(a => a.ExportedTypes);

        var dtoTypes = allTypes.Where(type =>
                type.IsClass && !type.IsAbstract && type.BaseType != null && type.BaseType.IsGenericType &&
                (type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,>) ||
                type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,,>)));

        return dtoTypes;
    }
}
عملیات با فراخوانی متد ایستا InitializeAutoMapper شروع می‌شود و باید این متد فقط یکبار در اجرای پروژه فراخوانی شود. (مثلا در سازنده کلاس Startup.cs)
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        AutoMapperConfiguration.InitializeAutoMapper();
    }
- درون این متد کانفیگ، Mapping نوع‌های مختلف قابل نگاشت برای AutoMapper توسط Mapper.Initialize انجام می‌شود.
- متد ConfigureAutoMapperForDto متد دیگری را به همین نام، فراخوانی می‌کند؛ با این تفاوت که Assembly ورودی پروژه را توسط متد ()Assembly.GetEntryAssembly، یافته و به آن پاس میدهد.
- EntryAssembly به اسمبلی ای که به عنوان نقطه ورود برنامه است، اشاره می‌کند. در این سورس کد چون پروژه ما از نوع ASP.NET Core است، اسمبلی این پروژه به عنوان EntryAssmebly شناخته می‌شود؛ یعنی همان لایه‌ای که کلاس‌های Dto ما (مانند PostDto) داخل آن تعریف شده‌است. ما به این اسمبلی از این جهت نیاز داریم که می‌خواهیم توسط Reflection، تمام نوع‌هایی که از BaseDto ارث بری می‌کنند (مانند PostDto) را یافته و Mapping آنها را به AutoMapper معرفی و اعمال کنیم.
نکته : اگر در پروژه شما Dto‌ها در لایه/لایه‌های دیگری تعریف شده‌اند باید اسمبلی آن لایه‌ها را به آن پاس دهید.
در این مرحله توسط متد GetDtoTypes کار یافتن نوع‌های Dto موجود در اسمبلی/اسمبلی‌های مشخص شده انجام می‌شود.
public static IEnumerable<Type> GetDtoTypes(params Assembly[] assemblies)
{
    var allTypes = assemblies.SelectMany(a => a.ExportedTypes);

    var dtoTypes = allTypes.Where(type =>
            type.IsClass && !type.IsAbstract && type.BaseType != null && type.BaseType.IsGenericType &&
            (type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,>) ||
            type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,,>)));

    return dtoTypes;
}
  • در خط اول ابتدا تمامی نوع‌های قابل دسترس از بیرون (ExportedTypes) از assembly‌های دریافتی واکشی می‌شود.
  • سپس توسط Where، نوع‌هایی که کلاس بوده، abstract نیستند و از BaseDto ارث بری کرده‌اند، فیلتر شده و بازگردانده می‌شوند.
در ادامه، از لیست نوع‌های Dto یافت شده، پارامتر‌های جنریک TDto و TEntity به ازای هر نوع استخراج می‌شوند.
public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config, params Assembly[] assemblies)
{
var dtoTypes = GetDtoTypes(assemblies);

var mappingTypes = dtoTypes
.Select(type =>
{
var arguments = type.BaseType.GetGenericArguments();
return new
{
DtoType = arguments[0],
EntityType = arguments[1]
};
}).ToList();

foreach (var mappingType in mappingTypes)
config.CreateMappingAndIgnoreUnmappedProperties(mappingType.EntityType, mappingType.DtoType);
}

در آخر بر روی لیست یافت شده، گردش می‌کنیم (foreach) و دو نوع DtoType و EntityType (مانند postDto و post) را که باید به یکدیگر نگاشت شوند، به متد CreateMappingAndIgnoreUnmappedProperties ارسال می‌کنیم. کار این متد، معرفی/اعمال Mapping بین نوع‌ها به کانفیگ AutoMapper می‌باشد. همچنین خواصی را که نباید نگاشت شوند، به طور خودکار یافته و Ignore می‌کند.
در مثال جاری، خاصیت CategoryName کلاس PostDto برای خواندن و select از دیتابیس لازم است زیرا می‌خواهیم هر postDto، شامل نام دسته بندی هر پست نیز باشد، ولی این ویژگی برای افزودن یا به‌روزرسانی مدنظر ما نیست؛ چرا که کلاینت ما به هنگام فراخوانی اکشن Create، فقط مقادیر خواص Post (مانند Title, Text و CategoryId) را ارسال می‌کند و نه CategoryName را. در نتیجه CatgoryName همیشه null است. اما مشکلی که ایجاد می‌کند این است که AutoMapper به هنگام نگاشت یک PostDto به Post، چون خاصیت CategoryName با (مقدار null)  وجود دارد، یک وهله جدید (با مقادیر پیشفرض) را برای Category ایجاد می‌کند که خاصیت Name آن برابر با null است و قطعا این مدنظر ما نیست. پس جهت جلوگیری از این مشکل لازم است خواصی از Entity که در Dto موجود نیستند (مانند Category) را Ignore کنیم و این دقیقا همان کاری است که متد CreateMappingAndIgnoreUnmappedProperties انجام می‌دهد. 
public static void CreateMappingAndIgnoreUnmappedProperties(this IMapperConfigurationExpression config, Type entityType, Type dtoType)
{
    var mappingExpression = config.CreateMap(entityType, dtoType).ReverseMap();

    //Ignore mapping to any property of entity (like Post.Categroy) that dose not contains in dto (like PostDto.CategoryName)
    //To prevent from wrong mapping. for example in mapping of "PostDto -> Post", automapper create a new instance for Category (with null catgeoryName) because we have CategoryName property that has null value
    foreach (var property in entityType.GetProperties())
    {
        if (dtoType.GetProperty(property.Name) == null)
            mappingExpression.ForMember(property.Name, opt => opt.Ignore());
    }
}
البته اساسا استفاده از یک Dto هم برای Create/Update و هم برای Select اصولی نیست و بهتر است دو Dto جداگانه که صرفا خواص مورد نیاز را دارند، داشته باشیم که در این صورت مشکل بالا نیز اصلا رخ نخواهد داد. راه حل مورد استفاده کنونی صرفا مرهمی برای یک استفاده غیر اصولی است!
در آخر می‌توان گفت تنها ایراد کوچک ایده‌ی فوق، استفاده از Api‌های استاتیک AutoMapper در کلاس BaseDto است (متد Mapper.Map)  که باعث می‌شود نتوانیم به هنگام تست نویسی، سرویس AutoMapper را با پیاده سازی دیگری (Fake) جایگزین و آن را Mock کنیم. البته این کار برای AutoMapper زیاد معمول هم نبوده و در مقابل مزایای این ایده، به نظرم ارزش استفاده را خواهد داشت.
در قسمت بعدی همین ایده را توسعه خواهیم داد و قابلیت سفارشی سازی Mapping را برای آن فراهم خواهیم کرد.
نظرات مطالب
بخش دوم - بررسی امکانات (کلاس ها و متدهای) کتابخانه Gridify
من قصد پیاده سازی یک صفحه برای جستجوی پیشرفته بین همه جداول رو دارم. برای این کار یک کلاس تعریف کردم و در ورودی تابع یک لیست از این نوع را بهش پاس میدم. این کلاس اینطور تعریف شده:
public class SearchDTO
{
    public string TableName { get; set; }
    public string ColumnName { get; set; }
    public string searchPhrase { get; set; } = string.Empty;
    public DateTimeOffset? searchDateFrom { get; set; }
    public DateTimeOffset? searchDateTo { get; set; }
    public int Include { get; set; } = 1;
}
کاربر اسم جدول و اون ستونی که میخواد شرط رو براش اعمال کنه رو انتخاب میکنه و بعدش عبارت یا محدوده تاریخ رو وارد میکنه. این که شامل بشه یا نشه رو هم با Include میتونه مشخص کنه. 
من هر چی داکومنت رو خوندم توی همشون اسم جدول رو نمیشد به صورت string وارد کرد. اگه اشتباه میکنم لطفا اصلاح کنید.
رویه ای که مدنظرم هست اینه که داخل یه حلقه for یا foreach یک کوئری بنویسم که همه‌ی جداولی که کاربر انتخاب کرده رو با هم join کنه. بعدش توی یه حلقه دیگه شرط‌ها رو روی ستون هایی که انتخاب کرده اعمال کنم.
در نهایت ستون‌های نتیحه نهایی رو Select کنم تا اون ستون هایی که مجاز هستند به سمت کلاینت برگشت داده بشه. اسامی این ستون‌ها رو توی یه فایل .resx ذخیره کردم.
یعنی یه چیزی شبیه به این کد:(ولی این کد درست و قابل اجرا نیست)
// Join the tables dynamically based on the table names
for (int i = 1; i < filterList.Count; i++)
{
    var joinEntityType = entityTypes.FirstOrDefault(t => t.ClrType.Name == filterList[0].TableName)?.ClrType;
    if (entityType == null)
    {
        return (null, 0, 0);
    }
    var joinEntityQuery = (IQueryable<object>)Activator.CreateInstance(typeof(DbSet<>).MakeGenericType(joinEntityType), _dbContext);
    query = query.Join(joinEntityQuery.ToList(),
                                    x => x.GetType().GetProperty($"{filterList[i - 1].TableName}.{filterList[i - 1].TableName}Id").GetValue(x),
                                    y => y.GetType().GetProperty($"{filterList[i].TableName}.{filterList[i].TableName}Id").GetValue(y),
                                    (x, y) => x);
}

// Apply the conditions dynamically based on the column names and conditions
for (int i = 0; i < filterList.Count; i++)
{
    if (!string.IsNullOrEmpty(filterList[i].searchPhrase))
    {
        var parameter = Expression.Parameter(entityType, "x");
        var condition = Expression.Call(
            typeof(string).GetMethod("Contains", new[] { typeof(string) }),
            Expression.PropertyOrField(parameter, filterList[i].ColumnName),
            Expression.Constant(filterList[i].searchPhrase)
        );
        var lambda = Expression.Lambda<Func<object, bool>>(condition, parameter);

        query = query.Where(lambda);
    }
    if (filterList[i].searchDateFrom.HasValue)
    {
       //must write expression for date constraint
    }
}

// Select the specified columns dynamically
ResourceManager resourceManager = new ResourceManager(typeof(TablePropertiesResources));
var columnNames = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true)
    .OfType<DictionaryEntry>()
    .Select(entry => entry.Key.ToString())
    .ToList();
var selectColumns = columnNames.ToArray();
var selectedData = query
    .Select(x => new
    {
        // Dynamically select the desired properties
        Result = selectColumns.ToDictionary(column => column, column => x.GetType().GetProperty(column).GetValue(x))
    })
    .ToList();
آیا اینکار با gridify امکان پذیر می‌باشد؟
اشتراک‌ها
IIS 10.0 Express منتشر شد

Internet Information Services (IIS) 10.0 Express is a free, simple and self-contained version of IIS that is optimized for developers. IIS 10.0 Express makes it easy to use the most current version of IIS to develop and test websites. IIS 10.0 Express has all the core capabilities of IIS 10.0 and additional features to ease website development.

The benefits of using IIS 10.0 Express include:

  • The same web server that runs on your production server is now available on your development computer.
  • Most tasks can be done without the need for administrative privileges.
  • IIS Express runs on Windows 7 Service Pack 1 and all later versions of Windows.
  • Many users can work independently on the same computer. 
IIS 10.0 Express منتشر شد
اشتراک‌ها
انتشار Windows Forms Designer مخصوص NET Core.

We just released a GA version of .NET Core 3.0 that includes support for Windows Forms and WPF. And along with that release we’re happy to announce the first preview version of the Windows Forms Designer for .NET Core projects! 

انتشار Windows Forms Designer مخصوص NET Core.