Thursday, 15 April 2010

View Models and Form Models in ASP.Net MVC - A Second Look

Following my previous post looking at options on the use of view models within ASP.Net MVC, where I came down on the side of using a simpler approach passing domain models to views directly, in this blog post I'll outline the approach I'm taking with more recent project - where I've changed my approach to create view models and form models that wrap the domain model and can include additional fields and options that may be required in the particular view.

The argument for using custom view models is that they can be tailored precisely to each strongly typed view, providing a cleaner approach for display and updating data from views. The downside though is that there is some additional work involved. However, after further reading on the subject - would recommend both Steve Michelotti's post on MVC view model patterns and Jorit Salverda's maintainable MVC series - on balance now I've been using them more and more on projects, it seems the extra steps are worthwhile.

To illustrate this approach let's take again a simple example, where we have a domain model that represents a user. Each user has a user name and a role. The application using them needs to present a form for the update of a user's details, with the option to send them a welcome email.

The following code illustrates the code for the domain model.


    1 public class User


    2 {


    3     public string Username { get; set; }


    4     public Role Role { get; set; }


    5 }




And then I've created two further models in the UI layer - a view model that contains all the data for presentation on the form (the user object itself, and a SelectList for the various role options the administrator of the application can choose from. The second form model is created via model binding following the form post, where the user object is extracted and updated, and additional information also passed in a strongly typed manner is processed.


    1 public class UserViewModel


    2 {


    3     public User User { get; set; }


    4     public SelectList Roles { get; set; }


    5 }


    6 


    7 public class UserFormModel


    8 {


    9     public User User { get; set; }


   10     public bool SendEmail { get; set; }


   11 }




The controller code illustrates how the view models are created and information from the form model extracted.


    1 public ActionResult Add()


    2 {


    3     return View(new UserViewModel


    4     {


    5         Roles = new SelectList(usersService.GetRoleList(), "Id", "Name"),


    6     });


    7 }


    8 


    9 public ActionResult Edit(int id)


   10 {


   11     User user = usersService.GetUserById(id);


   12     return View(new UserViewModel


   13     {


   14         User = user,


   15         Roles = new SelectList(usersService.GetRoleList(), "Id", "Name", user.Role.Id),


   16     });


   17 }


   18 


   19 [AcceptVerbs(HttpVerbs.Post)]


   20 public ActionResult Edit(UserFormModel userFormModel)


   21 {


   22     if (ModelState.IsValid)


   23     {


   24         try


   25         {


   26             usersService.SaveUser(userFormModel.User, userFormModel.ResetPassword, GetUser());


   27         }


   28         catch (RuleException ex)


   29         {


   30             ex.CopyToModelState(ModelState);


   31         }


   32     }


   33     if (ModelState.IsValid)


   34     {


   35         TempData["Message"] = "User account " + (userFormModel.User.Id == 0 ? "created" : "updated") + ".";


   36         return RedirectToAction("Index");


   37     }


   38     else


   39     {


   40         return View((userFormModel.User.Id == 0 ? "Add" : "Edit"), new UserViewModel


   41         {


   42             User = userFormModel.User,


   43             Roles = new SelectList(usersService.GetRoleList(), "Id", "Name", userFormModel.User.Role.Id),


   44         });


   45     }


   46 }




And finally this strongly typed view code demonstrates how the form fields are named to support model binding following the post.


    1 <% using (Html.BeginForm()) {%>


    2 


    3     <fieldset>


    4         <h2>Account Details</h2>


    5         <p>


    6             <label for="User.Username">User name:</label>


    7             <span class="field">


    8                 <%= Html.TextBox("User.Username", Model.User.Username)%>


    9             </span>


   10         </p>


   11         <p>


   12             <label for="User.Role.Id">Role:</label>


   13             <span class="field">


   14                 <%= Html.DropDownList("User.Role.Id", Model.Roles)%>


   15             </span>


   16         </p>


   17         <p>


   18             <label for="SendEmail">Reset Password:</label>


   19             <span class="field">


   20                 <%= Html.CheckBox("SendEmail", false)%>


   21             </span>


   22         </p> 


   23         <p>


   24             <input type="submit" value="Save" class="indent" />                             


   25         </p>                           


   26         <%= Html.Hidden("User.Id",Model.User.Id) %>


   27     </fieldset>


   28 


   29 <% } %>




Without this approach it would be necesssary to pass the additional data to the view - the list of roles - in a loosely typed manner in the ViewData. And the additional information required from the form post - the flag to send the welcome email - would need to be passed as an extra paramenter to the controller action responding to the form post.

With hindsight I think this is clearly a cleaner approach and am using it for the MVC projects I'm working on now. In particular the method of creating a custom class for both the form display and the form post, provides a very elegent approach in my opinion.

1 comment: