Mapping ViewModel To Model Using Implicit Conversion Operator In C#

Background

In ASP.NET MVC, we have three important things in which we are moving all the time which is Model, View and Controller. Sometimes, we want specific information of model to be passed from View to action, but if we use the Model classes that are mapped to our database tables make things messy, as all the model is round tripping from View to action or vice versa.

Consider the following model class which is mapped to the user table in my database.

  1. namespace JIRA.Domain.Models  
  2. {  
  3.     public class User  
  4.     {  
  5.         public int UserID { getset; }  
  6.   
  7.         public string FirstName { getset; }  
  8.   
  9.         public string LastName { getset; }  
  10.   
  11.         public string UserName { getset; }  
  12.   
  13.         public string Password { getset; }  
  14.   
  15.         public bool IsActive { getset; }  
  16.   
  17.         public bool IsDeleted { getset; }  
  18.   
  19.         public DateTime CreatedAt { getset; }  
  20.   
  21.         public int CreatedBy { getset; }  
  22.   
  23.         public DateTime UpdatedAt { getset; }  
  24.   
  25.         public int UpdatedBy { getset; }  
  26.   
  27.         public string Email { getset; }  
  28.     }  
  29. }  
Here is my database table:
  1. CREATE TABLE [dbo].[tblUser] (  
  2.     [UserID]    INT           IDENTITY (1, 1) NOT NULL,  
  3.     [FirstName] NVARCHAR (25) NULL,  
  4.     [LastName]  NVARCHAR (25) NULL,  
  5.     [UserName]  NVARCHAR (25) NULL,  
  6.     [Password]  NVARCHAR (25) NULL,  
  7.     [IsActive]  BIT           NULL,  
  8.     [IsDeleted] BIT           NULL,  
  9.     [CreatedBy] INT           NULL,  
  10.     [CreatedAt] DATETIME      NULL,  
  11.     [UpdatedBy] INT           NULL,  
  12.     [UpdatedAt] DATETIME      NULL,  
  13.     [Email]     NVARCHAR (50) NULL,  
  14.     PRIMARY KEY CLUSTERED ([UserID] ASC)  
  15. );  
So what happens normally is developers strongly type their view with the model class that is mapped with the table in our db which is not a good approach, as our View doesn't need all information of the table every time.

Scenario

Now consider the scenario of Register/SignUp of user in which we have different fields from which some will map to the User class but as User is registering so some properties of Model are useless here which will be posted when user submits form and there are some additional properties that we may need but they are not mapped in the table. You can take an example when user registers we take Password from user two times for Confirming, in that case, we don't want to change our Model that represents our Entity in the database, so ViewModel comes in.

ViewModels and Models

ViewModels are specific to the Views, we put information in View Model that we need on the particular View. Here is the snippet that is not a preferred way. So now, we will create a ViewModel for Register View which will have properties specific to that View that it needs to post and we will map the ViewModel properties to Entity Model that represents our table and will insert it in the database.

Example

The following is the ViewModel for the current use case that I explained above:
  1. namespace JIRA.ViewModels  
  2. {  
  3.     public class RegisterViewModel  
  4.     {  
  5.         public int UserID { getset; }  
  6.   
  7.         [Required(ErrorMessage = "First Name is required")]  
  8.         public string FirstName { getset; }  
  9.   
  10.         [Required(ErrorMessage = "Last Name is Required")]  
  11.         public string LastName { getset; }  
  12.   
  13.         [Required(ErrorMessage = "Username is Required")]  
  14.         [RegularExpression(@"^[a-zA-Z0-9]+$",   
  15.         ErrorMessage = "user name must be combination of letters and numbers only.")]  
  16.         [Remote("UsernameExists","Account",  
  17.         HttpMethod="POST",ErrorMessage="User name already registered.")]  
  18.         public string UserName { getset; }  
  19.   
  20.         [Required(ErrorMessage = "Password is Required")]  
  21.         public string Password { getset; }  
  22.   
  23.         [Required(ErrorMessage = "Password is Required")]  
  24.         [System.Web.Mvc.Compare("Password",  
  25.         ErrorMessage="Both Password fields must match.")]  
  26.         public string ConfirmPassword { getset; }  
  27.   
  28.         [Required(ErrorMessage = "Email Address is required")]  
  29.         [EmailAddress(ErrorMessage = "Invalid Email Address")]  
  30.         [Remote("EmailExists""Account",   
  31.         HttpMethod = "POST", ErrorMessage = "Email address already registered.")]  
  32.         public string Email { getset; }  
  33.     }  
  34. }  
