مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
در ادامه قسمت قبلی به برسی ویژگی‌های پیشرفته‌ی AutoMapper می‌پردازیم...


Custom type converters
همانطور که از اسمش مشخصه، زمانی کاربرد داره که نوع عضو یا اعضای یک شی در مبداء، با معادلشون در مقصد یکی نیستند. مثلا فرض کنید نوع Bool در مبداء رو می‌خواهیم به نوع String در مقصد نگاشت کنیم؛ همون Yes و No  معروف بجای True یا False .
کلاس‌های زیر رو در نظر بگیرید:
public class Source
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }
    public string Value3 { get; set; }
}

public class Destination
{
    public int Value1 { get; set; }
    public DateTime Value2 { get; set; }
    public Type Value3 { get; set; }
}
طبق مستندات AutoMapper  اگه بخواهیم این دو رو نگاشت کنیم Exception  میده چون AutoMapper  نمیدونه چطوری باید مثلا Int  رو به String تبدیل کنه؛ برای همین ما باید به AutoMapper  بگیم چطور این تبدیل نوع رو انجام بده.

نکته: در تستی که من انجام دادم، AutoMapper  تبدیل نوع‌های ابتدایی رو خودش انجام میده؛ مثلا همین تبدیل Int به String  رو!

یکی از روش‌های مهیا کردن تبدیل کننده‌ی نوع، پیاده سازی اینترفیس ITypeConverter<TSource, TDestination> هست. تقریبا مثل کاری که در WPF  و SL با پیاده سازی اینترفیس IValueConverter انجام می‌دادیم.   
من برای تست از همون  تبدیل نوع Bool  به String استفاده میکنم و البته بخاطر ساده بودن دیگه  Model ‌ها رو نمی‌نویسم.
ابتدا تعریف کلاس تبدیل کننده‌ی نوع:
public class BooltoStringTypeConvertor : ITypeConverter<bool, string>
    {
        public string Convert(ResolutionContext context)
        {
            return (bool)context.SourceValue ? "Yes" : "No";
        }
    }
و نحوه استفاده:
Mapper.CreateMap<bool,string>().ConvertUsing<BooltoStringTypeConvertor>();
            Mapper.CreateMap<Product, ProductDto>();
            Mapper.AssertConfigurationIsValid();

var product = new Product { Id = 1,Name ="PC" ,InStock = true };
var productDto = Mapper.Map<Product, ProductDto>(product);
خروجی به شکل زیر میشه.

نکته: TypeConvertor‌ها میدان دیدشون سراسریه و نیازی نیست به ازای هر نگاشتی اونو به AutoMapper  معرفی کنیم Global Scope.

Custom value resolvers

کلاس‌های زیر رو در نظر بگیرید

public class Person
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }
}

public class PersonDTO
{
    public int Id { get; set; }

    public string RawData { get; set; }
}
فرض کنید داخل RawData  تمامی اعضای شی مبداء رو به صورت Comma Delimited ذخیره کنیم. برای این کار می‌تونیم از Value Resolver استفاده کنیم.
یک روش برای این کار ارث بری از کلاس Abstract  ی بنام ValueResolver<TSource, TDestination> هست.
public class CommaDelimetedResolver:ValueResolver<Person,string>
    {
        protected override string ResolveCore(Person source)
        {
            return string.Join(",", source.Id, source.FirstName, source.LastName);
        }
    }
و نحوه استفاده
Mapper.CreateMap<Person, PersonDTO>().ForMember(
                des => des.RawData, op => op.ResolveUsing<CommaDelimetedResolver>());


var person = new Person
{
Id = 1,
FirstName = "Mohammad",
LastName = "Saheb",
};

var personDTO = Mapper.Map<Person, PersonDTO>(person);
و خروجی به شکل زیر میشه

نکته: توجه کنید این فقط یک مثال بود و این کار رو با روش‌های دیگه هم میشه انجام داد مثلا MapFrom  و...
نکته: میدان دید Value Resolver‌ها سراسری نیست و باید به ازای هر نگاشتی اونو معرفی کنیم.

