اشتراک‌ها
معرفی Nano Server ویندوز سرور 2016

Nano Server is a deeply refactored version of Windows Server with a small footprint and remotely managed installation, optimized for the cloud and a DevOps workflow.  

معرفی Nano Server ویندوز سرور 2016
اشتراک‌ها
دوره 8 ساعته ASP.NET Core Razor Pages

Web Development with ASP.NET Core Razor Pages || FULL COURSE || Trevoir Williams

00:00 Introduction
1:00 Lesson 1: Setting Up
4:36 Lesson 2: Folder Tour
23:33 Lesson 3: Understanding Razor Syntax
35:52 Lesson 4: Message From Settings
43:44 Lesson 5: Adding Entity Framework Core
55:48 Lesson 6: Connect to Database with Entity Framework Core
1:10:20 Lesson 7: Scaffolding Database Tables as Classes
1:23:26 Lesson 8: GitHub Commit 1
1:32:50 Lesson 9: Create Page
1:54:32 Lesson 10: Update Page
2:18:25 Lesson 11: Details Page
2:23:56 Lesson 12: Delete Page
2:46:26 Lesson 13: UI Enhancements
3:19:38 Lesson 14: Check-In Module
3:21:04 Lesson 15: Added New Table
3:43:19 Lesson 16: Enhance Forms
4:07:40 Lesson 17: Further Form Enhancements
4:20:00 Lesson 18: Adding More Requirements
4:46:35 Lesson 19: Adding More Requirements Continued
5:05:23 Lesson 20: Add Cascading Dropdown
5:29:26 Lesson 21: Finish Cascading Dropdown
5:45:59 Lesson 22: Cleanup Labels
5:52:42 Lesson 23: Finish Interface Cleanup
6:23:57 Lesson 24: Setup Repositories
6:49:54 Lesson 25: Add First Repository Code
7:06:41 Lesson 26: Refactoring Pages
7:21:02 Lesson 27: Complete Repositories
7:42:27 Lesson 28: Section Conclusion
7:52:14 Lesson 29: User Authentication Setup
8:03:08 Lesson 30: Extend Users Table
8:08:01 Lesson 31: Setup Registration Page
8:28:27 Lesson 32: Setup Login
8:38:12 Lesson 33: Setup Authorization
8:47:47 Lesson 34: Add Authorization

دوره 8 ساعته ASP.NET Core Razor Pages
اشتراک‌ها
ساخت برنامه های CLI در NET.

It can be useful to create console apps to support backend processes such as data anonymisation or batch processing. This post demonstrates how to develop console apps in .NET, including support for built-in help, arguments, configuration, logging, dependency injection, and more. 

ساخت برنامه های CLI در NET.
اشتراک‌ها
سایت آموزشی: C# Station
C# Station Tutorial This is a set of lessons suited for beginning to intermediate programmers or anyone who would like to gain familiarity with the C# programming language. 
سایت آموزشی: C# Station
اشتراک‌ها
کتابخانه MathSharp :انجام سریع محاسبات ریاضی به کمک Vector و Matrix توسط Hardware Intrinsics

MathSharp is a vector and matrix library written in C# using hardware intrinsics. Thanks to hardware acceleration, MathSharp is significantly faster than most mathematics libraries out there, but only supports .NET Core 3.0 and up. 

کتابخانه MathSharp :انجام سریع محاسبات ریاضی به کمک Vector و Matrix توسط Hardware Intrinsics
اشتراک‌ها
دوره 4 ساعته شروع به کار با NET MAUI.
Learn .NET MAUI - Full Course for Beginners | Tutorial for iOS, Android, Mac, Windows in C#
Let's start our journey together to build beautiful native cross-platform apps for iOS, Android, macOS, and Windows with .NET MAUI, C#, and Visual Studio! In this full workshop, I will walk you through everything you need to know about .NET MAUI and building your very first app. You will learn the basics including how to build user interfaces with XAML, how MVVM and data binding simplify development, how to navigate between pages, access platform features like geolocation, optimize data collections, and theme your app for light theme and dark theme. This course has everything you need to learn the basics and set you up for success when building apps with .NET MAUI!

