Asp.mvc(二)~使用AutoMapper实现领域模型与DTO映射
上一篇中介绍了Core, Data 以及 Services 层,在介绍 Presentation 层之前,我们需要了解下面几个知识点:
- AutoMapper
- Autofac
- 以及上篇博文中 Data 层未详细介绍的 WebActivatorEx
AutoMapper
有时候,你需要将一种类型转换为另外一种类型,这种情况在mvc 项目中较为常见,在数据查询的时候,通过数据持久层将数据绑定到领域模型中,在数据写入的时候,通过数据持久层将绑定到领域模型上的数据保存至数据库。 但是在用户界面上, 我们并不需要将数据完完全全的暴露的用户眼前, 所以在这里我们一般都会使用到 “贫血模式”, 建立一个 ViewModel 层,其作用就是将实际需要的数据定义为模型, 也就是DTO (Data Transfer Object), 在用户查询的时候, 程序通常会将数据保存至领域模型中, 然后将领域模型转换至 DTO,来保证传输的数据都是必须的,这样的一种做法可以减少领域模型与 Presentation 的耦合, 以及完全不需要将数据字段暴露在 Presentation 层,也保证了一定情况下数据的安全性,获取必要的数据, 也可以提高数据传输的效率。
一般情况下,如果将一个对象的部分属性克隆给另外一个对象, 通常的做法就是:
Object A = new Object(args...);
Object B = new Object
{
Property1 = A.Property1,
Property2 = A.Property2,
Property3 = A.Property3...
};
这样的做法存在着较大的不足:
- 冗杂,繁琐
- 灵活性较差
- 都讨厌编写这种无聊的代码…
我们需要一种工具来帮助我们完成这段枯燥无味的工作: http://automapper.org/
根据官网对AutoMapper的介绍:
AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?
可以看出 AutoMapper 就是为了处理这一项枯燥代码的工具。 下面建立一个控制台 Demo 来演示 AutoMapper 的作用:
新建 AutoMapperSample 控制台程序,打开 Nuget 包控制台,键入:
Install-Package AutoMapper
创建以下领域模型:
using System;
using System.Collections.Generic;
namespace AutoMapperSample
{
public partial class Student
{
public string Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public DateTime? Birthday { get; set; }
public string ClassId { get; set; }
public virtual Class Class { get; set; }
}
public partial class Class
{
public Class()
{
this.Students = new List<Student>();
}
public string Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; }
public string GradeId { get; set; }
public virtual Grade Grade { get; set; }
}
public partial class Grade
{
public Grade()
{
this.Classes = new List<Class>();
}
public string Id { get; set; }
public string Name { get; set; }
public List<Class> Classes { get; set; }
}
}
较为清晰的嵌套结构,下面创建 DTO 模型,里面声明一些必要的属性:
using System;
namespace AutoMapperSample
{
public partial class StudentDto
{
/// <summary>
/// 学员编号 --> Student.Id
/// </summary>
public string Id { get; set; }
/// <summary>
/// 姓名 --> Student.Name
/// </summary>
public string Name { get; set; }
/// <summary>
/// 生日 --> Student.Birthday
/// </summary>
public DateTime? Birthday { get; set; }
/// <summary>
/// 班级编号 --> Student.ClassId
/// </summary>
public string ClassId { get; set; }
/// <summary>
/// 班级 --> Student.Class.Name
/// </summary>
public string ClassName { get; set; }
/// <summary>
/// 年级编号 Student.Class.GradeId
/// </summary>
public string GradeId { get; set; }
/// <summary>
/// 年级 Student.Class.Grade.Name
/// </summary>
public string GradeName { get; set; }
}
}
在上面的注释中可以看到一些嵌套的映射关系,下面建立映射,创建 Mapping
using AutoMapper;
namespace AutoMapperSample
{
public static class Mapping
{
/// <summary>
/// 注册映射关系
/// </summary>
public static void Register()
{
Mapper.CreateMap<Student, StudentDto>()
.ForMember(dest => dest.ClassName, mo => mo.MapFrom(src => src.Class.Name))
.ForMember(dest => dest.GradeId, mo => mo.MapFrom(src => src.Class.GradeId))
.ForMember(dest => dest.GradeName, mo => mo.MapFrom(src => src.Class.Grade.Name));
Mapper.CreateMap<StudentDto, Student>()
.ForMember(dest => dest.Gender, mo => mo.Ignore())
.ForMember(dest => dest.Class, mo => mo.Ignore());
}
/// <summary>
/// 领域模型转化为Dto
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static StudentDto ToModel(this Student entity)
{
return Mapper.Map<Student, StudentDto>(entity);
}
/// <summary>
/// Dto转化为领域模型
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public static Student ToEntity(this StudentDto model)
{
return Mapper.Map<StudentDto, Student>(model);
}
/// <summary>
/// 重载 ToEntity, 在已有 Dto模型基础上使用领域模型转换成 Dto
/// </summary>
/// <param name="model"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static Student ToEntity(this StudentDto model, Student entity)
{
return Mapper.Map(model, entity);
}
}
}
把解释都写在注释中了,很详细。下面来测试一下:
using System;
namespace AutoMapperSample
{
class Program
{
static void Main(string[] args)
{
//注册映射关系
Mapping.Register();
//数据初始化
var grade = new Grade { Id = "g001", Name = "一年级" };
var _class = new Class { Id = "c001", Name = "一班", GradeId = grade.Id, Grade = grade };
var student = new Student
{
Id = "s001",
Name = "Cigarette",
Birthday = DateTime.Now,
Gender = "Male",
ClassId = _class.Id,
Class = _class
};
_class.Students.Add(student);
grade.Classes.Add(_class);
//1.Student --> StudentDto
var studentDto = student.ToModel();
//2.StudentDto --> Student
student = studentDto.ToEntity();
//3.StudentDto --> Student(以一个已存在的Student作为基础)
var studentPart = new Student { Gender = "Female", Class = new Class { Name = "new class" } };
student = studentDto.ToEntity(studentPart);
Console.ReadKey();
}
}
}
在1.2.3处分别打上断点,监视情况为: (图片无法上传…所以这里直接Copy了)
- student {AutoMapperSample.Student} AutoMapperSample.Student
+ Birthday {2015/7/22 21:55:38} System.DateTime?
+ Class {AutoMapperSample.Class} AutoMapperSample.Class
ClassId "c001" string
Gender "Male" string
Id "s001" string
Name "Cigarette" string
- studentDto {AutoMapperSample.StudentDto} AutoMapperSample.StudentDto
+ Birthday {2015/7/22 21:55:38} System.DateTime?
ClassId "c001" string
ClassName "一班" string
GradeId "g001" string
GradeName "一年级" string
Id "s001" string
Name "Cigarette" string
- student {AutoMapperSample.Student} AutoMapperSample.Student
+ Birthday {2015/7/22 21:55:38} System.DateTime?
- Class {AutoMapperSample.Class} AutoMapperSample.Class
Grade null AutoMapperSample.Grade
GradeId null string
Id null string
Name "new class" string
+ Students Count = 0 System.Collections.Generic.List<AutoMapperSample.Student>
ClassId "c001" string
Gender "Female" string
Id "s001" string
Name "Cigarette" string
从上面可以看出我们的领域模型对象student转换为studentDto之后,根据我们所配置的映射关系,数据已经完全映射正确,其实在上面这段代码中,已经解决了两个看似复杂的问题:相同类型不同名的属性映射,嵌套属性映射。
让我们再来看看注册映射规则的重要代码段(Mapping.Register()):
/// <summary>
/// 注册映射关系
/// </summary>
public static void Register()
{
Mapper.CreateMap<Student, StudentDto>()
.ForMember(dest => dest.ClassName, mo => mo.MapFrom(src => src.Class.Name))
.ForMember(dest => dest.GradeId, mo => mo.MapFrom(src => src.Class.GradeId))
.ForMember(dest => dest.GradeName, mo => mo.MapFrom(src => src.Class.Grade.Name));
Mapper.CreateMap<StudentDto, Student>()
.ForMember(dest => dest.Gender, mo => mo.Ignore())
.ForMember(dest => dest.Class, mo => mo.Ignore());
}
通过 Mapper 的静态方法 CreateMap 来创建两个模型之间的映射规则,第一个类型为 Source 即源模型,第二个类型为 Destination 即目标模型,
CreateMap<TSource,TDestination>()
返回一个
IMappingExpression<TSource, TDestination>
,使用它的 ForMember 来定义规则。 前者表示 Student --> StudentDto 的规则,后者则为 StudentDto --> Student 的规则。 这段代码一般都在程序启动后第一时间执行,完成对规则的注册, 在 asp.net mvc项目中, 我们可以使用 WebActivatorEx 来完成这种类型代码的执行,这个是要讲解的第三个点了。 Ok, AutoMapper 的基本使用就已经说得差不多了, 下一篇会了解一下 Autofac 这个依赖注入容器, 以解耦合。