Custom Value Formatters
فرض کنید تاریخ رو در بانک، به صورت میلادی ذخیره کرده‌اید و می‌خواهید سمت View به صورت شمسی نمایش بدید. بنابراین در مبدا ویژگی بنام MiladiDate از نوع DateTime دارید و در مقصد ویژگی بنام ShamsiDate از نوع String. هنگام نگاشت، AutoMapper  به صورت پیش فرض ToString رو فراخونی میکنه که بدرد ما نمیخوره و...
برای این کار میشه  از Value Formatter استفاده کرد با پیاده سازی اینترفیس IValueFormatter.
public class ShamsiFormatter:IValueFormatter
    {
        public string FormatValue(ResolutionContext context)
        {
            return ToShamsi(context.SourceValue.ToString());
        }
    }
نحوه استفاده
Mapper.CreateMap<Person, PersonDTO>().ForMember(
            des => des.ShamsiDate, op => op.AddFormatter<ShamsiFormatter>());
مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت اول
در پست قبلی مقدمه‌ای داشتیم بر AutoMapper؛ مثالی که در اون پست عنوان شد ساده‌ترین و پرکاربردترین روش استفاده از AutoMapper هست بنام Flattening که در واقع از یک شیء کل به یک شیء کوچکتر می‌رسیم.
همانطور که در قسمت اول گفتم AutoMapper کارش رو بر اساس قراردادها انجام میده یا همون Convention Base. یکی از قردادهای AutoMapper، نگاشت براساس نام اعضای اون شی هست؛ مثلا در مثال قبلی FirstName در مبداء، به خاصیتی با همین نام نگاشت شد و ...

Projection
روش استفاده بعدی که به اون Projection (معنی فارسی خوب برا Projection چیه ؟) میگن برای مواقعی هست که اعضای یک شیء در مبداء، همتایی در مقصد ندارد (از نظر نام) و در واقع می‌خواهیم یک شیء رو به یک شیء دیگه تغییر شکل بدیم.
مدل زیر رو در نظر بگیرید:
  public class Person
    {
        public int Id { get; set; }

        public string FirstName { get; set; }       

        public string LastName { get; set; }

    }

  که قرار است به مدل PersonDTO نگاشت بشه.
  public class PersonDTO
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Family { get; set; }

public string FullName { get; set; }

        public string Email { get; set; }
    }
همنطور که می‌بینید در مقصد خاصیت‌هایی داریم که در مبداء همتایی ندارند. برای نگاشت چنین اشیایی، از متد ForMember  استفاده و نگاشت‌های شی‌های موردنظر رو Custom می‌کنیم.
 Mapper.CreateMap<Person, PersonDTO>().ForMember(des => des.Name, op => op.MapFrom(src => src.FirstName)).
                ForMember(des => des.FullName, op => op.MapFrom(src => src.FirstName + " " + src.LastName)).ForMember(
                    des => des.Email, op => op.Ignore());
متد ForMember  از یک Action Delegate  برای کانفیگ کردن هر عضو استفاده میکنه. در مثال بالا ما از MapFrom برای اعمال نگاشت Custom  استفاده کردیم.
AutoMapper.IMappingExpression<TSource,TDestination>.ForMember(System.Linq.Expressions.Expression<System.Func<TDestination,object>>, System.Action<AutoMapper.IMemberConfigurationExpression<TSource>>)

نکته:برای تست کردن اینکه آیا نگاشت ما Exception  داره یا نه میتوان از متد زیر استفاده کرد.
Mapper.AssertConfigurationIsValid();

