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