ASP.NET Core Web Application Series (five) - Use AutoMapper for physical mapping in ASP.NET Core

This chapter brief introduction on how to use AutoMapper for physical mapping in ASP.NET Core. Before officially entering the theme we look at several concepts:

1, the database persistent object PO (Persistent Object) : As the name suggests, this object is used to our data persisted to the database, in general, the persistent object fields in the database table corresponding consistent.

2, the view object VO (View Object) : VO is the view object oriented front-end user page, all data fields normally contain an entire page / assembly presented to the user contained.

3, the data transfer object DTO (the Data Transfer Object) : Data Transfer Object DTO typically for data transfer between the front end and back-end presentation layer service layer, to complete the form of a media database of persistent data objects and view objects between transfer.

4, AutoMapper is a OOM (Object-Object-Mapping) component , can be seen from the name, this series components mainly to help us achieve mutual conversion between the entities, thereby avoiding the use of hand every time we write code way conversion.

 

The next formal entry into the subject of this chapter, we directly through a use case to illustrate how to use AutoMapper for physical mapping in ASP.NET Core.

Before we add on the basis of a web project TianYa.DotNetShare.AutoMapperDemo for demo AutoMapper, first look at our solutions:

Respectively for the above project a simple explanation:

1, TianYa.DotNetShare.Model: the physical layer for the demo

2, TianYa.DotNetShare.Repository: for the storage layer, i.e., the data access layer demo

3, TianYa.DotNetShare.Service: for the demo of the service that is the business logic layer

4, TianYa.DotNetShare.SharpCore: Sharp as a demo of the core libraries

5, TianYa.DotNetShare.AutoMapperDemo: for the demo web tier projects, MVC framework

Conventions:

1, a public library, we chose .NET Standard 2.0 as the target frames may be shared with the Framework.

2, the demo web project to ASP.NET Core Web Application (.NET Core 2.2) MVC framework.

In addition to those mentioned above, the other in the previous article it has been described in detail before, this demo did not use do not do too much elaborated.

First, the physical layer

To demonstrate Next we create some entities, as shown below:

 1, the student class database persistent objects Student (PO)

using System;
using System.Collections.Generic;
using System.Text;

namespace TianYa.DotNetShare.Model
{
    /// <summary>
    /// 学生类
    /// </summary>
    public class Student
    {
        /// <summary>
        /// 唯一ID
        /// </summary>
        public string UUID { get; set; } = Guid.NewGuid().ToString().Replace("-", "");

        /// <summary>
        /// 学号
        /// </summary>
        public string StuNo { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }

        /// <summary>
        /// 出生日期
        /// </summary>
        public DateTime Birthday { get; set; }

        /// <summary>
        /// 年级编号
        /// </summary>
        public short GradeId { get; set; }

        /// <summary>
        /// 是否草稿
        /// </summary>
        public bool IsDraft { get; set; } = false;

        /// <summary>
        /// 学生发布的成果
        /// </summary>
        public virtual IList<TecModel> Tecs { get; set; }
    }
}

2、学生类视图对象V_Student(VO)

using System;
using System.Collections.Generic;
using System.Text;

namespace TianYa.DotNetShare.Model.ViewModel
{
    /// <summary>
    /// 学生视图对象
    /// </summary>
    public class V_Student
    {
        /// <summary>
        /// 唯一ID
        /// </summary>
        public string UUID { get; set; }

        /// <summary>
        /// 学号
        /// </summary>
        public string StuNo { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        public string Sex { get; set; }

        /// <summary>
        /// 出生日期
        /// </summary>
        public string Birthday { get; set; }

        /// <summary>
        /// 年级编号
        /// </summary>
        public short GradeId { get; set; }

        /// <summary>
        /// 年级名称
        /// </summary>
        public string GradeName => GradeId == 1 ? "大一" : "未知";

        /// <summary>
        /// 发布的成果数量
        /// </summary>
        public short TecCounts { get; set; }

        /// <summary>
        /// 操作
        /// </summary>
        public string Operate { get; set; }
    }
}

3、用于配合演示的成果实体

using System;
using System.Collections.Generic;
using System.Text;

namespace TianYa.DotNetShare.Model
{
    /// <summary>
    /// 成果
    /// </summary>
    public class TecModel
    {
        /// <summary>
        /// 唯一ID
        /// </summary>
        public string UUID { get; set; } = Guid.NewGuid().ToString().Replace("-", "");