نکته
: همیشه موقع نگاشت، اعضای شیء مقصد برای AutoMapper  مهمن؛ مثلا در مثال بالا خاصیت LastName در مبداء به هیچ عضوی در مقصد نگاشت نشده (به صورت مستقیم) و این تولید Exception  نمیکنه ولی برعکس اون باعث تولید Exception میشه؛ مثلا اگه خاصیت Email  رو که در مبداء همتایی نداره رو کانفیگ نکنیم، باعث تولید Exception میشه.
 
نحوه استفاده
var person = new Person
                {
                    Id = 1,
                    FirstName = "Mohammad",
                    LastName = "Saheb",
                    
                };
var personDTO = Mapper.Map<Person, PersonDTO>(person);
خروجی به شکل زیر میشه


Collection
بعد از نوشتن کانفیگ نگاشت‌ها، بدون نیاز به تنظیمات خاصی میتونیم مجموعه ای از شی‌های مبداء رو به مقصد نگاشت کنیم. مجموعه‌های پشتیبانی شده به شرح زیرن.
IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T>, List<T>.
و البته آرایه ها.
مثال
var persons = new[]
                {
                    new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb" },
                    new Person { Id = 2, FirstName = "Babak", LastName = "Saheb" }
                };

var personDTOs = Mapper.Map<Person[], List<PersonDTO>>(persons);
خروجی به شکل زیر میشه

Nested Mappings
برای نگاشت کلاس‌های تو در تو از این روش استفاده می‌کنیم و ...
فرض کنید در کلاس Person خاصیتی از نوع Address داریم و در کلاس PersonDTO  خاصیتی از نوع AddressDTO.
public class Address
    {
        public string Ad1 { get; set; }
        public string Ad2 { get; set; }
    }


public class AddressDTO
    {
        public string Ad1 { get; set; }
        public string Ad2 { get; set; }
    }
برای نگاشت‌هایی از این دست باید تنظیمات نگاشت مربوط به نوع‌های تودرتو را صریحا معین کنیم.
Mapper.CreateMap<Address, AddressDTO>();
نکته: هرچند منطقی‌تر بنظر میرسه که تعریف نگاشت‌های داخلی ابتدا بیاد، ولی فرقی نمیکنه که تعریف این نگاشت قبل یا بعد از نگاشت اصلی باشه.
ادامه دارد...
مطالب
مقایسه سرعت نگاشت AutoMapper

قبل ازاین مقاله، درباره راه اندازی و استفاده از کتابخانه Automapper  بحث شده ولی موردی که شاید کمتر به آن توجه شده سرعت این نگاشت میباشد. در این مقاله با استفاده از نوشتن تست، این موضوع بررسی میشود.

کلاس ساده زیر را در نظر بگیرید که برای مثال از سمت لایه دسترسی به داده گرفته شده است:

public enum PersonType
{
    Real =0,
    Legal=1
}

