AutoMapper之ABP項目中的使用介紹


最近在研究ABP項目,昨天寫了Castle Windsor常用介紹以及其在ABP項目的應用介紹 歡迎各位拍磚,有關ABP的介紹請看陽光銘睿 博客

AutoMapper只要用來數據轉換,在園里已經有很多這方面文章了,本文主要介紹其在實際項目常用總結,以及在ABP項目中的應用介紹。AutoMapper應用非常簡單,大家稍微看下文檔就可以上手,但是性能不好啊,所以一般用在后台項目,對外的項目千萬不要用。就那NOP來說吧,它也是把AutoMapper放在后台項目使用,商城前台的項目是不敢用的。

有關性能的問題本文沒有涉及到,想了解的請參考EmitMapper,AutoMapper,NLiteMapper和手工映射性能大比拼 和 NLiteMapper與EmitMapper性能簡單比較

下面主要講下項目的入門和項目中的使用。

 

AutoMapper使用只要兩步,配置和Mapper,一般的在項目中我們會在Global中進行配置

配置映射關系

public class Source
{
   public int SomeValue { get; set; }
}

public class Destination
{
   public int SomeValue { get; set; }
}

//這個就是配置映射關系
Mapper.CreateMap<Source, Destination>();

然后就是Mapper

Source source = new Source()
{
    SomeValue = 1
};

var destination = Mapper.Map<Source, Destination>(source);
Console.WriteLine(destination.SomeValue);//1

是不是很簡單,這是最簡單的使用了,當然AutoMapper是個“機關槍”,這個只是它的最簡單使用。下面在介紹幾點常用的功能。還是上面那個例子,只是字段變了

    public class Source
    {
        public int SomeValue { get; set; }
    }

    public class Destination
    {
        public int SomeValuefff { get; set; }
    }

    Mapper.CreateMap<AddressDto, Address>();

這樣子字段都不一樣明細是不能映射的嘛,所有呢我們可以用Mapper.AssertConfigurationIsValid()來驗證,就會AutoMapperConfigurationException異常,

選擇忽略相關字段

Mapper.CreateMap<Source, Destination>()
        .ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());

類型轉換,自定義類型轉換

    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; }
    }

Source要轉Destination,但是第二和第三的字段類型都不一致,所以我們可以自定義類型轉換,下面看下轉換函數ConvertUsing一般最常用第二種,接受一個ITypeConverter

    void ConvertUsing(Func<TSource, TDestination> mappingFunction);
    void ConvertUsing(ITypeConverter<TSource, TDestination> converter);
    void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;

下面看下ITypeConverter接口

    public interface ITypeConverter<TSource, TDestination>
    {
        TDestination Convert(ResolutionContext context);
    }

我們可以繼承這個接口隊Convert進行重寫

    public class DateTimeTypeConverter : ITypeConverter<string, DateTime>
    {
        public DateTime Convert(ResolutionContext context)
        {
            return System.Convert.ToDateTime(context.SourceValue);
        }
    }

    public class TypeTypeConverter : ITypeConverter<string, Type>
    {
        public Type Convert(ResolutionContext context)
        {
              return context.SourceType;
        }
    }

這樣我們就可以映射了,下面看下完整代碼

    public void Example()
    {
        Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32);
        Mapper.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
        Mapper.CreateMap<string, Type>().ConvertUsing<TypeTypeConverter>();
        Mapper.CreateMap<Source, Destination>();
        Mapper.AssertConfigurationIsValid();

        var source = new Source
        {
            Value1 = "5",
            Value2 = "01/01/2000",
            Value3 = "AutoMapperSamples.GlobalTypeConverters.GlobalTypeConverters+Destination"
        };

        Destination result = Mapper.Map<Source, Destination>(source);
        result.Value3.ShouldEqual(typeof (Destination));
    }

    public class DateTimeTypeConverter : ITypeConverter<string, DateTime>
    {
        public DateTime Convert(ResolutionContext context)
        {
            return System.Convert.ToDateTime(context.SourceValue);
        }
    }

    public class TypeTypeConverter : ITypeConverter<string, Type>
    {
        public Type Convert(ResolutionContext context)
        {
              return context.SourceType;
        }
    }