        /// <summary>
        /// 成果名称
        /// </summary>
        public string TecName { get; set; }
    }
}

二、服务层

 本demo的服务层需要引用以下几个程序集:

1、我们的实体层TianYa.DotNetShare.Model

2、我们的仓储层TianYa.DotNetShare.Repository

3、需要从NuGet上添加AutoMapper

约定:

1、服务层接口都以“I”开头,以“Service”结尾。服务层实现都以“Service”结尾。

为了演示,我们新建一个Student的服务层接口IStudentService.cs

using System;
using System.Collections.Generic;
using System.Text;

using TianYa.DotNetShare.Model;
using TianYa.DotNetShare.Model.ViewModel;

namespace TianYa.DotNetShare.Service
{
    /// <summary>
    /// 学生类服务层接口
    /// </summary>
    public interface IStudentService
    {
        /// <summary>
        /// 根据学号获取学生信息
        /// </summary>
        /// <param name="stuNo">学号</param>
        /// <returns>学生信息</returns>
        Student GetStuInfo(string stuNo);

        /// <summary>
        /// 获取学生视图对象
        /// </summary>
        /// <param name="stu">学生数据库持久化对象</param>
        /// <returns>学生视图对象</returns>
        V_Student GetVStuInfo(Student stu);
    }
}

接着我们同样在Impl中新建一个Student的服务层实现StudentService.cs,该类实现了IStudentService接口

using System;
using System.Collections.Generic;
using System.Text;

using TianYa.DotNetShare.Model;
using TianYa.DotNetShare.Model.ViewModel;
using TianYa.DotNetShare.Repository;
using AutoMapper;

namespace TianYa.DotNetShare.Service.Impl
{
    /// <summary>
    /// 学生类服务层
    /// </summary>
    public class StudentService : IStudentService
    {
        /// <summary>
        /// 定义仓储层学生抽象类对象
        /// </summary>
        protected IStudentRepository StuRepository;

        /// <summary>
        /// 实体自动映射
        /// </summary>
        protected readonly IMapper _mapper;

        /// <summary>
        /// 空构造函数
        /// </summary>
        public StudentService() { }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="stuRepository">仓储层学生抽象类对象</param>
        /// <param name="mapper">实体自动映射</param>
        public StudentService(IStudentRepository stuRepository, IMapper mapper)
        {
            this.StuRepository = stuRepository;
            _mapper = mapper;
        }

        /// <summary>
        /// 根据学号获取学生信息
        /// </summary>
        /// <param name="stuNo">学号</param>
        /// <returns>学生信息</returns>
        public Student GetStuInfo(string stuNo)
        {
            var stu = StuRepository.GetStuInfo(stuNo);
            return stu;
        }

        /// <summary>
        /// 获取学生视图对象
        /// </summary>
        /// <param name="stu">学生数据库持久化对象</param>
        /// <returns>学生视图对象</returns>
        public V_Student GetVStuInfo(Student stu)
        {
            return _mapper.Map<Student, V_Student>(stu);
        }
    }
}

最后添加一个实体映射配置类MyProfile,该类继承于AutoMapper的Profile类,在配置类MyProfile的无参构造函数中通过泛型的 CreateMap 方法写映射规则。

using System;
using System.Collections.Generic;
using TianYa.DotNetShare.Model;
using TianYa.DotNetShare.Model.ViewModel;

namespace TianYa.DotNetShare.Service.AutoMapperConfig
{
    /// <summary>
    /// 实体映射配置类
    /// </summary>
    public class MyProfile : AutoMapper.Profile
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public MyProfile()
        {
            // 配置 mapping 规则

            CreateMap<Student, V_Student>();
        }
    }
}

服务层就这样了,接下来就是重头戏了,如果有多个实体映射配置类,那么要如何实现一次性依赖注入呢,这个我们在Sharp核心类库中去统一处理。