public class Person
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public Person(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

از سازنده آن برای دریافت مقادیر مربوط به خصوصیات شیء استفاده شد.

در طرف دیگر نیز کلاسی برای نگاشت از آبجکت رسیده از سمت لایه داده ساخته میشود که برای نمایش در ویوها ایجاد شده است: 

public class PersonDto
{
    public long PersonId { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public PersonType PersonType { get; set; }

    public PersonDto(long personId, string name, string family, PersonType personType)
    {
        PersonId = personId;
        Name = name;
        Family = family;
        PersonType = personType;
    }
}

همانطور که مشاهده میکنید در سازنده این کلاس نیز مقادیر خصوصیات، دریافت شده‌است.

برای ایجاد لیستی که در تست مورد استفاده قرار میگیرد نیز کلاس زیر را فراهم میکنیم: 

public class PersonList
{
    readonly List<Person> _list = new List<Person>();
    public ReadOnlyCollection<Person> GetPersons()
    {
        if (!_list.Any())
        {
            for (int i = 0; i < 100*1000; i++)
            {
                _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
            }

        }
        return _list.AsReadOnly();
    }
}

در اینجا برای محسوس بودن نتیجه تست میتوان تعداد آبجکتهای لازم برای تست را تعیین کرد، فعلا 100 هزار آبجکت در نظر گرفته شده است: 

for (int i = 0; i < 100*1000; i++)
{
    _list.Add(new Person(i + 1, "Person Name" + i, "Person Family" + i, (PersonType)(i % 2)));
}

برای ارجاع به AutoMapper، با استفاده از نیوگت، پکیج را به پروژه تست ارجاع میدهیم: (در حال حاضر نسخه 5.1.1 استفاده شده است) 

<package id="AutoMapper" version="5.1.1" targetFramework="net452" />

در سمت تست نگاشت نیز از دو متد برای مقایسه استفاده میکنیم؛ یکی با استفاده از AutoMapper و دیگری بدون استفاده از آن: 

[TestMethod]
public void FillPersonDtoList_AutoMapperShouldMapPersonListToPersonDtoList_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    personDtoList = Mapper.Map<List<PersonDto>>(persons);

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}
[TestMethod]
public void FillPersonDtoList_UsingHandlyAssignment_WhenLargeAmountOfPerson()
{
    // arrange
    var personDtoList = new List<PersonDto>(); persons = new PersonList().GetPersons();

    // act
    foreach (var person in persons)
    {
        personDtoList.Add(new PersonDto(person.PersonId, person.Name, person.Family, person.PersonType));
    }

    //assert
    Assert.AreEqual(persons.Count, personDtoList.Count);
}

سرعت نگاشت AutoMapper در نسخه حال حاضر تقریبا سه بار کندتر از استفاده معمول برای تهیه نگاشت جدید از یک آبجکت است: 

نکته: این تست با نسخه قدیمی تر(4.0.4.0) نیز انجام شده که این اختلاف سرعت نزدیک به 13 بار کندتر هم رسیده است. 

پ.ن: سورس پروژه تست

مطالب
مقدمه ای بر AutoMapper
AutoMapper کتابخانه ای ساده و سبک برای نگاشت اطلاعات یک شی به شی دیگر به صورت خودکار هست و...
اگه این پست رو مطالعه کرده باشید یه مشکل امنیتی بنام «Mass Assignment» مطرح شد.برای رفع این مشکل یک روش استفاده از ViewModel بود.
فرض کنید Model ما
 public class User
    {
        public int Id { get; set; }
       
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string UserName { get; set; }

        public string Password { get; set; }

        public bool IsAdmin { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; }
    }
و ViewModel ما
 public class UserViewModel
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Password { get; set; }
    }
باشه(توجه کنید در واقع من برای View ی مورد نظرم فقط به نام , نام خانوادگی و پسورد نیاز دارم)
برای استفاده UserViewModel بعنوان Model در View ی مورد نظر باید شی UserViewModel رو با اطلاعات شی User مقدار دهی کنیم مثلا با کدی مثل این در کنترلر.
 
public ActionResult Index(int id = 1)
        {
            var user = _userService.GetById(id);
            var userViewModel = new UserViewModel
                {
                    FirstName = user.FirstName,
                    LastName = user.LastName,
                    Password = user.Password
                };

            return View(userViewModel);
        }
رهایی از نوشتن اینجور کدهای تکراری و خسته کننده باعث پیدایش AutoMapper شد...
برای استفاده از AutoMapper از نوگت استفاده میکنیم.
PM> Install-Package AutoMapper
در شروع برنامه نگاشت‌ها رو تعریف میکنم.یک روش ابداعی تعریف نگاشت‌ها در یک کلاس استاتیک و فراخوانی اون تو متد  Application_Start هست.
public static class AutoMapperWebConfiguration
    {

        public static void Configure()
        {
            ConfigureUserMapping();
        }

        private static void ConfigureUserMapping()
        {
            Mapper.CreateMap<User, UserViewModel>();
        }
    }