Now, we will strongly type our View with the RegisterViewModel type which only contains properties that are related with the Register View:
  1. @model JIRA.ViewModels.RegisterViewModel  
  2.   
  3. using (Html.BeginForm("SignUp""Account",   
  4. FormMethod.Post, new { @class = "form-inline", role = "form" }))  
  5.         {  
  6.     @Html.AntiForgeryToken()  
  7.       
  8.     <div class="row">  
  9.         <div class="span8 offset5 aui-page-panel">  
  10.   
  11.             <div>  
  12.                     <h2>Sign Up</h2>  
  13.                 </div>  
  14.             <fieldset style="margin-bottom: 40px;">  
  15.                   
  16.   
  17.                                 <legend style="border-bottom: 1px solid gray;   
  18.                                 font-size: 16px;">Basic Information</legend>  
  19.   
  20.                 <table width="100%" border="0"   
  21.                 cellspacing="0" cellpadding="0">  
  22.                     <tr id="tr_basic">  
  23.                         <td style="vertical-align: top;" width>  
  24.                             <div id="basicinfo"   
  25.                                 style="width: 100%">  
  26.                                 <div style="height: auto !important;   
  27.                                     overflow-y: visible;">  
  28.                                     <table cellpadding="3">  
  29.   
  30.                                         <tbody>  
  31.                                             <tr>  
  32.                                                 <td width="150">  
  33.                                                     @Html.LabelFor  
  34.                                                     (model => model.FirstName,   
  35.                                                     new { @class = "sr-only" })  
  36.                                                 </td>  
  37.                                                 <td>  
  38.                                                     @Html.TextBoxFor  
  39.                                                     (model => model.FirstName,   
  40.                                                     new { @class =   
  41.                                                     "form-control input-sm" })  
  42.                                                     @Html.MyValidationMessageFor  
  43.                                                     (model => model.FirstName)  
  44.                                                 </td>  
  45.   
  46.                                             </tr>  
  47.                                             <tr>  
  48.                                                 <td>  
  49.                                                     @Html.LabelFor  
  50.                                                     (model => model.LastName)  
  51.                                                 </td>  
  52.                                                 <td>  
  53.                                                     @Html.TextBoxFor  
  54.                                                         (model => model.LastName,   
  55.                                                     new { @class =   
  56.                                                     "input-xsmall" })  
  57.                                                     @Html.MyValidationMessageFor  
  58.                                                         (model => model.LastName)  
  59.                                                 </td>  
  60.                                             </tr>  
  61.                                             @*<tr>  
  62.                                                 <td>  
  63.                                                     @Html.LabelFor(model => model.Email)  
  64.                                                 </td>  
  65.                                                 <td>  
  66.                                                     @Html.TextBoxFor(model => model.Email,   
  67.                                                     new { @class = "required" })  
  68.                                                     @Html.MyValidationMessageFor  
  69.                                                         (model => model.Email)  
  70.                                                 </td>  
  71.                                             </tr>*@  
  72.                                               
  73.                                             <tr>  
  74.                                             </tr>  
  75.   
  76.                                         </tbody>  
  77.                                     </table>  
  78.   
  79.                                 </div>  
  80.                         </td>  
  81.                     </tr>  
  82.   
  83.                 </table>  
  84.   
  85.                 <legend style="border-bottom: 1px solid gray;   
  86.                 font-size: 16px;">Account Information</legend>  
  87.                 <table cellpadding="5">  
  88.                     <tr>  
  89.                         <td width="150">  
  90.                             @Html.LabelFor(model => model.UserName)  
  91.                         </td>  
  92.                         <td>  
  93.                             @Html.TextBoxFor(model => model.UserName)  
  94.                             @Html.MyValidationMessageFor(model => model.UserName)  
  95.                         </td>  
  96.                         <td id="tdValidate">  
  97.                             <img id="imgValidating" src="@Url.Content  
  98.                             ("~/Images/validating.gif")"   
  99.                             style="display:none;" /></td>  
  100.   
  101.                     </tr>  
  102.                     <tr>  
  103.                         <td>  
  104.                             @Html.LabelFor(model => model.Password)  
  105.                         </td>  
  106.                         <td>  
  107.                             @Html.PasswordFor(model => model.Password)  
  108.                             @Html.MyValidationMessageFor(model => model.Password)  
  109.                         </td>  
  110.                     </tr>  
  111.   
  112.                     <tr>  
  113.                         <td>  
  114.                             @Html.LabelFor(m => m.ConfirmPassword,   
  115.                             new { @class = "control-label" })  
  116.                         </td>  
  117.   
  118.                         <td>  
  119.                             @Html.PasswordFor(model => model.ConfirmPassword)  
  120.                             @Html.MyValidationMessageFor(model => model.ConfirmPassword)  
  121.   
  122.                         </td>  
  123.                     </tr>  
  124.                     <tr>  
  125.                         <td>  
  126.                             @Html.LabelFor(m => m.Email,   
  127.                             new { @class = "control-label" })  
  128.                         </td>  
  129.   
  130.                         <td>  
  131.                             @Html.TextBoxFor(model => model.Email)  
  132.                             @Html.MyValidationMessageFor(model => model.Email)  
  133.                         </td>  
  134.   
  135.                     </tr>  
  136.                     <tr>  
  137.                     <td>  
  138.                         <p>  
  139.                             <div class="control-group">  
  140.                                 <div class="controls">  
  141.                                     <input id="btnRegister"   
  142.                                     type="submit"   
  143.                                     class="btn btn-primary"   
  144.                                     value="Register" />  
  145.                                 </div>  
  146.                             </div>  
  147.   
  148.                         </p>  
  149.                     </td>  
  150.                     <td></td>  
  151.                 </tr>  
  152.                 </table>  
  153.   
  154.             </fieldset>  
  155.         </div>  
  156.     </div>  
  157.   }  
  158. }  