Chapters:
00:00:00 - Intro to the .NET MAUI Workshop
00:04:10 - What is .NET MAUI & How to Install
00:06:25 - Workshop overview
00:08:00 - First .NET MAUI app & Architecture (slides)
00:21:40 - Get code to build your first .NET MAUI app
00:25:00 - .NET MAUI Project Walkthrough
00:29:40 - Start to build first .NET MAUI app
00:56:48 - Intro to MVVM (slides)
01:09:30 - Implementing INotifyPropertyChanged
01:22:30 - .NET Community Toolkit for MVVM (Source Generators)
01:32:30 - HTTP REST Calls & JSON Deserialization
01:43:00 - ICommand in .NET MAUI
01:59:30 - Create our UI with XAML & MVVM
02:16:20 - Navigation in .NET MAUI (slides)
02:25:20 - Implementing Navigation in .NET MAUI & Passing Parameters
02:46:00 - Building a details UI with XAML & MVVM
02:54:10 - Modal, Back Navigation, & More
02:58:20 - Access Platform APIs in .NET MAUI (slides)
03:02:53 - Platform API Integration - Connectivity
03:08:30 - Geolocation & Permissions Implementation
03:18:50 - Open Map Integration
03:22:40 - Platform Specifics - iOS Safe Area
03:25:50 - CollectionView & RefreshView Overview (slides)
03:34:00 - Implementing Pull-to-Refresh
03:40:00 - CollectionView Layouts - Grids and more
03:41:30 - CollectionView EmptyView
03:45:00 - App Resources, Styles, and Themes (slides)
03:56:20 - Implementing Light & Dark Mode
04:06:00 - Thanks, wrap-up, and resources 
دوره 4 ساعته شروع به کار با NET MAUI.
مطالب
راهنمای تغییر بخش احراز هویت و اعتبارسنجی کاربران سیستم مدیریت محتوای IRIS به ASP.NET Identity – بخش دوم
در بخش اول، کارهایی که انجام دادیم به طور خلاصه عبارت بودند از:
1-  حذف کاربرانی که نام کاربری و ایمیل تکراری داشتند
2-  تغییر نام فیلد Password به PasswordHash در جدول User
 
سیستم مدیریت محتوای IRIS، برای استفاده از Entity Framework، از الگوی واحد کار (Unit Of Work) و تزریق وابستگی استفاده کرده است و اگر با نحوه‌ی پیاده سازی این الگو‌ها آشنا نیستید، خواندن مقاله EF Code First #12  را به شما توصیه می‌کنم.
برای استفاده از ASP.NET Identity نیز باید از الگوی واحد کار استفاده کرد و برای این کار، ما از مقاله اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity استفاده خواهیم کرد.
نکته مهم: در ادامه اساس کار ما بر پایه‌ی مقاله اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity است و چیزی که بیشتر برای ما اهمیت دارد کدهای نهایی آن هست؛ پس حتما به مخزن کد آن مراجعه کرده و کدهای آن را دریافت کنید.
 
تغییر نام کلاس User به ApplicationUser

اگر به کدهای مثال رسمی ASP.NET Identity نگاهی بیندازید، می‌بینید که کلاس مربوط به جدول کاربران ApplicationUser نام دارد، ولی در سیستم IRIS نام آن User است. بهتر است که ما هم نام کلاس خود را از User به ApplicationUser تغییر دهیم چرا که مزایای زیر را به دنبال دارد:

1- به راحتی می‌توان کدهای مورد نیاز را از مثال Identity کپی کرد.
2- در سیستم Iris، بین کلاس User متعلق به پروژه خودمان و User مربوط به HttpContext تداخل رخ می‌داد که با تغییر نام کلاس User دیگر این مشکل را نخواهیم داشت.
 
برای این کار وارد پروژه Iris.DomainClasses شده و نام کلاس User را به ApplicationUser تغییر دهید. دقت کنید که این تغییر نام را از طریق Solution Explorer انجام دهید و نه از طریق کدهای آن. پس از این تغییر ویژوال استودیو می‌پرسد که آیا نام این کلاس را هم در کل پروژه تغییر دهد که شما آن را تایید کنید.

برای آن که نام جدول Users در دیتابیس تغییری نکند، وارد پوشه‌ی Entity Configuration شده و کلاس UserConfig را گشوده و در سازنده‌ی آن کد زیر را اضافه کنید:
ToTable("Users");

نصب ASP.NET Identity

برای نصب ASP.NET Identity دستور زیر را در کنسول Nuget وارد کنید:
Get-Project Iris.DomainClasses, Iris.Datalayer, Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.EntityFramework
از پروژه AspNetIdentityDependencyInjectionSample.DomainClasses کلاس‌های CustomUserRole، CustomUserLogin، CustomUserClaim و CustomRole را به پروژه Iris.DomainClasses منتقل کنید. تنها تغییری که در این کلاس‌ها باید انجام دهید، اصلاح namespace آنهاست.
همچنین بهتر است که به کلاس CustomRole، یک property به نام Description اضافه کنید تا توضیحات فارسی نقش مورد نظر را هم بتوان ذخیره کرد:

 
    public class CustomRole : IdentityRole<int, CustomUserRole>
    {
        public CustomRole() { }
        public CustomRole(string name) { Name = name; }

        public string Description { get; set; }

    }

نکته: پیشنهاد می‌کنم که اگر می‌خواهید مثلا نام CustomRole را به IrisRole تغییر دهید، این کار را از طریق find and replace انجام ندهید. با همین نام‌های پیش فرض کار را تکمیل کنید و سپس از طریق خود ویژوال استودیو نام کلاس را تغییر دهید تا ویژوال استودیو به نحو بهتری این نام‌ها را در سرتاسر پروژه تغییر دهد.
 
سپس کلاس ApplicationUser پروژه IRIS را باز کرده و تعریف آن را به شکل زیر تغییر دهید:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>

اکنون می‌توانید property‌های Id،  UserName، PasswordHash و Email را حذف کنید؛ چرا که در کلاس پایه IdentityUser تعریف شده اند.
 
تغییرات DataLayer

وارد Iris.DataLayer شده و کلاس IrisDbContext را به شکل زیر ویرایش کنید:
public class IrisDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>,
        IUnitOfWork

اکنون می‌توانید property زیر را نیز حذف کنید چرا که در کلاس پایه تعریف شده است: 
 public DbSet<ApplicationUser> Users { get; set; }

 

نکته مهم: حتما برای کلاس IrisDbContext سازنده ای تعریف کنید که صراحتا نام رشته اتصالی را ذکر کرده باشد، اگر این کار را انجام ندهید با خطاهای عجیب غریبی روبرو می‌شوید. 

        public IrisDbContext()
            : base("IrisDbContext")
        {
        }

