ViewModels and Passing Data Between Views
Now that we have an understanding for what a model is, we can focus on how to effectively pass information between the three elements of MVC applications. With our current MVC application, we can add new events and remove events. However, our application is also susceptible to run-time errors. Our view can accept any type of input and if we mistype something in our view, we can run into issues later down the line.
A ViewModel
is a model class designed specifically to be used in a view. By utilizing ViewModels in our application, we can make our views strongly-typed and add validation to forms to prevent bad user input. Also, if we have information we want to collect as part of a form, but not save as part of a model, we can store that data as a property of a ViewModel. An example of this would be if we have a form for users to create an account on our site. The form includes two fields: one for the password and one for confirming the new user’s password. While we only want to save the password and may only have a Password
property in our model, we can add a ConfirmPassword
property to the ViewModel
so we can check that the two match before saving the user’s info. These benefits of ViewModels
will help reduce potential errors in our application.
Refactoring the Project
To start with understanding why we may want to use a ViewModel, let’s refactor our code to use a model directly in our view. This will require some updates to our controllers and views.
We need to do the following steps:
- Create our new ViewModel
- Update the Model
- Update our Index Action Method
- Update the Index View
- Update the Add and New Event Action Methods
- Update the Add View Model
Let’s get started!
Creating a ViewModel
Add a
ViewModels
directory at the top level of the project.Add a new class to the
ViewModels
directory and name itAddEventViewModel
.
Add
Name
andDescription
properties to the new class. We can remove the constructor. We do not need it at this time.We will need to declare the
Name
andDescription
as nullable. You do this by using the nullable value type?
after the modifiers. To see these in action, look at lines 7 and 8 in the code block below.Check Your Code3 4 5 6 7 8 9 10
namespace CodingEvents.ViewModels { public class AddEventViewModel { public string? Name { get; set; } public string? Description { get; set; } } }
NoteDeclaring a Value Nullable
Do you see the
?
afterstring
in lines 7 and 8 in the code block above? This declares this property to be a nullable value type. This means that the value ofName
orDescription
is allowed to be null at some point.A null value for either of these fields is not ideal, but we need them to have this flexibility when we begin to add validation attributes later in this chapter. If the validation checks fail, a new event will not be added to the project’s data storage.
Check out these resources on nullable value types and strategies to handle them.
We will declare our
Event
fields as nullable types, too. See the next section below.
Update the Model with Nullable Values
We need to declare the fields of our Event
model nullable as well.
|
|
Update the Index
Action Method in the Controller
In the EventsController
, find the Index
action method. We want to convert our ViewBag
to a List
collection type.
Update the
ViewBag.events
to aList
ofEvent
objects. Let’s store this list in a variable calledevents
.Pass the contents of the data layer to the new
List
.Return the new list
events
to the View.Check Your Code19 20 21 22 23 24 25
// GET: /<controller>/ public IActionResult Index() { List<Event> events = new List<Event>(EventData.GetAll()); return View(events); }
Update the Index
View
Now that we are storing our items in a List
, we need to import the model into our Events/Index.cshtml
view so we can use the new events collection. We can start by adding a @using
statement to let the view know which model to reference. We can also use the @model
statement to let the view know which type of object to expect. In this case we can expect a list of Event
objects.
Add a
@using
statement that informs the view about which portion of the project to access.Add a
@model
statement to inform the view about the object type.Check Your Code1 2
@using CodingEvents.Models @model List<Event>
Wherever we used our
ViewBag
property, we can now useModel
syntax. We want to count the number ofModel
objects, like we did with theViewBag
.Check Your Code14
@if (Model.Count == 0)
We need to update the loop to check the
Model
for events (evt
).Check Your Code32
@foreach (var evt in Model)
Once the view has been updated, run the application!
Update the Add
and NewEvent
Action Methods in the Controller
In the
EventsController
, add ausing
statement for your new ViewModel.Check Your Code8
using CodingEvents.ViewModels;
In the
Add()
action method responsible for retrieving the form to add events, inEventsController
, create a new instance ofAddEventViewModel
calledaddEventViewModel
and pass it to theView()
.Check Your Code24 25 26 27 28 29 30
[HttpGet] public IActionResult Add() { AddEventViewModel addEventViewModel = new AddEventViewModel(); return View(addEventViewModel); }
Next let’s update our NewEvent
action method.
Rename this method
Add()
and keep the parameters.Remove the
[Route]
attribute, but make sure to keep the[HttpPost]
attribute.It should take an instance of
AddEventViewModel
calledaddEventViewModel
as a parameter.Check Your Code29 30
[HttpPost] public IActionResult Add(AddEventViewModel addEventViewModel)
We want to create new events in this action method that we add to our data storage.
We are going to use new constructor syntax to create our
addEventViewModel
objects.- We will create a new
Event
object callednewEvent
. - We will instantiate all of the properties of Event using the
AddEventViewModel
inside curly braces{ }
. - We want to add our newEvent to our
EventData
. - Finally, we want to return a redirected view back to the
/Events
view to see our newly added event.
Check Your Code29 30 31 32 33 34 35 36 37 38 39 40 41
[HttpPost] public IActionResult Add(AddEventViewModel addEventViewModel) { Event newEvent = new Event { Name = addEventViewModel.Name, Description = addEventViewModel.Description }; EventData.Add(newEvent); return Redirect("/Events"); }
- We will create a new
You should now have two Add()
methods. The framework is clever enough to know the difference. The [HttpPost]
attribute designates the Add
method that processes the form while the other Add()
method retrieves the form.
This is similar to how we created the Delete()
action methods in the previous chapter
.
This renaming is not critical to your application, but can help you with the design logic of the program as it grows in size.
Update the Add
View
We are now ready to update our Add
view.
Import the ViewModel to the
Add.cshtml
view with the necessary@using
syntax. Inform the view that we will be using the new ViewModel with the correct@model
syntax.Check Your Code1 2
@using CodingEvents.ViewModels @model AddEventViewModel
We are going to add anchor tags helpers to the form.
Add
asp-controller = Events
andasp-action = Add
to the<form>
tag to designate which method the form data should be sent to. We want our form to send data to theAdd
method that handlesPOST
requests inside theEvents
controller.Add
asp-for
to<label>
and<input>
tags. This allows us to specify which form field corresponds to which property in our ViewModel.Check Your Code6 7 8 9 10 11 12 13 14 15 16
<form asp-controller="Events" asp-action="Add" method="post"> <div class="form-group"> <label asp-for="Name"></label> <input asp-for="Name" /> </div> <div class="form-group"> <label asp-for="Description"></label> <input asp-for="Description" /> </div> <input type="submit" value="Add Event" /> </form>
WarningIn the code block above, notice the lines where we use
asp-for
. We removed thename
andtype
requirements from the<input>
tag and addedasp-for
to the<label>
tag too.asp-for
reflects the name we provided to the property in the ViewModel. The reflected name will be used in the view when we run this project.Run your application.
- Test what happens when you provide input in each box.
- Test what happens when you leave one empty.
- Finally, test what happens when you leave all of them blank. Looks like the app would benefit from some validation.
Recap for the Refactor
This was merely a refactor so the functionality of the app hasn’t changed, but we have eliminated some of the possibility of bugs in our code being discovered at runtime! Using a model in a view like this makes our view strongly-typed. Before if we misspelled a property of Event
or ViewBag
, those errors would have been caught at run-time and possibly interfered with users’ experience. With a strongly-typed view, the same errors would be caught at compile-time before users see the application. Strongly-typed views also support intellisense, so as we work with properties of a model, we can make sure we have the correct property name.
While the functionality of the application remains the same, we are now in a position to easily add validation to our application.
Check Your Understanding
True or False: ViewModels are views designed to specifically be used in models.