اولین پارامتر نوع مبدا و دومین پارامتر نوع مقصد هست.
برای انجام نگاشت هم از متد Map استفاده میکنیم.
public ActionResult Index(int id=1)
        {
            var user = _userService.GetById(id);
            var userViewModel=new UserViewModel();
            AutoMapper.Mapper.Map(user, userViewModel);
            
            return View(userViewModel);
        }
همنطور که میبنید با نوشتن چندین خط کد عملیات نگاشت اطلاعات یک شی به شی دیگه انجام شد.
ادامه دارد...
نظرات مطالب
چگونگی گزارشگیری از Business Objects مانند List توسط StimulSoft
با کد زیر
    public class Book
    {
        public string Title { get; set; }
        public int Id { get; set; }
        public Author Author { get; set; }
        public Publisher Publisher { get; set; }

        public IList<Book> GetBooks()
        {
            var books = new List<Book>();

            for (int i = 0; i < 10; i++)
            {
                var book = new Book()
                {
                    Id = i,
                    Title = $"Title {i}",
                    Author = new Author()
                    {
                        FirstName = $"Author {i} First Name",
                        LastName = $"Author {i} Last Name",
                        Person = new Person()
                        {
                            Type = $"Type {i}",
                            Value = $"Value {i}"
                        }
                    },
                    Publisher = new Report.Publisher()
                    {
                        Name = $"Publiser {i}"
                    }
                };
                books.Add(book);
            }
            return books;
        }
    }

    public class Author
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Person Person { get; set; }
    }

    public class Person
    {
        public string Type { get; set; }
        public string Value { get; set; }
    }

    public class Publisher
    {
        public string Name { get; set; }
    }
و فایل mrt زیر مشکلی دیده نشد.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
با سلام و احترام
من سه تا کلاس به شکل زیر دارم
public class Location {
        [Key]
        public int Id { get; set; }
        [Required]
        public string LocationName { get; set; }

        public virtual ICollection<User> Users { get; set; }
        public virtual ICollection<Person> People { get; set; }

    }

   public class Person {
        [Key]
        public int Id { get; set; }

        [MaxLength (250)]
        [Required]
        public string LastName { get; set; }

        [MaxLength (250)]
        [Required]
        public string FirstName { get; set; }


        public int? UserId { get; set; }
        public  virtual User User { get; set; }
        public int? LocationId { get; set; }
        public virtual Location Location { get; set; }

    }

   public class User {
        [Key]
        public int Id { get; set; }

        [Required]
        [MaxLength (250)]
        public string LastName { get; set; }

        [Required]
        [MaxLength (250)]
        public string FirstName { get; set; }

        [Required]
        [MaxLength (250)]
        public string UserName { get; set; }

        [Required]
        [MaxLength (250)]
        public string Password { get; set; }

        public int? LocationId { get; set; }
        public virtual Location Location { get; set; }

        public virtual ICollection<Person> People { get; set; }
}
همانطور که در طراحی کلاسها مشاهده می‌شود 
1- در کلاس Location با استفاده از navigation property‌های Users و People به کاربران و افراد یک مکان دسترسی داریم.
2- در کلاس Person با استفاده از navigation property‌های User و Location میتوان پی برد که این فرد توسط چه کاربری ثبت شده و همچنین فرد در چه مکانی میباشد.
3- در کلاس User نیز با استفاده از navigation property‌های Location و People میتوان پی برد که این کاربر چه افرادی را ثبت کرده و کاربر در چه مکانی میباشد.
مشکل:
1- همانطور که در تعریف این کلاسها مشاهده میکنیم به طور مثال از کلاس کاربر به افراد دسترسی داریم و هر فرد نیز دوباره به کاربر دسترسی دارد و این navigation property به صورت loop به یکدیگر دسترسی دارند و این مسئله در واکشی اطلاعات با زمان زیادی همراه بوده و همچنین در تبدیل آنها به به DTO توسط Automaper مشکلاتی ایجاد می‌کند. و یا به طور مثال یک User به افراد ثبت شده توسط خودش دسترسی دارد و هر فرد دوباره به کاربر دسترسی دارد و ...
2- هنگام  استفاده از متد 
ProjectTo در  automapper موضوع LazyLoading در نظر گرفته نمیشود و تمامی  navigation property ها لود می‌شوند.
سوال : 
1- آیا طراحی کلاسها به این شکل ایراد دارد؟
2- برای حل مشکل این نوع navigation property ها راهکاری وجود دارد؟
3- از متد 
ProjectTo چطور استفاده کنیم تا LazyLoading را لغو نکند؟
4- آیا فعال بودن 
LazyLoading در پروژه‌های وبی اشکالاتی ایجاد میکند؟
نظرات مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
ممنون از پاسختون.
پراپرتی‌ها مقادیر درستی دارند و بعد از نگاشت مقادیر Null میگیرند.
این هم کد‌ها :
public class Kala {
        [Key]
        public int Kala_id { get; set; }