همچنین درون متد OnModelCreating کدهای زیر را پس از فراخوانی متد (base.OnModelCreating(modelBuilder  جهت تعیین نام جداول دیتابیس بنویسید:
            modelBuilder.Entity<CustomRole>().ToTable("AspRoles");
            modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims");
            modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles");
            modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins");
از این جهت نام جدول CustomRole را در دیتابیس AspRoles انتخاب کردم تا با نام جدول Roles نقش‌های کنونی سیستم Iris تداخلی پیش نیاید.
اکنون دستور زیر را در کنسول Nuget وارد کنید تا کدهای مورد نیاز برای مهاجرت تولید شوند:
Add-Migration UpdateDatabaseToAspIdentity
public partial class UpdateDatabaseToAspIdentity : DbMigration
{
        public override void Up()
        {
            CreateTable(
                "dbo.UserClaims",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        UserId = c.Int(nullable: false),
                        ClaimType = c.String(),
                        ClaimValue = c.String(),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.UserLogins",
                c => new
                    {
                        LoginProvider = c.String(nullable: false, maxLength: 128),
                        ProviderKey = c.String(nullable: false, maxLength: 128),
                        UserId = c.Int(nullable: false),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId })
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.UserRoles",
                c => new
                    {
                        UserId = c.Int(nullable: false),
                        RoleId = c.Int(nullable: false),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => new { t.UserId, t.RoleId })
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .ForeignKey("dbo.AspRoles", t => t.RoleId, cascadeDelete: true)
                .Index(t => t.RoleId)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.AspRoles",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        Description = c.String(),
                        Name = c.String(nullable: false, maxLength: 256),
                    })
                .PrimaryKey(t => t.Id)
                .Index(t => t.Name, unique: true, name: "RoleNameIndex");
            
            AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "SecurityStamp", c => c.String());
            AddColumn("dbo.Users", "PhoneNumber", c => c.String());
            AddColumn("dbo.Users", "PhoneNumberConfirmed", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "TwoFactorEnabled", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "LockoutEndDateUtc", c => c.DateTime());
            AddColumn("dbo.Users", "LockoutEnabled", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "AccessFailedCount", c => c.Int(nullable: false));
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.UserRoles", "RoleId", "dbo.AspRoles");
            DropForeignKey("dbo.UserRoles", "ApplicationUser_Id", "dbo.Users");
            DropForeignKey("dbo.UserLogins", "ApplicationUser_Id", "dbo.Users");
            DropForeignKey("dbo.UserClaims", "ApplicationUser_Id", "dbo.Users");
            DropIndex("dbo.AspRoles", "RoleNameIndex");
            DropIndex("dbo.UserRoles", new[] { "ApplicationUser_Id" });
            DropIndex("dbo.UserRoles", new[] { "RoleId" });
            DropIndex("dbo.UserLogins", new[] { "ApplicationUser_Id" });
            DropIndex("dbo.UserClaims", new[] { "ApplicationUser_Id" });
            DropColumn("dbo.Users", "AccessFailedCount");
            DropColumn("dbo.Users", "LockoutEnabled");
            DropColumn("dbo.Users", "LockoutEndDateUtc");
            DropColumn("dbo.Users", "TwoFactorEnabled");
            DropColumn("dbo.Users", "PhoneNumberConfirmed");
            DropColumn("dbo.Users", "PhoneNumber");
            DropColumn("dbo.Users", "SecurityStamp");
            DropColumn("dbo.Users", "EmailConfirmed");
            DropTable("dbo.AspRoles");
            DropTable("dbo.UserRoles");
            DropTable("dbo.UserLogins");
            DropTable("dbo.UserClaims");
        }
}

بهتر است که در کدهای تولیدی فوق، اندکی متد Up را با کد زیر تغییر دهید: 
AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false, defaultValue:true));

چون در سیستم جدید احتیاج به تایید ایمیل به هنگام ثبت نام است، بهتر است که ایمیل‌های قبلی موجود در سیستم نیز به طور پیش فرض تایید شده باشند.
در نهایت برای اعمال تغییرات بر روی دیتابیس دستور زیر را در کنسول Nuget وارد کنید:
Update-Database
 
تغییرات ServiceLayer

ابتدا دستور زیر را در کنسول Nuget  وارد کنید: 
Get-Project Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.Owin
سپس از فولدر Contracts پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer فایل‌های IApplicationRoleManager، IApplicationSignInManager، IApplicationUserManager، ICustomRoleStore و ICustomUserStore را در فولدر Interfaces پروژه Iris.ServiceLayer کپی کنید. تنها کاری هم که نیاز هست انجام بدهید اصلاح namespace هاست.

باز از پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer کلاس‌های ApplicationRoleManager، ApplicationSignInManager،  ApplicationUserManager، CustomRoleStore، CustomUserStore، EmailService و SmsService را به پوشه EFServcies پروژه‌ی Iris.ServiceLayer کپی کنید.
نکته: پیشنهاد می‌کنم که EmailService را به IdentityEmailService تغییر نام دهید چرا که در حال حاضر سیستم Iris دارای کلاسی به نامی EmailService هست.
 
تنظیمات StructureMap برای تزریق وابستگی ها
پروژه Iris.Web  را باز کرده، به فولدر DependencyResolution بروید و به کلاس IoC کدهای زیر را اضافه کنید:
                x.For<IIdentity>().Use(() => (HttpContext.Current != null && HttpContext.Current.User != null) ? HttpContext.Current.User.Identity : null);

                x.For<IUnitOfWork>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<IrisDbContext>();

                x.For<IrisDbContext>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>());
                x.For<DbContext>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>());

                x.For<IUserStore<ApplicationUser, int>>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<CustomUserStore>();

                x.For<IRoleStore<CustomRole, int>>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<RoleStore<CustomRole, int, CustomUserRole>>();

                x.For<IAuthenticationManager>()
                      .Use(() => HttpContext.Current.GetOwinContext().Authentication);

                x.For<IApplicationSignInManager>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<ApplicationSignInManager>();

                x.For<IApplicationRoleManager>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<ApplicationRoleManager>();

                // map same interface to different concrete classes
                x.For<IIdentityMessageService>().Use<SmsService>();
                x.For<IIdentityMessageService>().Use<IdentityEmailService>();

                x.For<IApplicationUserManager>().HybridHttpOrThreadLocalScoped()
                   .Use<ApplicationUserManager>()
                   .Ctor<IIdentityMessageService>("smsService").Is<SmsService>()
                   .Ctor<IIdentityMessageService>("emailService").Is<IdentityEmailService>()
                   .Setter<IIdentityMessageService>(userManager => userManager.SmsService).Is<SmsService>()
                   .Setter<IIdentityMessageService>(userManager => userManager.EmailService).Is<IdentityEmailService>();

                x.For<ApplicationUserManager>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (ApplicationUserManager)context.GetInstance<IApplicationUserManager>());

                x.For<ICustomRoleStore>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<CustomRoleStore>();

                x.For<ICustomUserStore>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<CustomUserStore>();

اگر ()HttpContext.Current.GetOwinContext شناسایی نمی‌شود دلیلش این است که متد GetOwinContext یک متد الحاقی است که برای استفاده از آن باید پکیج نیوگت زیر را نصب کنید:
Install-Package Microsoft.Owin.Host.SystemWeb

تغییرات Iris.Web
در ریشه پروژه‌ی Iris.Web  یک کلاس به نام Startup  بسازید و کدهای زیر را در آن بنویسید:
using System;
using Iris.Servicelayer.Interfaces;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.DataProtection;
using Owin;
using StructureMap;

namespace Iris.Web
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            configureAuth(app);
        }

        private static void configureAuth(IAppBuilder app)
        {
            ObjectFactory.Container.Configure(config =>
            {
                config.For<IDataProtectionProvider>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use(() => app.GetDataProtectionProvider());
            });

            //ObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase();

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.
                    OnValidateIdentity = ObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity()
                }
            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

            // Enables the application to remember the second login verification factor such as phone or email.
            // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
            // This is similar to the RememberMe option when you log in.
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

            app.CreatePerOwinContext(
               () => ObjectFactory.Container.GetInstance<IApplicationUserManager>());

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            //app.UseTwitterAuthentication(
            //   consumerKey: "",
            //   consumerSecret: "");

            //app.UseFacebookAuthentication(
            //   appId: "",
            //   appSecret: "");

            //app.UseGoogleAuthentication(
            //    clientId: "",
            //    clientSecret: "");

        }
    }
}

