65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (21投票s)

2015 年 10 月 27 日

CPOL

3分钟阅读

viewsIcon

151433

如何使用 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 的某些属性在这里无用,这些属性将在用户提交表单时被发布,并且我们可能需要一些额外的属性,但它们没有在表中映射。 您可以举个例子,当用户注册时,我们从用户那里获取两次密码以进行确认,在这种情况下,我们不想更改表示数据库中 EntityModel,因此 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);
  }

摘要

通过实现 ModelViewModel 映射的隐式运算符,反之亦然,我们可以看到我们的 action 现在更简洁了,因为转换已移至中心位置,因此我们的代码也可重用,并且我们正在尝试在某种程度上遵循 DRY 原则。

您可以在MSDN 上阅读有关隐式运算符的更多信息

© . All rights reserved.