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.
- 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; }
- }
- }
Here is my database table:
- 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)
- );
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:
- 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; }
- }
- }
Now, we will strongly type our View with the
RegisterViewModel type which only contains properties that are related with the Register 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>
- }
- }
And our action will be like the following,
- [HttpPost]
- [AllowAnonymous]
- public ActionResult SignUp(RegisterViewModel registerVM)
- {
- if (ModelState.IsValid)
- {
-
- }
- return View(registerVM);
- }
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:
- [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);
- }
- }
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:
- 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
- };
- }
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:
- [HttpPost]
- [AllowAnonymous]
- public ActionResult SignUp(RegisterViewModel registerVM)
- {
- if (ModelState.IsValid)
- {
- var result = authenticateService.RegisterUser(registerVM);
-
-
- RegisterViewModel vm = result;
-
-
- return View(vm);
-
- }
- return View(registerVM);
- }
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.