تا به این جای کار اگر پروژه را اجرا کنید نباید هیچ مشکلی مشاهده کنید. در بخش بعدی کدهای مربوط به کنترلر‌های ورود، ثبت نام، فراموشی کلمه عبور و ... را با سیستم Identity پیاده سازی می‌کنیم.
مطالب
نگاشت خودکار اشیاء توسط AutoMapper و Reflection - ایده شماره 2
پیش نیاز این مطلب، قسمت قبل آن است. در قسمت قبل، یک کلاس جنریک را به نام BaseDto ایجاد کردیم که با ارث بری Dto‌های پروژه از این کلاس، علاوه بر متد‌های ToEntity و FromEntity جهت ساده سازی عملیات نگاشت، Mapping‌های لازم بین Dto‌ها و Entity‌های مربوطه، توسط Reflection به صورت خودکار انجام می‌شد.
در این قسمت می‌خواهیم مکانیزم Mapping خودکار را کمی تغییر داده و قابلیت سفارشی سازی Mapping‌ها را فراهم کنیم. سورس کامل مثال را می‌توانید در این  ریپازیتوری  مشاهده کنید. 
ابتدا یک اینترفیس را به نام IHaveCustomMapping به نحو زیر ایجاد می‌کنیم.
public interface IHaveCustomMapping
{
    void CreateMappings(AutoMapper.Profile profile);
}
هر کلاسی که این اینترفیس را پیاده سازی کند، در متد CreateMappings آن، یک شیء از نوع Profile را دریافت می‌کند و می‌تواند تمامی کانفیگ Mapping‌های دلخواه را اعمال کند.
به عنوان مثال کلاس زیر، Mapping لازم برای PostDto و Post را درون متد CreateMappings خود اعمال می‌کند.
public class PostDtoMapping : IHaveCustomMapping
{
    public void CreateMappings(Profile profile)
    {
        profile.CreateMap<PostDto, Post>().ReverseMap();
    }
}
اکنون لازم است تدبیری بیاندیشیم تا کلاس‌هایی را که از اینترفیس IHaveCustomMapping مشتق شده‌اند، به AutoMapper معرفی کنیم. در واقع باید کلاس‌های مذکور (مانند PostDtoMapping) را یافته، یک وهله از آنها را ایجاد کنیم، سپس متد CreateMappings آنها فراخوانی کرده و شیء ای از نوع Profile را به عنوان ورودی به آن پاس دهیم.
بدین منظور کلاسی را به نام CustomMappingProfile به نحو زیر تعریف می‌کنیم.
public class CustomMappingProfile : Profile
{
    public CustomMappingProfile(IEnumerable<IHaveCustomMapping> haveCustomMappings)
    {
        foreach (var item in haveCustomMappings)
            item.CreateMappings(this);
    }
}
  • این کلاس از AutoMapper.Profile ارث بری کرده‌است.
  • درون سازنده‌ی خود لیستی از اشیاء اینترفیس IHaveCustomMapping را دریافت کرده و بر روی آنها گردش می‌کند.
  • و متد CreateMappings هرکدام را فراخوانی کرده و خودش (this : شی جاری) را (که از نوع Profile شده) به عنوان پارامتر ورودی پاس می‌دهد.
