在實際開發中,模型往往被劃分為視圖模型和業務模型兩部分,視圖模型靠近視圖,業務模型靠近業務,但是在具體編碼上,它們之間並不是隔離的。 6.1 視圖模型和業務模型 模型大多數時候都是用來傳遞數據的。然而即使在傳遞數據這一點上,也可以看出,視圖需要的模型更加靈活一點,因為視圖變化性更大,而處理業務的模型 ...
在實際開發中,模型往往被劃分為視圖模型和業務模型兩部分,視圖模型靠近視圖,業務模型靠近業務,但是在具體編碼上,它們之間並不是隔離的。
6.1 視圖模型和業務模型
模型大多數時候都是用來傳遞數據的。然而即使在傳遞數據這一點上,也可以看出,視圖需要的模型更加靈活一點,因為視圖變化性更大,而處理業務的模型更加穩定一些。因此,在實際開發中,往往有視圖模型和業務模型的區分。在實際開發中,為了體現邏輯的分離,往往是視圖模型和業務模型分別定義。
例如,在傳統三層開發中,我們定義的實體類,可以看作是業務模型的定義,在開發視圖時,可能會開發各種和User相關的視圖,如用戶註冊視圖、用戶狀態列表視圖、登錄視圖,這時候往往根據視圖構造不同的模型,因為它們依賴的數據都不太一樣,這些模型可稱為視圖模型。
這種方式的好處就是可以讓業務模型比較穩定,不會因為視圖的變更而頻繁地去修改業務模型。但是從另一方面來看,這種分離的方式也會有壞處。壞處之一就是帶來很多重覆的代碼。另一個壞處就是在動作方法中,模型自動綁定完視圖模型後,還需要再手動編碼把視圖模型映射到業務模型,帶來額外的工作量。
在小型項目開發中,為了簡化代碼,並不會完全區分視圖模型和業務模型,但不意味著實際開發都這樣做。
6.2 AutoMapper框架
AutoMapper 是一個能夠實現由一個對象到另一個對象間映射的輕量級框架,利用AutoMapper框架可以讓我們從視圖模型到業務模型轉換的繁瑣工作中解放出來。
AutoMapper 框架是基於約定的。
6.2.1 AutoMapper 框架的安裝
新建asp.net mvc 項目 AutoMapperExample,點擊 工具→NuGetB包管理器→管理解決方案的NuGet程式包,在彈出的界面中,搜索autoMapper,在搜索出的程式包然後安裝。
6.2.2 AutoMapper 框架的使用
我們以常見的用戶角色案例來講解AutoMapper 的使用。
首先我們創建角色和用戶兩個類。代碼如下。其中User類中的Role屬性是對Role類的引用。
//角色 public class Role { public int Id { get; set; } public string Name { get; set; } } //用戶 public class User { public int Id { get; set; } public string LoginName { get; set; } public string LoginPwd { get; set; } public string Phone { get; set; } public int RoleId { get; set; } public Role Role { get; set; } } |
在創建註冊用戶時所需的視圖模型。代碼如下。
//註冊視圖中使用的視圖模型 public class RegUserVM { public int Id { get; set; } public string LoginName { get; set; } public string LoginPwd { get; set; } public string ConfirmPwd { get; set; } public string Phone { get; set; } public int RoleId { get; set; } } |
在視圖模型中增加了ConfirmPwd屬性,並將Role屬性去除。
接下來我們在程式中新建AutoMapper文件夾,用於存放對象映射的類,該文件夾下新建類AutoMapperConfig,該類處理所有的對象映射,即從一個對象轉化到另一個對象。如示例1所示。
示例1
using AutoMapper; //引用命名空間 public class AutoMapperConfig { public static void Config() { Mapper.Initialize(cfg => cfg.CreateMap<RegUserVM, User>()); } } |
Mapper.Initialize()方法執行AutoMapper的初始化操作,此操作在一個應用程式中只能執行一次.在初始化方法中可以初始化映射中的任何操作。
CreateMap()泛型方法,用來創建兩個類型的映射,第一個類型為原類型,第二個類型為目標類型。在這裡配置為將視圖模型映射為業務模型。
然後,在項目的Global.asax文件的Application_Start()方法中調用該靜態方法。代碼如下所示。
protected void Application_Start() { //調用AutoMapper配置 AutoMapper.AutoMapperConfig.Config();
AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } |
至此,所有AutoMapper的配置全部配置完成。
下麵我們完成用戶註冊功能。在控制器中創建兩個Action。如示例2所示。
示例2
[HttpGet] public ActionResult Register() { return View(); } [HttpPost] public ActionResult Register(RegUserVM userVM) { //將視圖模型對象映射為業務模型對象 User user = Mapper.Map<User>(userVM); if (UserManager.Add(user)) { return RedirectToAction("Login"); } return View(); } |
Mapper.Map<S,T> 執行映射方法 S為源類型,T為目標類型,參數為源類型。
示例2中,AutoMapper 根據欄位名稱去自動對應,並忽略大小寫。我們應用 AutoMapper 省去了繁瑣的賦值操作。
6.2.3 映射規則
-
預設規則
- 預設情況下,我們的"原類型"和"目標類型"是根據屬性名稱進行匹配映射的。
- 如果在目標類型的屬性與源類型中配有對應的屬性,則映射失敗(為空)。
- 在映射過程中,會執行自動類型轉換。(6.2.0以上版本)
-
反向映射
在AutoMapper中 ReverseMap() 方法可以配置為反向映射。如示例3所示。
示例3
public class AutoMapperConfig { public static void Config() { Mapper.Initialize(cfg => cfg.CreateMap<RegUserVM, User>().ReverseMap()); } } |
- 指定映射欄位
在實際的業務環境中,我們的源類型和目標類型的欄位不可能一對一的匹配,這個時候我們就需要來指定他們的實際映射關係。如示例3所示。
示例3
//源類型 public class User { public int Id { get; set; } public string LoginName { get; set; } public string LoginPwd { get; set; } public string Phone { get; set; } public int RoleId { get; set; } public Role Role { get; set; } } //源目標類型 public class UserVM { public int UserId { get; set; } public string UserName { get; set; } public string RoleName { get; set; } }
public static void Config() { Mapper.Initialize(cfg => { cfg.CreateMap<RegUserVM, User>().ReverseMap(); cfg.CreateMap<User, ListUserVM>().ReverseMap();
cfg.CreateMap<User, UserVM>() .ForMember(vm => vm.UserId, opt => opt.MapFrom(s => s.Id)) //指定映射 .ForMember(vm=>vm.UserName,opt=>opt.MapFrom(s=>s.LoginName)) .ReverseMap()); }); }
|
ForMember()方法用來配置匹配信息。參數1:目標類型屬性的表達式,參數2:執行操作的選擇。
-
空值替換
AutoMapper中允許設置一個備用值來代替源類型中的空值。如示例4所示。
示例4
public static void Config() { Mapper.Initialize(cfg => { cfg.CreateMap<RegUserVM, User>().ReverseMap(); cfg.CreateMap<User, ListUserVM>().ReverseMap();
cfg.CreateMap<User, UserVM>() .ForMember(vm => vm.UserId, opt => opt.MapFrom(s => s.Id)) .ForMember(vm=>vm.UserName,opt=>opt.MapFrom(s=>s.LoginName)) .ForMember(vm => vm.RoleName, opt => opt.NullSubstitute("普通用戶")).ReverseMap()); }); } |
-
扁平化映射
在AutoMapper中, 如果對目標類型上的任何屬性,方法或以"Get"為首碼的方法不存在源類型上,則AutoMapper會將目標成員名稱拆分為單個單詞。
例如,在顯示用戶信息時,我們只想顯示用戶的登錄名、電話和角色名稱三個屬性,代碼如示例5所示。
示例5
// 顯示用戶信息的視圖模型 public class ListUserVM { public int Id { get; set; } public string LoginName { get; set; } public string Phone { get; set; } public string RoleName { get; set; } } // AutoMapper配置 public static void Config() { Mapper.Initialize(cfg => { cfg.CreateMap<RegUserVM, User>().ReverseMap(); cfg.CreateMap<User, ListUserVM>().ReverseMap(); }); } // 控制器方法 public ActionResult Index() { Role role = new Role() { Id = 1, Name = "管理員" }; User user = new User() { Id = 1, LoginName = "admin", Phone = "111", Role=role }; ListUserVM userVM = Mapper.Map<ListUserVM>(user); return View(userVM); } |
程式運行後,userVM 對象的 RoleName 屬性會被賦值為"管理員"。(RoleName屬性會和User類的 Role.Name 屬性進行匹配)