        [DisplayName("نام کالا")]
        public string Name { get; set; }

        [DisplayName("قیمت خرید")]
        public double Fee_Kharid { get; set; }
     
        public virtual Brand Brand { get; set; }
     }

  public class Brand
    {
        [Key]
        public int Brand_id { get; set; }
        public string Brand_Name { get; set; }
        public virtual ICollection<Kala> Kalas { get; set; }
    }

 public class KalaViewModel 
    {
        public int Kala_Id { get; set; }
        public string  Name { get; set; }
        public double Fee_Kharid { get; set; }
        public string Brand_Name { get; set; }
    }

     //Controller
     [HttpGet]
        public ActionResult Index()
        {
            var kala = _Kala_Service.GetAllKalas();
            var brand = _Brand_Service.GetAllBrands();

            var kalaviewmodel = EntityMapper.Map<List<KalaViewModel>>(kala, brand);
            return View(kalaviewmodel);
        }

 protected override void Configure()
        {
            Mapper.CreateMap<Kala, KalaViewModel>();

            Mapper.CreateMap<Brand, KalaViewModel>()
                      .ForMember(des => des.Kala_Id, op => op.Ignore())
                      .ForMember(des => des.Name, op => op.Ignore())
                      .ForMember(x => x.Fee_Kharid, opt => opt.Ignore());

        }
سپاس
مطالب دوره‌ها
تبدیلگر تاریخ شمسی برای AutoMapper
فرض کنید مدل معادل با جدول بانک اطلاعاتی ما چنین ساختاری را دارد:
public class User
{
    public int Id { set; get; }
    public string Name { set; get; }
    public DateTime RegistrationDate { set; get; }
}
و ViewModel ایی که قرار است به کاربر نمایش داده شود این ساختار را دارد:
public class UserViewModel
{
    public int Id { set; get; }
    public string Name { set; get; }
    public string RegistrationDate { set; get; }
}
در اینجا می‌خواهیم حین تبدیل User به UserViewModel، تاریخ میلادی به صورت خودکار، تبدیل به یک رشته‌ی شمسی شود. برای مدیریت یک چنین سناریوهایی توسط AutoMapper، امکان نوشتن تبدیلگرهای سفارشی نیز پیش بینی شده‌است.


تبدیلگر سفارشی تاریخ میلادی به شمسی مخصوص AutoMapper

در ذیل یک تبدیلگر سفارشی مخصوص AutoMapper را با پیاده سازی اینترفیس ITypeConverter آن ملاحظه می‌کنید:
public class DateTimeToPersianDateTimeConverter : ITypeConverter<DateTime, string>
{
    private readonly string _separator;
    private readonly bool _includeHourMinute;
 
    public DateTimeToPersianDateTimeConverter(string separator = "/", bool includeHourMinute = true)
    {
        _separator = separator;
        _includeHourMinute = includeHourMinute;
    }
 
    public string Convert(ResolutionContext context)
    {
        var objDateTime = context.SourceValue;
        return objDateTime == null ? string.Empty : toShamsiDateTime((DateTime)context.SourceValue);
    }
 
