在 ASP.NET MVC 中使用 C# 的隐式转换运算符将 ViewModel 映射到 Model






4.87/5 (21投票s)
如何使用 C# 中的隐式运算符将 ViewModel 映射到 Model 或反之。
背景
在 ASP.NET MVC 中,我们一直在处理三个重要的事情,即 Model、View 和 Controller。 有时,我们希望将 Model 的特定信息从 View 传递到 action,但是如果我们使用映射到数据库表的 Model 类,那么事情就会变得混乱,因为所有 Model 都从 View 来回传递到 action,反之亦然。
考虑以下映射到数据库中 user 表的 Model 类。
namespace JIRA.Domain.Models { public class User { public int UserID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string UserName { get; set; } public string Password { get; set; } public bool IsActive { get; set; } public bool IsDeleted { get; set; } public DateTime CreatedAt { get; set; } public int CreatedBy { get; set; } public DateTime UpdatedAt { get; set; } public int UpdatedBy { get; set; } public string Email { get; set; } } }
这是我的数据库表
CREATE TABLE [dbo].[tblUser] (
[UserID] INT IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (25) NULL,
[LastName] NVARCHAR (25) NULL,
[UserName] NVARCHAR (25) NULL,
[Password] NVARCHAR (25) NULL,
[IsActive] BIT NULL,
[IsDeleted] BIT NULL,
[CreatedBy] INT NULL,
[CreatedAt] DATETIME NULL,
[UpdatedBy] INT NULL,
[UpdatedAt] DATETIME NULL,
[Email] NVARCHAR (50) NULL,
PRIMARY KEY CLUSTERED ([UserID] ASC)
);
通常发生的情况是,开发人员使用映射到数据库表的 Model 类对他们的 View 进行强类型化,这不是一个好方法,因为我们的 View 并不总是需要表中所有信息。
场景
现在考虑注册/用户注册的场景,其中我们有不同的字段,其中一些将映射到 User
类,但是由于正在注册 User
,因此 Model
的某些属性在这里无用,这些属性将在用户提交表单时被发布,并且我们可能需要一些额外的属性,但它们没有在表中映射。 您可以举个例子,当用户注册时,我们从用户那里获取两次密码以进行确认,在这种情况下,我们不想更改表示数据库中 Entity 的 Model,因此 ViewModel 就出现了。
ViewModels 和 Models
ViewModels 专用于 Views,我们在 ViewModel 中放置我们在特定 View 中需要的信息。 这是一个不推荐使用的方法的片段。 现在,我们将为注册 View 创建一个 ViewModel,该 ViewModel 将具有特定于该 View 的属性,该 View 需要发布这些属性,并且我们将把 ViewModel 属性映射到表示我们表的 Entity Model,并将其插入到数据库中。
示例
以下是上述用例的 ViewModel
namespace JIRA.ViewModels
{
public class RegisterViewModel
{
public int UserID { get; set; }
[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is Required")]
public string LastName { get; set; }
[Required(ErrorMessage = "Username is Required")]
[RegularExpression(@"^[a-zA-Z0-9]+$",
ErrorMessage = "user name must be combination of letters and numbers only.")]
[Remote("UsernameExists","Account",
HttpMethod="POST",ErrorMessage="User name already registered.")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password is Required")]
public string Password { get; set; }
[Required(ErrorMessage = "Password is Required")]
[System.Web.Mvc.Compare("Password",
ErrorMessage="Both Password fields must match.")]
public string ConfirmPassword { get; set; }
[Required(ErrorMessage = "Email Address is required")]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
[Remote("EmailExists", "Account",
HttpMethod = "POST", ErrorMessage = "Email address already registered.")]
public string Email { get; set; }
}
}
现在,我们将使用 RegisterViewModel
类型对我们的 View 进行强类型化,该类型仅包含与注册 View 相关的属性
@model JIRA.ViewModels.RegisterViewModel
using (Html.BeginForm("SignUp", "Account",
FormMethod.Post, new { @class = "form-inline", role = "form" }))
{
@Html.AntiForgeryToken()
<div class="row">
<div class="span8 offset5 aui-page-panel">
<div>
<h2>Sign Up</h2>
</div>
<fieldset style="margin-bottom: 40px;">
<legend style="border-bottom: 1px solid gray;
font-size: 16px;">Basic Information</legend>
<table width="100%" border="0"
cellspacing="0" cellpadding="0">
<tr id="tr_basic">
<td style="vertical-align: top;" width>
<div id="basicinfo"
style="width: 100%">
<div style="height: auto !important;
overflow-y: visible;">
<table cellpadding="3">
<tbody>
<tr>
<td width="150">
@Html.LabelFor
(model => model.FirstName,
new { @class = "sr-only" })
</td>
<td>
@Html.TextBoxFor
(model => model.FirstName,
new { @class =
"form-control input-sm" })
@Html.MyValidationMessageFor
(model => model.FirstName)
</td>
</tr>
<tr>
<td>
@Html.LabelFor
(model => model.LastName)
</td>
<td>
@Html.TextBoxFor
(model => model.LastName,
new { @class =
"input-xsmall" })
@Html.MyValidationMessageFor
(model => model.LastName)
</td>
</tr>
@*<tr>
<td>
@Html.LabelFor(model => model.Email)
</td>
<td>
@Html.TextBoxFor(model => model.Email,
new { @class = "required" })
@Html.MyValidationMessageFor
(model => model.Email)
</td>
</tr>*@
<tr>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</table>
<legend style="border-bottom: 1px solid gray;
font-size: 16px;">Account Information</legend>
<table cellpadding="5">
<tr>
<td width="150">
@Html.LabelFor(model => model.UserName)
</td>
<td>
@Html.TextBoxFor(model => model.UserName)
@Html.MyValidationMessageFor(model => model.UserName)
</td>
<td id="tdValidate">
<img id="imgValidating" src="@Url.Content
("~/Images/validating.gif")"
style="display:none;" /></td>
</tr>
<tr>
<td>
@Html.LabelFor(model => model.Password)
</td>
<td>
@Html.PasswordFor(model => model.Password)
@Html.MyValidationMessageFor(model => model.Password)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.ConfirmPassword,
new { @class = "control-label" })
</td>
<td>
@Html.PasswordFor(model => model.ConfirmPassword)
@Html.MyValidationMessageFor(model => model.ConfirmPassword)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.Email,
new { @class = "control-label" })
</td>
<td>
@Html.TextBoxFor(model => model.Email)
@Html.MyValidationMessageFor(model => model.Email)
</td>
</tr>
<tr>
<td>
<p>
<div class="control-group">
<div class="controls">
<input id="btnRegister"
type="submit"
class="btn btn-primary"
value="Register" />
</div>
</div>
</p>
</td>
<td></td>
</tr>
</table>
</fieldset>
</div>
</div>
}
}
并且我们的 action 看起来像这样
[HttpPost] [AllowAnonymous] public ActionResult SignUp(RegisterViewModel registerVM) { if (ModelState.IsValid) { // save to database } return View(registerVM); }
我们的服务方法将 User
类型的对象作为输入,这是我们的 Domain 或 Entity Model,因此我们将不得不将 RegisterViewModel
对象转换为 User
对象,一个快速而粗糙的方法是创建 User
类型的实例,并将其映射到 RegisterViewModel
,然后再调用服务方法。
如下所示
[HttpPost]
[AllowAnonymous]
public ActionResult SignUp(RegisterViewModel registerVM)
{
if (ModelState.IsValid)
{
User user = new User
{
FirstName = registerVM.FirstName,
LastName = registerVM.LastName,
UserName = registerVM.UserName,
Email = registerVM.Email,
Password = registerVM.Password
};
var result = authenticateService.RegisterUser(user);
上面的代码显然会起作用,但它会导致代码冗余,结果将违反 DRY 原则,因为将来有可能将 ViewModel
实例映射到 Model
实例,反之亦然,并且我们将一次又一次地在需要它的不同地方编写相同的代码,这不好。
C# 中的隐式运算符
现在出现了 C# 提供的 隐式运算符功能,我们将为 RegisterViewModel
和 User
类编写我们的运算符,以便它们可以在需要时隐式地相互转换。 我们将不得不修改 RegisterViewModel
的实现
我们将不得不在 RegisterViewModel
类中添加这两个运算符
public static implicit operator RegisterViewModel(User user)
{
return new RegisterViewModel
{
UserID = user.UserID,
FirstName = user.FirstName,
UserName = user.UserName,
Password = user.Password,
ConfirmPassword = user.Password,
Email = user.Email
};
}
public static implicit operator User(RegisterViewModel vm)
{
return new User
{
FirstName = vm.FirstName,
LastName = vm.LastName,
UserName = vm.UserName,
Email = vm.Email,
Password = vm.Password
};
}
因此,我们在一处编写了这两种类型之间的隐式转换,并且我们将在需要它的任何地方重复使用它。
是的,我的意思是,现在这两种类型可以隐式地相互转换/强制转换。
我的 action 现在看起来像这样
[HttpPost]
[AllowAnonymous]
public ActionResult SignUp(RegisterViewModel registerVM)
{
if (ModelState.IsValid)
{
var result = authenticateService.RegisterUser(registerVM); // implicit
// conversion from RegisterViewModel to User Model
RegisterViewModel vm = result; // see implicit conversion
// from User model to RegisterViewModel
return View(vm);
}
return View(registerVM);
}
摘要
通过实现 Model 到 ViewModel 映射的隐式运算符,反之亦然,我们可以看到我们的 action 现在更简洁了,因为转换已移至中心位置,因此我们的代码也可重用,并且我们正在尝试在某种程度上遵循 DRY 原则。
您可以在MSDN 上阅读有关隐式运算符的更多信息。