And our action will be like the following,
  1. [HttpPost]  
  2. [AllowAnonymous]  
  3. public ActionResult SignUp(RegisterViewModel registerVM)  
  4. {  
  5.     if (ModelState.IsValid)  
  6.     {  
  7.         // save to database  
  8.     }  
  9.   return View(registerVM);  
  10. }  
Our service method takes object of type User as input which is our Domain or Entity Model so we will have to convert RegisterViewModel object to User object, a quick and dirty way is to create an instance of type User and map it to RegisterViewModel before calling service method.

Here it is:
  1. [HttpPost]  
  2. [AllowAnonymous]  
  3. public ActionResult SignUp(RegisterViewModel registerVM)  
  4. {  
  5.     if (ModelState.IsValid)  
  6.     {  
  7.        User user = new User  
  8.        {  
  9.           FirstName = registerVM.FirstName,  
  10.           LastName = registerVM.LastName,  
  11.           UserName = registerVM.UserName,  
  12.           Email = registerVM.Email,  
  13.           Password = registerVM.Password  
  14.        };  
  15.   
  16.        var result = authenticateService.RegisterUser(user);  
  17.     }  
  18.  }  
The above code will obviously work but it will cause code redundancy and in result it will be violation of DRY principle, because in future there is a possibility of mapping ViewModel instance to Model instance or vice versa and we will end up writing the same code again and again at different places where it is needed which is not good.

Implicit Operator in C#


Here comes in the implicit operator feature provided by C#, we will write our operator for RegisterViewModel and User class so that they can be implicitly converted to each other wherever needed. We will have to modify the implementation of RegisterViewModel for this:

We will have to add these two operators in the RegisterViewModel class:
  1. public static implicit operator RegisterViewModel(User user)  
  2. {  
  3.     return new RegisterViewModel  
  4.     {  
  5.         UserID = user.UserID,  
  6.         FirstName = user.FirstName,  
  7.         UserName = user.UserName,  
  8.         Password = user.Password,  
  9.         ConfirmPassword = user.Password,  
  10.         Email = user.Email  
  11.     };  
  12. }  
  13.   
  14. public static implicit operator User(RegisterViewModel vm)  
  15. {  
  16.     return new User  
  17.     {  
  18.         FirstName = vm.FirstName,  
  19.         LastName = vm.LastName,  
  20.         UserName = vm.UserName,  
  21.         Email = vm.Email,  
  22.         Password = vm.Password  
  23.      };  
  24.  }  
So we have written the implicit conversion between these two types at one place and we will be reusing it everywhere we need.

Yes, I mean that, now these two types can be implicitly convertible / cast-able into each other.

My action will look like this now:
  1. [HttpPost]  
  2. [AllowAnonymous]  
  3. public ActionResult SignUp(RegisterViewModel registerVM)  
  4. {  
  5.     if (ModelState.IsValid)  
  6.     {  
  7.         var result = authenticateService.RegisterUser(registerVM);  // implicit   
  8.                     // conversion from RegisterViewModel to User Model  
  9.   
  10.         RegisterViewModel vm = result; // see implicit conversion   
  11.                     // from User model to RegisterViewModel  
  12.   
  13.         return View(vm);  
  14.   
  15.      }  
  16.    return View(registerVM);  
  17. }  
Summary

By implementing implicit operator for Model to ViewModel mapping and vice versa, we can see that our action is cleaner now, as conversion is being moved to a central place due to which our code is reusable as well, and we are trying to follow DRY principle to some extent.

You can read more about implicit operator at MSDN here