    private string toShamsiDateTime(DateTime info)
    {
        var year = info.Year;
        var month = info.Month;
        var day = info.Day;
        var persianCalendar = new PersianCalendar();
        var pYear = persianCalendar.GetYear(new DateTime(year, month, day, new GregorianCalendar()));
        var pMonth = persianCalendar.GetMonth(new DateTime(year, month, day, new GregorianCalendar()));
        var pDay = persianCalendar.GetDayOfMonth(new DateTime(year, month, day, new GregorianCalendar()));
        return _includeHourMinute ?
            string.Format("{0}{1}{2}{1}{3} {4}:{5}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture), info.Hour.ToString("00"), info.Minute.ToString("00"))
            : string.Format("{0}{1}{2}{1}{3}", pYear, _separator, pMonth.ToString("00", CultureInfo.InvariantCulture), pDay.ToString("00", CultureInfo.InvariantCulture));
    } 
}
ITypeConverter دو پارامتر جنریک را قبول می‌کند. پارامتر اول نوع ورودی و پارامتر دوم، نوع خروجی مورد انتظار است. در اینجا باید خروجی متد Convert را بر اساس آرگومان دوم ITypeConverter مشخص کرد. توسط ResolutionContext می‌توان به برای مثال context.SourceValue که معادل DateTime دریافتی است، دسترسی یافت. سپس این DateTime را بر اساس متد toShamsiDateTime تبدیل کرده و بازگشت می‌دهیم.


ثبت و معرفی تبدیلگرهای سفارشی AutoMapper

پس از تعریف یک تبدیلگر سفارشی AutoMapper، اکنون نیاز است آن‌را به AutoMapper معرفی کنیم:
public class TestProfile1 : Profile
{
    protected override void Configure()
    {
        // این تنظیم سراسری هست و به تمام خواص زمانی اعمال می‌شود
        this.CreateMap<DateTime, string>().ConvertUsing(new DateTimeToPersianDateTimeConverter()); 
        this.CreateMap<User, UserViewModel>();
     }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
جهت مدیریت بهتر نگاشت‌های AutoMapper ابتدا یک کلاس Profile را آغاز خواهیم کرد و سپس توسط متدهای CreateMap، کار معرفی نگاشت‌ها را آغاز می‌کنیم.
همانطور که مشاهده می‌کنید در اینجا دو نگاشت تعریف شده‌اند. یکی برای تبدیل User به UserViewModel و دیگری، معرفی نحوه‌ی نگاشت DateTime به string، توسط تبدیلگر سفارشی DateTimeToPersianDateTimeConverter است که به کمک متد الحاقی ConvertUsing صورت گرفته‌است.
باید دقت داشت که تنظیمات تبدیلگرهای سفارشی سراسری هستند و در کل برنامه و به تمام پروفایل‌ها اعمال می‌شوند.


بررسی خروجی تبدیلگر سفارشی تاریخ

اکنون کار استفاده از تنظیمات AutoMapper با ثبت پروفایل تعریف شده آغاز می‌شود:
Mapper.Initialize(cfg => // In Application_Start()
{
     cfg.AddProfile<TestProfile1>();
});
سپس نحوه‌ی استفاده از متد Mapper.Map همانند قبل خواهد بود:
var dbUser1 = new User
{
    Id = 1,
    Name = "Test",
    RegistrationDate = DateTime.Now.AddDays(-10)
};
 
var uiUser = new UserViewModel();

Mapper.Map(source: dbUser1, destination: uiUser);
در اینجا در حین کار تبدیل و نگاشت dbUser به uiUser، زمانیکه AutoMapper به هر خاصیت DateTime ایی می‌رسد، مقدار آن‌را با توجه به تبدیلگر سفارشی تاریخی که به آن معرفی کردیم، تبدیل به معادل رشته‌ای شمسی می‌کند.


نوشتن تبدیلگرهای غیر سراسری

همانطور که عنوان شد، معرفی تبدیلگرها به AutoMapper سراسری است و در کل برنامه اعمال می‌شود. اگر نیاز است فقط برای یک مدل خاص و یک خاصیت خاص آن تبدیلگر نوشته شود، باید نگاشت مورد نظر را به صورت ذیل تعریف کرد:
this.CreateMap<User, UserViewModel>()
             .ForMember(userViewModel => userViewModel.RegistrationDate,
                        opt => opt.ResolveUsing(src =>
                        {
                             var dt = src.RegistrationDate;
                             return dt.ToShortDateString();
                        }));
اینبار در همان کلاس پروفایل ابتدای بحث، نگاشت User به ViewModel آن با کمک متد ForMember، سفارشی سازی شده‌است. در اینجا عنوان شده‌است که اگر به خاصیت ویژه‌ی RegistrationDate رسیدی، مقدار آن‌را با توجه به فرمولی که مشخص شده، محاسبه کرده و بازگشت بده. این تنظیم خصوصی است و به کل برنامه اعمال نمی‌شود.


خصوصی سازی تبدیلگرها با تدارک موتورهای نگاشت اختصاصی

اگر می‌خواهید تنظیمات TestProfile1 به کل برنامه اعمال نشود، نیاز است یک MappingEngine جدید و مجزای از MappingEngine سراسری AutoMapper را ایجاد کرد:
var configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
configurationStore.AddProfile<TestProfile1>();
var mapper = new MappingEngine(configurationStore);
mapper.Map(source: dbUser1, destination: uiUser);
به صورت پیش فرض و در پشت صحنه، متد Mapper.Map از یک MappingEngine سراسری استفاده می‌کند. اما می‌توان در یک برنامه چندین MappingEngine مجزا داشت که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample02.zip
نظرات مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
public class Kala
{
        [Key]
        public int Kala_id { get; set; }
 
