什么是AutoMapper
我們都知道,引用類型直接賦值傳遞的是地址,如果直接賦值,則改變一個類的屬性的同時也在改變另一個類。
所以,當我們需要實現像int類型直接賦值更改互不影響的效果時,我們需要映射。
將A類映射賦值到B類的時候,我們就需要一個對象映射器(object-object),也就是AutoMapper。
我們只需要提前配置好要映射的兩個類,即可輕松實現反射。
配置
使用MapperConfiguration配置
創建一個 MapperConfiguration
實例並通過構造函數初始化配置:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo, Bar>();
cfg.AddProfile<FooProfile>();
});
MapperConfiguration
實例可以靜態存儲,也可以存儲在靜態字段或依賴注入容器中。一旦創建,它就不能被更改/修改。
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo, Bar>();
cfg.AddProfile<FooProfile>();
});
注:從9.0開始,靜態 API 不再可用。
使用Profile Instances配置
組織映射配置的一個好方法是使用配置文件。創建從 Profile 繼承的類,並將配置放入構造函數中:
// This is the approach starting with version 5
public class OrganizationProfile : Profile
{
public OrganizationProfile()
{
CreateMap<Foo, FooDto>();
// Use CreateMap... Etc.. here (Profile methods are the same as configuration methods)
}
}
// How it was done in 4.x - as of 5.0 this is obsolete:
// public class OrganizationProfile : Profile
// {
// protected override void Configure()
// {
// CreateMap<Foo, FooDto>();
// }
// }
在早期版本中,使用 Configure
方法而不是構造函數。從版本5開始,Configure ()
就過時了。它將在6.0版本中被刪除。
配置文件中的配置只應用於配置文件中的映射。應用於根配置的配置應用於創建的所有映射。
Assembly Scanning for auto configuration (自動配置程序集掃描)
配置文件可以通過多種方式直接添加到主映射器配置中:
cfg.AddProfile<OrganizationProfile>();
cfg.AddProfile(new OrganizationProfile());
or by automatically scanning for profiles:
或者通過自動掃描檔案:
// Scan for all profiles in an assembly
// ... using instance approach:
var config = new MapperConfiguration(cfg => {
cfg.AddMaps(myAssembly);
});
var configuration = new MapperConfiguration(cfg => cfg.AddMaps(myAssembly));
// Can also use assembly names:
var configuration = new MapperConfiguration(cfg =>
cfg.AddMaps(new [] {
"Foo.UI",
"Foo.Core"
});
);
// Or marker types for assemblies:
var configuration = new MapperConfiguration(cfg =>
cfg.AddMaps(new [] {
typeof(HomeController),
typeof(Entity)
});
);
AutoMapper 將掃描指定的程序集,從 Profile
繼承類,並將它們添加到配置中。
Naming Conventions(命名約定)
您可以設置源和目標命名約定
var configuration = new MapperConfiguration(cfg => {
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
這將把以下屬性映射到彼此: property _ name-> PropertyName
您還可以將其設置為每個配置文件級別
public class OrganizationProfile : Profile
{
public OrganizationProfile()
{
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
//Put your CreateMap... Etc.. here
}
}
如果你不需要變數命名原則,你可以使用精確匹配命名協議。
Replacing characters (替換字符)
還可以在成員名稱匹配過程中替換源成員中的單個字符或整個單詞:
public class Source
{
public int Value { get; set; }
public int Ävíator { get; set; }
public int SubAirlinaFlight { get; set; }
}
public class Destination
{
public int Value { get; set; }
public int Aviator { get; set; }
public int SubAirlineFlight { get; set; }
}
We want to replace the individual characters, and perhaps translate a word:
我們想要替換單個字符,或許可以翻譯一個單詞:
var configuration = new MapperConfiguration(c =>
{
c.ReplaceMemberName("Ä", "A");
c.ReplaceMemberName("í", "i");
c.ReplaceMemberName("Airlina", "Airline");
});
Recognizing pre/postfixes 識別前/后綴
有時候,源/目標屬性會有共同的前/后綴,這導致您必須執行一系列自定義成員映射,因為名稱不匹配。為了解決這個問題,您可以識別前/后綴:
public class Source {
public int frmValue { get; set; }
public int frmValue2 { get; set; }
}
public class Dest {
public int Value { get; set; }
public int Value2 { get; set; }
}
var configuration = new MapperConfiguration(cfg => {
cfg.RecognizePrefixes("frm");
cfg.CreateMap<Source, Dest>();
});
默認情況下,AutoMapper 識別前綴“ Get” ,如果您需要清除前綴:
var configuration = new MapperConfiguration(cfg => {
cfg.ClearPrefixes();
cfg.RecognizePrefixes("tmp");
});
Global property/field filtering 全局屬性/字段篩選
默認情況下,AutoMapper 會嘗試映射每個公共屬性/字段。您可以使用屬性/字段過濾器過濾出屬性/字段:
var configuration = new MapperConfiguration(cfg =>
{
// don't map any fields
cfg.ShouldMapField = fi => false;
// map properties with a public or private getter
cfg.ShouldMapProperty = pi =>
pi.GetMethod != null && (pi.GetMethod.IsPublic || pi.GetMethod.IsPrivate);
});
Configuring
Visibility 配置可見性
默認情況下,AutoMapper 只能識別公共成員。它可以映射到私有 setters
,但是如果整個屬性都是 private/internal
,則會跳過 internal/private
方法和屬性。要指示 AutoMapper 識別具有其他可視性的成員,請覆蓋默認過濾器 ShouldMapField
和/或 shouldmapproty
:
var configuration = new MapperConfiguration(cfg =>
{
// map properties with public or internal getters
cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.GetMethod.IsAssembly;
cfg.CreateMap<Source, Destination>();
});
Configuration compilation 配置編譯
由於表達式編譯可能占用位資源,因此 AutoMapper 在第一個映射上編譯類型映射計划。然而,這種行為並不總是可取的,所以你可以告訴 AutoMapper 直接編譯它的映射:
var configuration = new MapperConfiguration(cfg => {});
configuration.CompileMappings();
對於幾百個映射,這可能需要幾秒鍾。
Dependency Injection (依賴注入)
AutoMapper 支持使用靜態服務位置構建自定義值解析器、自定義類型轉換器和值轉換器:
var configuration = new MapperConfiguration(cfg =>
{
cfg.ConstructServicesUsing(ObjectFactory.GetInstance);
cfg.CreateMap<Source, Destination>();
});
或動態服務位置,用於基於實例的容器(包括子/嵌套容器) :
var mapper = new Mapper(configuration, childContainer.GetInstance);
var dest = mapper.Map<Source, Destination>(new Source { Value = 15 });
Queryable Extensions 可查詢擴展
從8.0開始,你可以使用 IMapper。ProjectTo.對於舊版本,您需要將配置傳遞給擴展方法 IQueryable。項目組 < t > (圖像提供者)。
注意 IQueryable。ProjectTo
是比IMappe更有限 的映射,因為只支持基礎 LINQ 提供程序所允許的內容。這意味着不能像對 Map 那樣對值解析器和轉換器使用 DI。
例子
ASP.NET Core
有一個 NuGet 包將與這里描述的默認注入機制一起使用,並在這個項目中使用。
您可以使用配置文件定義配置。然后你讓 AutoMapper 知道哪些程序集是通過在啟動時調用 IServiceCollection 擴展方法 AddAutoMapper 定義的概要文件:
services.AddAutoMapper(profileAssembly1, profileAssembly2 /*, ...*/);
or marker types:
或者標記類型:
services.AddAutoMapper(typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2) /*, ...*/);
現在你可以在運行時將 AutoMapper 注入到你的服務/控制器中:
public class EmployeesController {
private readonly IMapper _mapper;
public EmployeesController(IMapper mapper) => _mapper = mapper;
// use _mapper.Map or _mapper.ProjectTo
}
AutoFac
Ninject
對於那些使用 Ninject 的人來說,這里是一個用於 AutoMapper 的 Ninject 模塊的例子
public class AutoMapperModule : NinjectModule
{
public override void Load()
{
Bind<IValueResolver<SourceEntity, DestModel, bool>>().To<MyResolver>();
var mapperConfiguration = CreateConfiguration();
Bind<MapperConfiguration>().ToConstant(mapperConfiguration).InSingletonScope();
// This teaches Ninject how to create automapper instances say if for instance
// MyResolver has a constructor with a parameter that needs to be injected
Bind<IMapper>().ToMethod(ctx =>
new Mapper(mapperConfiguration, type => ctx.Kernel.Get(type)));
}
private MapperConfiguration CreateConfiguration()
{
var config = new MapperConfiguration(cfg =>
{
// Add all profiles in current assembly
cfg.AddMaps(GetType().Assembly);
});
return config;
}
}
Simple Injector 簡單注射器
工作流程如下:
- 通過 myregistry.Register 注冊你的類型
MapperProvider
允許您直接將IMapper
實例注入到其他類中- 使用
propertythatdependensoniovalueresolver
解析一個值 - 將 IService 注入到
propertythatdependensoniovalueresolver
中,然后就可以使用了
ValueResolver 可以訪問 IService,因為我們通過 MapperConfigurationExpression. ConstructServicesUsing 注冊容器
public class MyRegistrar
{
public void Register(Container container)
{
// Injectable service
container.RegisterSingleton<IService, SomeService>();
// Automapper
container.RegisterSingleton(() => GetMapper(container));
}
private AutoMapper.IMapper GetMapper(Container container)
{
var mp = container.GetInstance<MapperProvider>();
return mp.GetMapper();
}
}
public class MapperProvider
{
private readonly Container _container;
public MapperProvider(Container container)
{
_container = container;
}
public IMapper GetMapper()
{
var mce = new MapperConfigurationExpression();
mce.ConstructServicesUsing(_container.GetInstance);
mce.AddMaps(typeof(SomeProfile).Assembly);
var mc = new MapperConfiguration(mce);
mc.AssertConfigurationIsValid();
IMapper m = new Mapper(mc, t => _container.GetInstance(t));
return m;
}
}
public class SomeProfile : Profile
{
public SomeProfile()
{
var map = CreateMap<MySourceType, MyDestinationType>();
map.ForMember(d => d.PropertyThatDependsOnIoc, opt => opt.MapFrom<PropertyThatDependsOnIocValueResolver>());
}
}
public class PropertyThatDependsOnIocValueResolver : IValueResolver<MySourceType, object, int>
{
private readonly IService _service;
public PropertyThatDependsOnIocValueResolver(IService service)
{
_service = service;
}
int IValueResolver<MySourceType, object, int>.Resolve(MySourceType source, object destination, int destMember, ResolutionContext context)
{
return _service.MyMethod(source);
}
}
Castle Windsor
對於那些使用Castle Windsor在這里是一個例子
public class AutoMapperInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
// Register all mapper profiles
container.Register(
Classes.FromAssemblyInThisApplication(GetType().Assembly)
.BasedOn<Profile>().WithServiceBase());
// Register IConfigurationProvider with all registered profiles
container.Register(Component.For<IConfigurationProvider>().UsingFactoryMethod(kernel =>
{
return new MapperConfiguration(configuration =>
{
kernel.ResolveAll<Profile>().ToList().ForEach(configuration.AddProfile);
});
}).LifestyleSingleton());
// Register IMapper with registered IConfigurationProvider
container.Register(
Component.For<IMapper>().UsingFactoryMethod(kernel =>
new Mapper(kernel.Resolve<IConfigurationProvider>(), kernel.Resolve)));
}
}
Catel.IoC
對於那些使用 Catel.IoC 的用戶,下面介紹如何注冊自動控制器。首先使用配置文件定義配置。然后你讓 AutoMapper 知道在哪些程序集中這些配置文件是通過在啟動時在 ServiceLocator 中注冊 AutoMapper 定義的:
配置創建方法:
public static MapperConfiguration CreateConfiguration()
{
var config = new MapperConfiguration(cfg =>
{
// Add all profiles in current assembly
cfg.AddMaps(GetType().Assembly);
});
return config;
}
現在你可以在運行時將 AutoMapper 注入到你的服務/控制器中:
public class EmployeesController {
private readonly IMapper _mapper;
public EmployeesController(IMapper mapper) => _mapper = mapper;
// use _mapper.Map or _mapper.ProjectTo
}
后記
本人不是大佬,只是道路先行者,在落河后,向后來的人大喊一聲,這里有坑,不要過來啊!
縱然如此,依舊有人重復着落河,重復着吶喊······
個人博客網站 Blog
技術交流Q群: 1012481075 群內有各種流行書籍資料
文章后續會在公眾號更新,微信搜索 OneByOneDotNet 即可關注。
你的一分鼓勵,我的十分動力,點贊免費,感恩回饋。喜歡就點贊評論吧,雙擊6666~