好了,上面把 AutoMapper在項目中常用的方法都介紹完了,再介紹ABP之前我們先看下NOP是怎么使用的吧,由於代碼較長省略部分

    public class AutoMapperStartupTask : IStartupTask
    {
        public void Execute()
        {
            //TODO remove 'CreatedOnUtc' ignore mappings because now presentation layer models have 'CreatedOn' property and core entities have 'CreatedOnUtc' property (distinct names)
            
            //address
            Mapper.CreateMap<Address, AddressModel>()
                .ForMember(dest => dest.AddressHtml, mo => mo.Ignore())
                .ForMember(dest => dest.CustomAddressAttributes, mo => mo.Ignore())
                .ForMember(dest => dest.FormattedCustomAddressAttributes, mo => mo.Ignore())
                .ForMember(dest => dest.AvailableCountries, mo => mo.Ignore())
                .ForMember(dest => dest.AvailableStates, mo => mo.Ignore())
                .ForMember(dest => dest.FirstNameEnabled, mo => mo.Ignore())
                .ForMember(dest => dest.FirstNameRequired, mo => mo.Ignore())
                .ForMember(dest => dest.LastNameEnabled, mo => mo.Ignore())
                .ForMember(dest => dest.LastNameRequired, mo => mo.Ignore())
                .ForMember(dest => dest.EmailEnabled, mo => mo.Ignore())
                .ForMember(dest => dest.EmailRequired, mo => mo.Ignore())
                .ForMember(dest => dest.CompanyEnabled, mo => mo.Ignore())
                .ForMember(dest => dest.CompanyRequired, mo => mo.Ignore())
                .ForMember(dest => dest.CountryEnabled, mo => mo.Ignore())
                .ForMember(dest => dest.StateProvinceEnabled, mo => mo.Ignore())
...

好了,終於可以到ABP的了,ABP對AutoMapper的使用總結出來兩點,1、在模塊中初始化配置,2、遍歷bin目錄下所有的Types判斷哪些類是否被定義為需要轉換的Attribute

在模塊中初始化配置

    public class AbpAutoMapperModule : AbpModule
    {
        private readonly ITypeFinder _typeFinder;

        private static bool _createdMappingsBefore;
        private static readonly object _syncObj = new object();
        
        public AbpAutoMapperModule(ITypeFinder typeFinder)
        {
            _typeFinder = typeFinder;
        }

        private void FindAndAutoMapTypes()
        {
            var types = _typeFinder.Find(type =>
                type.IsDefined(typeof(AutoMapAttribute)) ||
                type.IsDefined(typeof(AutoMapFromAttribute)) ||
                type.IsDefined(typeof(AutoMapToAttribute))
                );

            foreach (var type in types)
            {
                AutoMapperHelper.CreateMap(type);
            }
        }
    }

AbpAutoMapperModule 模塊會在Global的時候被初始化,然后在PreInitialize的時候回調用到FindAndAutoMapTypes,有關模塊是怎么初始化的我想再寫一篇介紹。下面我們看下_typeFinder吧

上面_typeFinder.Find調用的是TypeFinder的Find方法

        public Type[] Find(Func<Type, bool> predicate)
        {
            return GetAllTypes().Where(predicate).ToArray();
        }

        public Type[] FindAll()
        {
            return GetAllTypes().ToArray();
        }

        private List<Type> GetAllTypes()
        {
            var allTypes = new List<Type>();

            foreach (var assembly in AssemblyFinder.GetAllAssemblies().Distinct())
            {
                try
                {
                    Type[] typesInThisAssembly;

                    try
                    {
                        typesInThisAssembly = assembly.GetTypes();
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        typesInThisAssembly = ex.Types;
                    }

                    if (typesInThisAssembly.IsNullOrEmpty())
                    {
                        continue;
                    }

                    allTypes.AddRange(typesInThisAssembly.Where(type => type != null));
                }
                catch (Exception ex)
                {
                    Logger.Warn(ex.ToString(), ex);
                }
            }

            return allTypes;
        }

好吧,上面代碼有點多,但是很簡單,就是獲取所有的Types,我們看下關鍵代碼AssemblyFinder.GetAllAssemblies()

    public class WebAssemblyFinder : IAssemblyFinder
    {
        /// <summary>
        /// This return all assemblies in bin folder of the web application.
        /// </summary>
        /// <returns>List of assemblies</returns>
        public List<Assembly> GetAllAssemblies()
        {
            var assembliesInBinFolder = new List<Assembly>();

            var allReferencedAssemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
            var dllFiles = Directory.GetFiles(HttpRuntime.AppDomainAppPath + "bin\\", "*.dll", SearchOption.TopDirectoryOnly).ToList();

            foreach (string dllFile in dllFiles)
            {
                var locatedAssembly = allReferencedAssemblies.FirstOrDefault(asm => AssemblyName.ReferenceMatchesDefinition(asm.GetName(), AssemblyName.GetAssemblyName(dllFile)));
                if (locatedAssembly != null)
                {
                    assembliesInBinFolder.Add(locatedAssembly);
                }
            }

            return assembliesInBinFolder;
        }
    }

看看吧,這代碼是或bin目錄下面的dll,好喪心病狂啊,回到剛剛AbpAutoMapperModule 的獲取FindAndAutoMapTypes方法。在獲取所有的Types之后我們就要判斷這個類是否是被標識了

AutoMapAttribute、AutoMapFromAttribute和AutoMapToAttribute

        private void FindAndAutoMapTypes()
        {
            var types = _typeFinder.Find(type =>
                type.IsDefined(typeof(AutoMapAttribute)) ||
                type.IsDefined(typeof(AutoMapFromAttribute)) ||
                type.IsDefined(typeof(AutoMapToAttribute))
                );

            foreach (var type in types)
            {
                AutoMapperHelper.CreateMap(type);
            }
        }

獲取之后再我下的Demo中就只有一個UserDto類被標識了 

namespace AbpDemo.Application.Users.Dto
{
    [AutoMapFrom(typeof(User))]
    public class UserDto : EntityDto<long>
    {
        public string UserName { get; set; }

        public string Name { get; set; }
        
        public string Surname { get; set; }
        
        public string EmailAddress { get; set; }
    }
}

接下來就是遍歷所有的types進行配置了AutoMapperHelper.CreateMap(type);配置也很簡單 看下代碼

        public static void CreateMap<TAttribute>(Type type)
            where TAttribute : AutoMapAttribute
        {
            if (!type.IsDefined(typeof (TAttribute)))
            {
                return;
            }

            foreach (var autoMapToAttribute in type.GetCustomAttributes<TAttribute>())
            {
                if (autoMapToAttribute.TargetTypes.IsNullOrEmpty())
                {
                    continue;
                }

                foreach (var targetType in autoMapToAttribute.TargetTypes)
                {
                    if (autoMapToAttribute.Direction.HasFlag(AutoMapDirection.To))
                    {
                        Mapper.CreateMap(type, targetType);
                    }

                    if (autoMapToAttribute.Direction.HasFlag(AutoMapDirection.From))
                    {
                        Mapper.CreateMap(targetType, type);                                
                    }
                }
            }
        }

好了,表達能力不是很好,各位就勉強看下吧。終於寫完了。發現作者很喜歡用Attribute來過濾。這邊AutoMapper是這樣,UnitOfWork也是這樣,其實這樣也是挺方便的,有點AOP的切面的感覺,值得學習。 

 

參考文章:

http://www.cnblogs.com/netcasewqs/archive/2011/04/13/2014684.html

http://www.cnblogs.com/repository/archive/2011/04/08/2009713.html

https://github.com/AutoMapper/AutoMapper/wiki/Configuration-validation

http://www.cnblogs.com/youring2/p/automapper.html

http://www.cnblogs.com/1-2-3/p/AutoMapper-Best-Practice.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM