最近在研究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