اکنون کلاس AutoMapperConfiguration قسمت قبل را به نحو زیر اصلاح می‌کنیم.
public static class AutoMapperConfiguration
{
    public static void InitializeAutoMapper()
    {
        Mapper.Initialize(config =>
        {
            config.AddCustomMappingProfile();
        });

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

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

    public static void AddCustomMappingProfile(this IMapperConfigurationExpression config, params Assembly[] assemblies)
    {
        var allTypes = assemblies.SelectMany(a => a.ExportedTypes);

        //Find all classes that implement IHaveCustomMapping inteface and create new instance of each
        var list = allTypes.Where(type => type.IsClass && !type.IsAbstract &&
            type.GetInterfaces().Contains(typeof(IHaveCustomMapping)))
            .Select(type => (IHaveCustomMapping)Activator.CreateInstance(type));

        //Create a new automapper Profile for this list to create mapping then add to the config
        var profile = new CustomMappingProfile(list);
        config.AddProfile(profile);
    }
}
  • توضیحات متد های InitializeAutoMapper و AddCustomMappingProfile، مشابه مطلب قبل است و لازم به ذکر مجدد نیست.
  • متد AddCustomMappingProfile آرایه‌ای از اسمبلی‌ها را دریافت و سپس تمامی نوع‌های قابل دسترس آنها را (ExportedTypes) واکشی می‌کند.
  • سپس توسط شرط Where، نوع‌هایی که کلاس بوده، abstract نیستند و از اینترفیس IHaveCustomMapping مشتق شده‌اند فیلتر می‌شوند. 
  • سپس توسط متد Activator.CreateInstance، وهله‌ای از آنها ایجاد و به نوع IHaveCustomMapping تبدیل می‌شوند و نهایتا لیستی از اشیاء وهله سازی شده را باز می‌گرداند.
  • سپس وهله‌ای از نوع CustomMappingProfile (که مسئول اعمال Mapping‌های اشیاء دریافتی است و قبلا بررسی کردیم) ایجاد می‌کنیم و لیست مذکور را به سازنده آن پاس می‌دهیم.
  • نهایتا profile ساخته شده (حاوی تمامی Mapping‌های اعمال شده) را توسط متد config.AddProfile به AutoMapper معرفی می‌کنیم (در این لحظه تمامی Mapping‌های تعریف شده داخل profile، به AutoMapper اعمال می‌شوند).
توسط این مکانیزم، هر کلاسی که اینترفیس IHaveCustomMapping را پیاده سازی کرده باشد، به صورت خودکار یافت شده و Mapping به آنها اعمال می‌شود. حال می‌توان این مکانیزم را با BaseDto قسمت قبل ترکیب کرده و کلاس BaseDto را به نحو زیر اصلاح کنیم.
public abstract class BaseDto<TDto, TEntity, TKey> : IHaveCustomMapping
        where TEntity : BaseEntity<TKey>
{
    [Display(Name = "ردیف")]
    public TKey Id { get; set; }

    /// <summary>
    /// Maps this dto to a new entity object.
    /// </summary>
    public TEntity ToEntity()
    {
        return Mapper.Map<TEntity>(CastToDerivedClass(this));
    }

    /// <summary>
    /// Maps this dto to an exist entity object.
    /// </summary>
    public TEntity ToEntity(TEntity entity)
    {
        return Mapper.Map(CastToDerivedClass(this), entity);
    }

    /// <summary>
    /// Maps the specified entity to a new dto object.
    /// </summary>
    public static TDto FromEntity(TEntity model)
    {
        return Mapper.Map<TDto>(model);
    }

    protected TDto CastToDerivedClass(BaseDto<TDto, TEntity, TKey> baseInstance)
    {
        return Mapper.Map<TDto>(baseInstance);
    }

    //Get automapper Profile then create mapping and ignore unmapped properties
    public void CreateMappings(Profile profile)
    {
        var mappingExpression = profile.CreateMap<TDto, TEntity>();

        var dtoType = typeof(TDto);
        var entityType = typeof(TEntity);

        //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());
        }

        //Pass mapping expressin to customize mapping in concrete class
        CustomMappings(mappingExpression.ReverseMap());
    }

    //Concrete class can override this method to customize mapping
    public virtual void CustomMappings(IMappingExpression<TEntity, TDto> mapping)
    {
    }
}
  • کلاس جنریک BaseDto، متدCreateMappings اینترفیس IHaveCustomMapping را پیاده سازی می‌کند.
  • درون این متد، Mapping بین دو نوع TDto و TEntity، توسط ()<profile.CreateMap<TDto, TEntity کانفیگ می‌شود.
  • مانند مطلب قبل، خواصی را که نباید نگاشت شوند، توسط Reflection یافته و Ignore می‌کنیم.
  • سپس Mapping برعکس را توسط ReverseMap اعمال کرده و به متد زیرین آن که virtual نیز است، پاس می‌دهیم.
متد CustomMappings ای که به صورت virtual تعریف شده‌است، این امکان را به ما می‌دهد که در کلاس‌هایی که از BaseDto ارث بری می‌کنند، در صورت لزوم آن را بازنویسی (override) کرده و سفارشی سازی دلخواه‌مان را بر روی Mapping دریافتی اعمال کنیم.
مثال: کلاس PostDto زیر از BaseDto ارث بری کرده و چون سفارشی سازی‌ای لازم دارد، متد CustomMappings والد خود را override کرده است.
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
    public string FullTitle { get; set; } //=> custom mapping for "Title (Category.Name)"
        
    public override void CustomMappings(IMappingExpression<Post, PostDto> mapping)
    {
        mapping.ForMember(
                dest => dest.FullTitle,
                config => config.MapFrom(src => $"{src.Title} ({src.Category.Name})"));
    }
}
  • این کلاس، خاصیتی به نام FullTitle دارد که معادلی (خاصیت همنامی) در کلاس Post برای آن وجود ندارد و قرار است مقدار ترکیبی حاصل از Title و Category.Name را نمایش دهد. 
  • به همین جهت متد CustomMappings را باز نویسی کرده، شیء mapping را دریافت و سفارشی سازی لازم را روی آن انجام داده‌ایم.
  • توسط متد ForMember مشخص کرده‌ایم که مقدار خاصیت FullTitle باید حاصلی از ترکیب Title و Category.Name به نحو مشخص شده باشد ( توسط متد MapFrom).
پس در این روش علاوه بر امکانات BaseDto و Mapping خودکار، امکان سفارشی سازی دلخواه را نیز خواهیم داشت.
برای کوئری گرفتن از دیتابیس نیز و تبدیل آنها به لیستی از Dto‌ها می‌توان از متد ProjectTo بر روی IQueryable استفاده کرد و حتی شرط Where را بر روی کوئری Dto‌ها اعمال کرد مانند زیر:
List<PostDto> list =
    //ProjectTo method select only needed properties (of PostDto) not all properties
    //Also select only needed property of navigations (like Post.Category.Name) not all unlike Include
    //This ability called "Projection"
    await _applicationDbContext.Posts.ProjectTo<PostDto>()
    //We can also use Where on IQuerable<PostDto>
    .Where(p => p.Title.Contains("test") || p.CategoryName.Contains("test"))
    .ToListAsync();
  • متد ProjectTo کوئری post را به IQueryable ای از postDto تبدیل می‌کند (این قابلیت Projection نامیده می‌شود).
  • نگاشت خودکار خواص موجود در postDto توسط AutoMapper به صورت خودکار انجام می‌شود و فقط خواص لازم برای postDto واکشی می‌شوند (نه همه خواص در جدول post، که این به لحاظ کارآیی بهتر است).
  • همچنین اگر خواصی را داخل Navigation Property‌ها مانند CategoryName داشته باشیم، موقع کوئری گرفتن از دیتابیس، آنها نیز اعمال شده و فقط خواص لازم از Category واکشی می‌شوند (فقط خاصیت Name، بر خلاف Include که همه ستون‌ها را واکشی می‌کند).
  • همچنین می‌توان بر روی خواص Dto شرط Where را قرار داد مانند p.CategoryName.Contains("test") و تماما به کوئری SQL معادل آن ترجمه و اجرا می‌شوند.