从我做起[AutoMapper实现模块化注册自定义扩展MapTo<>()].Net Core 之二
AutoMapper实现模块化注册自定义扩展MapTo<>()
我们都知道AutoMapper是使用的最多的实体模型映射,如果没有AutoMapper做对象映射那么我们需要想一下是怎么写的,是不是很麻烦写起来很难受这种,自从有了AutoMapper我们的代码量是不是减少了很多,但是.NetCore中的AutoMapper需要配置Profile文件,但是我们这个每次都需要在Starup中去注册,这就很麻烦了,下面我这篇文章来讲一下我们不需要每次去注册,让他实现自动注入而不需要每次都手动注入。
上次回顾
在上篇文章讲了Sukt.Core框架的一个整体架构搭建和怎样实现模块化注册和批量注入,如果没有看过上篇文章的请查看【从我做起[原生DI实现模块化和批量注入].Net Core 之一】,本篇来讲进行AutoMapper的模块化注册;
阶段一
上篇文章有讲到我们有一个自动注册的基类,那么我们这次来回顾一下上次的依赖注入也是重写的这个自动注册基类他就是SuktAppModuleBase,我们的AutoMapper类也是继承与SuktAppModuleBase然后重写它里面的方法;我们的基类方法里面其实就是Startup中的两个方法;我们首先先创建一个Sukt.Core.AutoMapper类库,里面包含一个类SuktMapperModuleBase类继承我们的SuktAppModuleBase,我们重写基类里面的方法在此之前我们先看一下自定义扩展,这个扩展有什么作用呢?其实他的作用就是相同字段不需要配置Profile文件,而不需要我们去进行构造函数注入或者创建一个静态对象,每次使用都要注入或者创建一个静态对象会很烦;所讲的不需要在构造函数注入或者创建静态对象;而是直接自定义扩展方法使用你的对象名称(点)出来的;我们先看使用例子
使用范例:
public async Task<bool> InsertAsync(DataDictionaryInputDto input) { input.NotNull(nameof(input)); var entity = input.MapTo<DataDictionaryEntity>(); }
阶段二
在进行自动化注册之前我们需要在Sukt.Core.Shared中添加我们的AutoMapper扩展类这个扩展类就是我们的AutoMapperExtension扩展方法我们可以看一下这里面的源代码,主要包括以下这些SetMapper()、CheckMapper()、MapTo<TTarget>()、MapTo<TSource, TTarget>()、IEnumerable<TTarget> MapToList<TTarget>()、IQueryable<TOutputDto> ToOutput<TOutputDto>()方法;SetMapper(IMapper mapper)方法主要用来进行对象实例注入、CheckMapper()方法用来检测实例有没有创建成功;自定义扩展方法分为这些,具体里面的代码怎么实现下面;废话少说;直接上代码。
private static IMapper _mapper = null; /// <summary> /// 写入AutoMapper实例 /// </summary> public static void SetMapper(IMapper mapper) { mapper.NotNull(nameof(mapper)); _mapper = mapper; } /// <summary> /// 检查传入的实例 /// </summary> private static void CheckMapper() { _mapper.NotNull(nameof(_mapper)); } /// 将对象映射为指定类型 /// </summary> /// <typeparam name="TTarget">要映射的目标类型</typeparam> /// <param name="source">源对象</param> /// <returns>目标类型的对象</returns> public static TTarget MapTo<TTarget>(this object source) { CheckMapper(); source.NotNull(nameof(source)); return _mapper.Map<TTarget>(source); } /// <summary> /// 使用源类型的对象更新目标类型的对象 /// </summary> /// <typeparam name="TSource">源类型</typeparam> /// <typeparam name="TTarget">目标类型</typeparam> /// <param name="source">源对象</param> /// <param name="target">待更新的目标对象</param> /// <returns>更新后的目标类型对象</returns> public static TTarget MapTo<TSource, TTarget>(this TSource source, TTarget target) { CheckMapper(); source.NotNull(nameof(source)); target.NotNull(nameof(target)); return _mapper.Map(source, target); } /// <summary> /// 将数据源映射为指定<typeparamref name="TTarget"/>的集合 /// </summary> /// <typeparam name="TTarget">动态实体</typeparam> /// <param name="sources">数据源</param> /// <returns></returns> public static IEnumerable<TTarget> MapToList<TTarget>(this IEnumerable<object> sources) { CheckMapper(); sources.NotNull(nameof(sources)); return _mapper.Map<IEnumerable<TTarget>>(sources); } /// <summary> /// 将数据源映射为指定<typeparamref name="TOutputDto"/>的集合 /// </summary> /// <param name="source">数据源</param> /// <param name="membersToExpand">成员展开</param> public static IQueryable<TOutputDto> ToOutput<TOutputDto>(this IQueryable source,params Expression<Func<TOutputDto, object>>[] membersToExpand) { CheckMapper(); return _mapper.ProjectTo<TOutputDto>(source, membersToExpand); }
以上就是自定义扩展方法,使用了这个之后我们不需要在使用AutoMapper的地方使用静态类或者注入属性了;
阶段三
现在看下实现AutoMapper模块化注册;上面说到了我们创建了一个Sukt.Core.AutoMapper类库在这个类库下面有一个SuktMapperModuleBase类,我们继承了SuktAppModuleBase类,现在重写基类里面ConfigureServices方法,先看下代码;
/// <summary> /// 重写SuktAppModuleBase /// </summary> /// <param name="service"></param> /// <returns></returns> public override IServiceCollection ConfigureServices(IServiceCollection service) { var assemblyFinder = service.GetOrAddSingletonService<IAssemblyFinder, AssemblyFinder>(); var assemblys = assemblyFinder.FindAll(); var suktAutoMapTypes = assemblys.SelectMany(x => x.GetTypes()).Where(s => s.IsClass && !s.IsAbstract && s.HasAttribute<SuktAutoMapperAttribute>(true)).Distinct().ToArray(); service.AddAutoMapper(mapper => { this.CreateMapping<SuktAutoMapperAttribute>(suktAutoMapTypes, mapper); },assemblys,ServiceLifetime.Singleton); var mapper = service.GetService<IMapper>();//获取autoMapper实例 AutoMapperExtension.SetMapper(mapper); return base.ConfigureServices(service); }
使用静态扩展方法主要的代码其实这些代码;
var mapper = service.GetService<IMapper>();//获取autoMapper实例
AutoMapperExtension.SetMapper(mapper);
service.AddAutoMapper(mapper =>{ this.CreateMapping<SuktAutoMapperAttribute>(suktAutoMapTypes, mapper);},assemblys,ServiceLifetime.Singleton);
到这里我们还不算完成,因为还有一些代码没有实现;只写了扩展方法但是它怎么知道要转给谁呢;我们就要用到特性了
创建一个SuktAutoMapperAttribute特性这个名字不要和官方的相同,不然会冲突,特性里面包含一下代码,还有一些代码就不贴了,具体代码请看Github【Sukt.Core】和【Destiny.Core.Flow】
/// <summary> /// 如果使用AutoMapper会跟官方冲突,所以在前面加了项目代号 /// </summary> [AttributeUsage(AttributeTargets.Class)] public class SuktAutoMapperAttribute:Attribute { /// <summary> /// 构造函数传值 /// </summary> /// <param name="targetTypes"></param> public SuktAutoMapperAttribute(params Type[] targetTypes) { targetTypes.NotNull(nameof(targetTypes)); TargetTypes = targetTypes; } /// <summary> /// 类型数组 /// </summary> public Type[] TargetTypes { get; private set; } public virtual SuktAutoMapDirection MapDirection { get { return SuktAutoMapDirection.From | SuktAutoMapDirection.To; } } }
我们在SuktMapperModuleBase类中在添加一个静态方法,方法代码;
/// <summary> /// 创建扩展方法 /// </summary> /// <typeparam name="TAttribute"></typeparam> /// <param name="sourceTypes"></param> /// <param name="mapperConfigurationExpression"></param> private void CreateMapping<TAttribute>(Type[] sourceTypes, IMapperConfigurationExpression mapperConfigurationExpression)where TAttribute : SuktAutoMapperAttribute { foreach (var sourceType in sourceTypes) { var attribute = sourceType.GetCustomAttribute<TAttribute>(); if (attribute.TargetTypes?.Count() <= 0) { return; } foreach (var tatgetType in attribute.TargetTypes) { ///判断是To if (attribute.MapDirection.HasFlag(SuktAutoMapDirection.To)) { mapperConfigurationExpression.CreateMap(sourceType, tatgetType); } ///判断是false if (attribute.MapDirection.HasFlag(SuktAutoMapDirection.From)) { mapperConfigurationExpression.CreateMap(tatgetType, sourceType); } } } }
使用的时候在我们的类上配置SuktAutoMapperAttribute要映射到那个对象;看下我的使用方法我这里的是把DataDictionaryInputDto转到DataDictionaryEntity类上;
这里有一点坑哦AutoMapper此方法仅支持相同字段映射;非相同字段还是需要自己写这个文件的,这个文件直接创建一个自己的类继承与AutoMapper的Profile就好,如果使用这个文件则不需要配置上面的特性;这里也会在程序运行时注入到容器内不需要管理实例;
到这里我们的AutoMapper模块化注册和自定义扩展已经完成了,
给个星星! ⭐️
如果你喜欢这篇文章或者它帮助你, 请给 在Github上给Star~(辛苦星咯)
GitHub
Sukt.Core:https://github.com/GeorGeWzw/Sukt.Core
Destiny.Core.Flow:https://github.com/GeorGeWzw/Destiny.Core.Flow
▼-----------------------------------------▼-----------------------------------------▼