        [DisplayName("نام کالا")]
        public string Name { get; set; }
 
        [DisplayName("قیمت خرید")]
        public double Fee_Kharid { get; set; }
      
        public virtual Brand Brand { get; set; }
     }
 
  public class Brand
    {
        [Key]
        public int Brand_id { get; set; }
        public string Brand_Name { get; set; }
        public virtual ICollection<Kala> Kalas { get; set; }
    }
 
 public class KalaViewModel
    {
        public int Kala_Id { get; set; }
        public string  Name { get; set; }
        public double Fee_Kharid { get; set; }
        public string Brand_Name { get; set; }
    }
 
     //Controller
     [HttpGet]
        public ActionResult Index()
        {
            var kala = _Kala_Service.GetAllKalas();
            var brand = _Brand_Service.GetAllBrands();
 
            var kalaviewmodel = EntityMapper.Map<List<KalaViewModel>>(kala, brand);
            return View(kalaviewmodel);
        }
 
 protected override void Configure()
        {
            Mapper.CreateMap<Kala, KalaViewModel>()
            .ForMember(des => des. Brand_Name, op => op.MapFrom(src =>     src.Brand.Brand_Name) );
        }
  کد پایین از لحاظ منطقی هم درست نیست
map کردن Brand با    KalaViewModel  معنایی نداره

نظرات مطالب
معرفی ASP.NET Identity
کلاس کاربر:
public class AppUser : IdentityUser
{
    public string Email { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }

    public virtual UserProfile Profile { get; set; }
}

کلاس پروفایل کاربر:
public class UserProfile
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public DateTime? Birthday { get; set; }

    public byte[] Avatar { get; set; }
}

کلاس کانتکست دیتابیس:
public class SampleDbContext : IdentityDbContext
{
    public SampleDbContext() : base("DefaultConnection") { }

    static SampleDbContext()
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SampleDbContext>());
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Product> Products { get; set; }
    ...
}


این کلاس‌ها می‌تونن تو لایه دیگری مثل Domain Models تعریف بشن.