三、Sharp核心类库

 在之前项目的基础上,我们还需要从NuGet上引用以下2个程序集用于实现实体自动映射:

//AutoMapper基础组件
AutoMapper
//AutoMapper依赖注入辅助组件
AutoMapper.Extensions.Microsoft.DependencyInjection

 1、首先我们添加一个反射帮助类ReflectionHelper

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;

namespace TianYa.DotNetShare.SharpCore
{
    /// <summary>
    /// 反射帮助类
    /// </summary>
    public static class ReflectionHelper
    {
        /// <summary>
        /// 获取类以及类实现的接口键值对
        /// </summary>
        /// <param name="assemblyName">程序集名称</param>
        /// <returns>类以及类实现的接口键值对</returns>
        public static Dictionary<Type, List<Type>> GetClassInterfacePairs(this string assemblyName)
        {
            //存储 实现类 以及 对应接口
            Dictionary<Type, List<Type>> dic = new Dictionary<Type, List<Type>>();
            Assembly assembly = GetAssembly(assemblyName);
            if (assembly != null)
            {
                Type[] types = assembly.GetTypes();
                foreach (var item in types.AsEnumerable().Where(x => !x.IsAbstract && !x.IsInterface && !x.IsGenericType))
                {
                    dic.Add(item, item.GetInterfaces().Where(x => !x.IsGenericType).ToList());
                }
            }

            return dic;
        }

        /// <summary>
        /// 获取指定的程序集
        /// </summary>
        /// <param name="assemblyName">程序集名称</param>
        /// <returns>程序集</returns>
        public static Assembly GetAssembly(this string assemblyName)
        {
            return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName));
        }

        /// <summary>
        /// 获取所有的程序集
        /// </summary>
        /// <returns>程序集集合</returns>
        private static List<Assembly> GetAllAssemblies()
        {
            var list = new List<Assembly>();
            var deps = DependencyContext.Default;
            var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package");//排除所有的系统程序集、Nuget下载包
            foreach (var lib in libs)
            {
                try
                {
                    var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
                    list.Add(assembly);
                }
                catch (Exception)
                {
                    // ignored
                }
            }

            return list;
        }
    }
}

2、接着我们添加一个AutoMapper扩展类

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;

using AutoMapper;

namespace TianYa.DotNetShare.SharpCore.Extensions
{
    /// <summary>
    /// AutoMapper扩展类
    /// </summary>
    public static class AutoMapperExtensions
    {
        /// <summary>
        /// 通过反射批量注入AutoMapper映射规则
        /// </summary>
        /// <param name="services">服务</param>
        /// <param name="assemblyNames">程序集数组 如:["TianYa.DotNetShare.Repository","TianYa.DotNetShare.Service"],无需写dll</param>
        public static void RegisterAutoMapperProfiles(this IServiceCollection services, params string[] assemblyNames)
        {
            foreach (string assemblyName in assemblyNames)
            {
                var listProfile = new List<Type>();
                var parentType = typeof(Profile);
                //所有继承于Profile的类
                var types = assemblyName.GetAssembly().GetTypes()
                    .Where(item => item.BaseType != null && item.BaseType.Name == parentType.Name);

                if (types != null && types.Count() > 0)
                {
                    listProfile.AddRange(types);
                }

                if (listProfile.Count() > 0)
                {
                    //映射规则注入
                    services.AddAutoMapper(listProfile.ToArray());
                }
            }
        }

        /// <summary>
        /// 通过反射批量注入AutoMapper映射规则
        /// </summary>
        /// <param name="services">服务</param>
        /// <param name="profileTypes">Profile的子类</param>
        public static void RegisterAutoMapperProfiles(this IServiceCollection services, params Type[] profileTypes)
        {
            var listProfile = new List<Type>();
            var parentType = typeof(Profile);

            foreach (var item in profileTypes)
            {
                if (item.BaseType != null && item.BaseType.Name == parentType.Name)
                {
                    listProfile.Add(item);
                }
            }

            if (listProfile.Count() > 0)
            {
                //映射规则注入
                services.AddAutoMapper(listProfile.ToArray());
            }
        }
    }
}

