哈嘍你們週四好,時間是過的真快,這幾天一直忙着在公司的項目,而後帶帶新人,眼看這周要過去了,仍是要抽出時間學習學習,這些天看到羣裏的小夥伴也都在忙着新學習,仍是很開心的,至少當時的初衷已經達到了,一塊兒學習一塊兒進步嘛,哪怕是對如今或者是對之後的工做有一丟丟的幫助,也是不枉此時的努力,哈哈夜裏寫文章老是容易多想,好啦,廢話很少說,上次我們說到了《從壹開始微服務 [ DDD ] 之七 ║項目第一次實現 & CQRS初探》,今天原本應該接着寫 領域命令 了,在設計的領域命令的時候,發現了值對象的存在,對 領域模型 和 視圖模型 有着剪不斷理還亂的困擾,因此我就暫時單寫一篇了,既是對上一篇的補充,又是對領域命令的鋪墊,好啦,立刻開始今天的說明吧~~html
仍是老規矩,每篇文章先給你們一個小問題,先思考下,而後有助於理解本文:前端
問題:咱們在領域模型 Student 中,有一個戶籍的值對象(爲啥叫戶籍,下邊會說到),而後咱們也有一個學生的視圖模型 StudentViewModel ,那麼問題來了,咱們在 StudentViewModel 中,如何去定義這個戶籍的視圖模型呢,而後又是如何傳給領域模型 Student 呢?git
一、不寫這戶籍一塊,直接在業務邏輯裏,手動賦值給 Student 領域模型github
public class StudentViewModel { [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其餘,只是學生的我的信息,不涉及戶籍地址 }
二、和領域模型同樣,也寫一個對象,甚至直接就用領域模型中的 Address 值對象數據庫
public class StudentViewModel { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其餘的信息 //這個就是在領域模型Student中使用的,戶籍值對象 public Address Address { get; set; } }
三、把 Address 屬性拆開,一個一個的放在視圖模型 StudentViewModel 中後端
public class StudentViewModel { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其餘學生信息,好比手機號,郵箱等
/// <summary> /// 城市 /// </summary> public string City { get; set; }//注意這裏能夠進行set 賦值操做,和值對象不是一回事 /// <summary> /// 區縣 /// </summary> public string County { get; set; } /// <summary> /// 街道 /// </summary> public string Street { get; set; } }
或許你還有其餘啥辦法,要是有感受更好的,或者更正確的,千萬要評論留言喲,只不過這三種辦法是我親身實驗的,這裏你們先思考一下,但願看完本文你會有一些本身的想法。app
話說上次我們是把領域模型(包括實體和值對象)經過EFCore保存到了數據庫,而後也查詢出來了相應的學習信息,(這裏注意下,學習的戶籍信息尚未取出來),這裏說一下爲何是戶籍地址信息,函數
上篇文章中,有小夥伴仍是對這個不是很理解,一直想着要必定和數據庫對應上,好比說,爲啥叫地址,那若是學生有多個地址咋辦;再好比,這樣修改學生信息,值對象就會發生變化呀,這樣就不能知足值對象不可變的特性;等等諸如此類的疑問,這裏說一下:微服務
一、值對象其實就是一個值,它和Name、Phone、Email等等如出一轍,只不過它是一個對象,複雜了一些,有了本身的內部結構,因此說,值對象是沒有狀態的,沒有惟一標識(多個學生叫張三 == 兩個學生一個地址),是內部不可變性,就好比咱們修改一個學校省份,須要將整個值對象都修改,這和修改Name是同樣的。post
二、值對象是一個領域中孕育出來的概念,千萬不要事事都要和數據庫,數據模型,扯上關係,若是想要一個會員多個地址,那這個時候地址就是一個實體,甚至是一個聚合了,好比物流地址,這也就是我爲何要把這個Address稱之爲 戶籍 的緣由了,從領域出發,而不要再和數據模型數據庫表相提並論了。
那我們就先添加學生的 Create 模塊
// GET: Student/Create // 頁面 public ActionResult Create() { return View(); } // POST: Student/Create // 方法 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(StudentViewModel studentViewModel) { try { // 視圖模型驗證 if (!ModelState.IsValid) return View(studentViewModel); // 執行添加方法 _studentAppService.Register(studentViewModel); ViewBag.success = "Student Registered!"; return View(studentViewModel); } catch(Exception e) { return View(e.Message); } }
這個時候你們確定都已經很熟悉了,並且 Service 層注入什麼的,相信你們已經駕輕就熟了,這裏都不細說了。
@model Christ3D.Application.ViewModels.StudentViewModel @{ ViewData["Title"] = "Register new Student"; } <h2>@ViewData["Title"]</h2> <form asp-action="Create"> <div class="form-horizontal"> <hr /> @* Replacing classic Validation Summary to Custom ViewComponent as TagHelper *@ <vc:summary /> <div class="form-group"> <label asp-for="Name" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Name" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Email" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Phone" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Phone" class="form-control" /> <span asp-validation-for="Phone" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="BirthDate" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="BirthDate" class="form-control" /> <span asp-validation-for="BirthDate" class="text-danger"></span> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-success" /> <a asp-action="Index" class="btn btn-info">Back to List</a> </div> </div> </div> </form>
這些都是 AspNetCore.Mvc.ViewFeature 的模型命令還有驗證等,相比之前的模型,已經有很大的改善了,這個能夠本身試試,很簡單,直接往下走,重頭戲來了。
這個時候,若是咱們添加信息保存的話,必定會發現一個問題,就是戶籍信息到底如何傳入呢,上邊說的三種辦法到底該選擇哪種呢,下邊我們一一來實驗下。
這個時候確定會有小夥伴說,爲何必定要把值對象放到視圖模型中,就好比文章的第一個方法,我就不放進去,我從頁面內獲取到Country、Province、City等等後,而後再傳到領域模型不就好了,真的麼?
假設咱們已經從前臺頁面內獲取到了戶籍信息,而後咱們就會這麼作(紅色部分)
public ActionResult Create(StudentViewModel studentViewModel,string country,string provice,string city,string street) { // 視圖模型驗證 if (!ModelState.IsValid) return View(studentViewModel); //這個時候還須要對戶籍信息進行驗證判斷 //好比字符串不能數字,字符啥的 // 執行添加方法,把戶籍信息傳遞過去
_studentAppService.Register(studentViewModel,country, provice, city, street); ViewBag.success = "Student Registered!"; return View(studentViewModel); }
Stop!相信我,你確定不會這麼作的,固然,偶爾偶爾咱們會這麼接受一個參數,也偶爾會這麼寫,但是這麼寫確定是不行的,且不說不是DDD領域驅動設計思想,就連OOP思想也沒有發揮起來,因此方法一直接pass。
這個時候咱們開始思考,至少須要把戶籍信息放到視圖模型 StudentViewMode 中吧,嗯看着文章開頭的第二個方法就特別好!對象是吧,這個但是真是的OOP思想,所有用對象接收參數,而後把數據傳如到倉儲的Add()方法中,這樣就直接保存了嘛,多好呀!想一想的心動,那就開始吧,一個小坑正在慢慢變大。
聽着很拗口,說白了,就是文章開頭的第二種方法,領域模型和視圖模型,共用一個 值對象。而後咱們修改下 view 頁面,用來傳遞參數。
<div class="form-group"> <label asp-for="BirthDate" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="BirthDate" class="form-control" /> <span asp-validation-for="BirthDate" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.County" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.County" class="form-control" /> <span asp-validation-for="Address.County" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.Province" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.Province" class="form-control" /> <span asp-validation-for="Address.Province" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.City" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.City" class="form-control" /> <span asp-validation-for="Address.City" class="text-danger"></span> </div> </div>
這個時候,咱們必定很歡喜,而後點擊提交,發現,不管怎麼提交都不會在
public ActionResult Create(StudentViewModel studentViewModel)
中獲取到咱們須要的戶籍信息,天哪!這是啥狀況,固然是獲取不到的,由於 Address 是一個值對象,具備不可變性,它的 set 都是私有的,不能被賦值,不信請看
這個時候怎麼辦,聰明的你確定能想到一個方法,既然值對象不行,它內部不可變,不能賦值,那我就本身在視圖模型中,再寫一個 AddressViewModel 不就行啦,而後能夠進行set操做,想到這裏仍是很激動,趕忙試試,這就看看能不能獲取到值。
很不錯,已經把內容獲取到了,而後經過視圖對象傳到Add() 方法,很成功的達到了目的。
看來這個方法也是能夠的,只不過有一個小問題就是,這裏須要多了一個類來實現,若是我不想用類接受,並且是直接用屬性呢?那就是第三種辦法了,請繼續往下看。
就是文章開頭的第三種辦法,這樣的:
public class StudentViewModel { [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 其餘 /// <summary> /// 省份 /// </summary> [Required(ErrorMessage = "The Province is Required")] [DisplayName("Province")] public string Province { get; set; } /// <summary> /// 城市 /// </summary> public string City { get; set; } /// <summary> /// 區縣 /// </summary> public string County { get; set; } /// <summary> /// 街道 /// </summary> public string Street { get; set; } }
而後再修改下頁面裏的調用狀況,直接用調用屬性
<div class="form-group"> <label asp-for="Province" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Province" class="form-control" /> <span asp-validation-for="Province" class="text-danger"></span> </div> </div>
這個時候,咱們滿懷開心的運行項目的時候,發現,index頁面的戶籍信息沒有了,也就是說 Student -> StudentViewModel 的時候,經過 Automapper 沒有成功。
而後咱們提交的時候,發現後端雖然能接受到數據,
但是在轉換到 Student 的時候失敗了:
這裏顯示的是,咱們沒法對其進行轉換,由於在視圖模型中,沒有匹配到 Student 的 Address 值對象信息,不要慌,下邊咱們會說這個問題。
爲了解決上一個問題,我研究了下 Automapper 官網,發現,這種複雜拷貝,須要進行手動配置,其實也是很簡單,只須要建立匹配屬性便可
注意,在第二種方法中是不須要配置的,由於第二種方法,兩個模型結構幾乎如出一轍,這第三種方法,結構已經變了,一個是對象,一個僅僅是一個屬性值。
/// <summary> /// 配置構造函數,用來建立關係映射 /// </summary> public DomainToViewModelMappingProfile() { CreateMap<Student, StudentViewModel>() .ForMember(d => d.County, o => o.MapFrom(s => s.Address.County)) .ForMember(d => d.Province, o => o.MapFrom(s => s.Address.Province)) .ForMember(d => d.City, o => o.MapFrom(s => s.Address.City)) .ForMember(d => d.Street, o => o.MapFrom(s => s.Address.Street)) ; }
這個時候,咱們看Index頁面,戶籍信息也出來了
public ViewModelToDomainMappingProfile()
{
//手動進行配置
CreateMap<StudentViewModel, Student>() .ForPath(d => d.Address.Province, o => o.MapFrom(s => s.Province)) .ForPath(d => d.Address.City, o => o.MapFrom(s => s.City)) .ForPath(d => d.Address.County, o => o.MapFrom(s => s.County)) .ForPath(d => d.Address.Street, o => o.MapFrom(s => s.Street)) ; }
這裏將 Student 中的戶籍信息,一一匹配到視圖模型中的屬性。
而後咱們測試數據,不只僅能夠把數據獲取到,還能夠成功的轉換過去:
最後首頁查看驗證信息,以及添加上了,完成。
今天呢,是補充了上一把的坑,一共提供了三個辦法,固然其實第一種也不算是方法,主要是後二者,不知道你們是否能看的懂,而後更傾向於哪種:
二、不用配置 Automapper 映射信息,只須要新建一個同樣的戶籍值對象的視圖模型 —— 戶籍視圖模型便可,由於結構相同,因此不須要手動配置映射,就能達到目的。
三、只須要一個視圖模型便可控制,在某些狀況下,咱們不方便使用嵌套的複雜視圖模型,只須要配置下映射文件便可達到目的。
今天,也爲下一篇作準備,怎麼說呢,你們發現,如今咱們能正確的添加進去了,可是若是咱們要進行驗證該怎麼辦?好比說,咱們要判斷學校不能小於14歲,手機號格式,郵箱格式等等,
固然,你能夠說,我會用前端js校驗,也能夠後端獲取到,if 判斷,都是能夠的,
不過我我的感受,後端校驗仍是很須要的,我採用 FluentValidation 進行後端校驗,而且融入到 領域命令 中,那如何實現呢,下次再見咯~~~