不难看出,该类实现AutoMapper一次性注入的核心思想是:通过反射获取程序集中继承了AutoMapper.Profile类的所有子类,然后利用AutoMapper依赖注入辅助组件进行批量依赖注入。

四、Web层

本demo的web项目需要引用以下几个程序集:

1、TianYa.DotNetShare.Model 我们的实体层

2、TianYa.DotNetShare.Service 我们的服务层

3、TianYa.DotNetShare.SharpCore 我们的Sharp核心类库

到了这里我们所有的工作都已经准备好了,接下来就是开始做注入工作了。

打开我们的Startup.cs文件进行注入工作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TianYa.DotNetShare.SharpCore.Extensions;
using TianYa.DotNetShare.Service.AutoMapperConfig;

namespace TianYa.DotNetShare.AutoMapperDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //DI依赖注入,批量注入指定的程序集
            services.RegisterTianYaSharpService(new string[] { "TianYa.DotNetShare.Repository", "TianYa.DotNetShare.Service" });
            //注入AutoMapper映射规则
            //services.RegisterAutoMapperProfiles(typeof(MyProfile)); //如果MyProfile写在Web项目中可通过该方式注入AutoMapper映射规则
            services.RegisterAutoMapperProfiles(new string[] { "TianYa.DotNetShare.Service" });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

其中用来实现批量依赖注入的只要简单的几句话就搞定了,如下所示:

//DI依赖注入,批量注入指定的程序集
services.RegisterTianYaSharpService(new string[] { "TianYa.DotNetShare.Repository", "TianYa.DotNetShare.Service" });
//注入AutoMapper映射规则
//services.RegisterAutoMapperProfiles(typeof(MyProfile)); //如果MyProfile写在Web项目中可通过该方式注入AutoMapper映射规则
services.RegisterAutoMapperProfiles(new string[] { "TianYa.DotNetShare.Service" });

Sharp核心类库在底层实现了批量注入的逻辑,程序集的注入必须按照先后顺序进行,先进行仓储层注入然后再进行服务层注入。

接下来我们来看看控制器里面怎么弄:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using AutoMapper;

using TianYa.DotNetShare.AutoMapperDemo.Models;
using TianYa.DotNetShare.Model;
using TianYa.DotNetShare.Model.ViewModel;
using TianYa.DotNetShare.Service;

namespace TianYa.DotNetShare.AutoMapperDemo.Controllers
{
    public class HomeController : Controller
    {
        /// <summary>
        /// 自动映射
        /// </summary>
        private readonly IMapper _mapper;

        /// <summary>
        /// 定义服务层学生抽象类对象
        /// </summary>
        protected IStudentService StuService;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        public HomeController(IMapper mapper, IStudentService stuService)
        {
            _mapper = mapper;
            StuService = stuService;
        }

        public IActionResult Index()
        {
            var model = new Student
            {
                StuNo = "100001",
                Name = "张三",
                Age = 18,
                Sex = "",
                Birthday = DateTime.Now.AddYears(-18),
                GradeId = 1,
                Tecs = new List<TecModel>
                {
                    new TecModel
                    {
                        TecName = "成果名称"
                    }
                }
            };

            var v_model = _mapper.Map<Student, V_Student>(model);
            var v_model_s = StuService.GetVStuInfo(model);

            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

到这里我们基础的自动实体映射就算是处理好了,最后我们调试起来看下结果:

 

 小结:

1、在该例子中我们实现了从Student(PO) 到 V_Student(VO)的实体映射。

2、可以发现在服务层中也注入成功了,说明AutoMapper依赖注入辅助组件的注入是全局的。

3、AutoMapper 默认是通过匹配字段名称和类型进行自动匹配,所以如果你进行转换的两个类中的某些字段名称不一样,这时我们就需要进行手动的编写转换规则。

4、在AutoMapper中,我们可以通过 ForMember 方法对映射规则做进一步的加工。

 

 未完待续......

 

参考博文:https://mp.weixin.qq.com/s/DyBEQbWQ0-IRP1Pkl_9RbA

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

Guess you like

Origin www.cnblogs.com/xyh9039/p/11964267.html
Recommended