Why Modify the Views?
By default, the views that are generated when you scaffold model entities are usually functional as they stand. However, there are certain instances where you’ll need to make some changes. For example, select lists in forms may display the Id of the item they represent as opposed to the name. Dates may be represented as text boxes when you want them to be a date picker. In list views (usually the index page for a particular entity), the table that displays may have too many fields such as the item Id.
It’s important to take stock of how each generated page appears and note necessary changes, so each page properly reflects your design intentions as well as those of your client.
In the last chapter, you may have noted some changes you would make to certain views in the project. Fortunately, all of the forms function quite well. However, aesthetically, they’re nothing to get excited about. In this chapter, I want to mention some particular problem areas for you to consider when dealing with the generated views, even though most don’t apply to your current project. They will be important considerations in your future work.
Ensure Form Fields Are the Correct Type
Form fields can sometimes be problem areas. When trying to enter data into a for, it can be especially frustrating to find that you are unable to enter the data properly because the wrong type of form field is being used. Here are a few sticky points to watch for:
Make sure the right data is displayed in the lists. If a list can have more than one selection, use a multi-select rather than a single-select, which is typically what is generated.
C# uses DateTime objects for both dates and times. If you need a time field in your form, don't display a date field and vice versa. For consistency, use a datepicker in your form and specify time field types as well.
If you have fields that require only integer input, make sure the correct input type is used. Text area inputs are particularly problematic. A simple text field will be generated for every string input. Entity Framework doesn't know when you need to provide a place for a paragraph or two, and it certainly doesn't know when you might need HTML formatted input. If you use such inputs in your forms, you will need to specify and format the text area inputs directly. The easiest way to do this is with the @Html.TextAreaFor() helper method. Using this method will create the appropriate text area HTML element in your form. For example:
@Html.TextAreaFor(model => model.Comments, 8, 0, new { @class = “form-control”, required = “required” })
This creates a new text area input for a model item named Comments. The second parameter indicates the number of rows (8), and the third parameter is the number of columns. A value of zero here means the box will fill the width of the available space as will the number of text columns. The last parameter is an anonymous object that represents the HTML attributes applied to the box. In this case, the form-control CSS class is applied, as is the required attribute.
These are just a few examples of problem areas to watch for. As always, carefully monitor all of the details in your forms to ensure they are intuitive and easy for your users to navigate.
Optimize Forms for Consistency
The Create and Edit forms are nearly identical when generated. If you make changes to one, you will need to make those same changes to the other. You don’t want a datepicker in one form and a text input for the same field in another.
Be careful with navigation as well. By default, each form has a submit and a cancel action. The submit action is a button labeled Create in the Create form and Save in the Edit form. The cancel action is a Back to List hyperlink in all forms. Developers often change this to a button labeled Cancel, or something similar, depending on the desired design. If you do this, be sure that you do it for all of the forms in your various views.
Handle Human Errors With Validation
For most data types, validation is something that is already part of the MVC scaffolding process. Each form that requires input will be generated with the following line below the <form> tag:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
This tag indicates where a summary of the validation errors will display. The values that may be used are All, ModelOnly, and None. ValidationSummary.All will display both property and model level validation messages. ValidationSummary.ModelOnly will display only validation messages that apply to the model level. ValidationSummary.None tells this tag to do nothing.
All data entry errors will be indicated by the red text below the input field. The HTML code that generates such errors looks like this:
<span asp-validation-for="Title" class="text-danger"></span>
This line sets a validation message for a model item called Title. If the input is a required attribute and left blank, a read message indicating the field is required will display below the input box.
Add Movies to the Watchlist
Did you notice that there isn't a way for you to add movies to your watchlist? You can add them to the database as well as edit and delete them, but there’s currently no way to put them into your watchlist. That’s a problem for a Watchlist app!
There are many ways you could go about this. Let me offer a few possibilities:
Add/remove movies directly from the Movies list page (Index) via individual Add/Remove buttons for each movie.
Add/remove movies directly from the Movies list page (Index) via checkboxes next to each movie.
Add a movie via the Create page.
Add/Remove a movie via the Edit page.
Add/Remove a movie via the Details page.
Each of these has its pros and cons. Options 1 and 2 are the more efficient and practical solutions, but they require some additional JavaScript coding and an API-style call to the controller. Options 3 - 5 fit an MVC-only application but are not an ideal way to handle the interaction, which could lead to frustration for the user. With this in mind, I would suggest option 1. It’s the simpler of the first two, and using it lets you show how MVC controllers can act as gateways to API functions as well.
Since you already have the view, you just need to make a few modifications there and to the MoviesController. Specifically, you need to:
Show whether the movie is in the user’s watchlist.
Insert Add/Remove buttons into each row of the table.
Add some JavaScript to handle the button clicks and call a new action from the MoviesController.
Update the Index method in the MoviesController to build the model using MovieViewModel objects instead of Movie objects.
Add a new method (AddRemove) to the MoviesController.
Let’s write some extra code.
Show Whether the Movie Is in the User’s Watchlist
To do this, you need to change the model the page uses. Fortunately, we have one that is perfect in the MovieViewModel class we wrote earlier in the course. The current model definition, on the first line of the Index page, is:
@model IEnumerable<Watchlist.Data.Movie>
Change it to this:
@model IEnumerable<Watchlist.Models.MovieViewModel>
Next, you need to add an extra column to the table. Add one to both the head and body of the table, in between the Year column and the last column. Label the head In watchlist.
In the body portion, the new column needs to show a button with a plus sign (+) if the movie is not in the user’s watchlist, and a minus sign (-) if it is. Each button needs its Id, as well as a data attribute that holds the value of the InWatchlist model attribute. For example:
<button id="@item.MovieId" data-val="@item.InWatchlist" class="btn">
@(item.InWatchlist ? " - " : " + ")
</button>
This example shows how to use Razor syntax denoted by the @ sign preceding a variable name, to access our model data. Insert each movie’s Id as the Id attribute of the corresponding button. Also, add data-val as a parameter, and give it the value of the InWatchlist attribute from the MovieViewModel object. To display the appropriate sign on the button, check the value of the InWatchlist attribute. If it is true, display the minus sign, if not, the plus sign.
Add the User Interaction With JavaScript
The added buttons all need to call an action from the MoviesController when clicked. To ensure the script gets called at the right time, add it in a Razor section called Scripts at the bottom of the page. At the bottom of the Index page, add the following:
@section Scripts {
<script>
jQuery(document).ready(function () {
$('.btn').click(function (e) {
var btn = $(this);
var movieId = btn.attr('id');
var movieVal = btn.attr('data-val') == "False" ? 0 : 1;
$.get('/Movies/AddRemove?id=' + movieId + '&val=' + movieVal,
function (data) {
if (data == 0) {
btn.attr('data-val', 'False');
btn.html(' + ');
}
else if (data == 1) {
btn.attr('data-val', 'True');
btn.html(' - ');
}
});
});
});
</script>
}
This script captures each button click, retrieves the button’s Id and data-val attributes, then calls the AddRemove method from the MoviesController, passing those values as part of the URL. Then, depending upon the returned value, changes the sign displayed on the button according to whether the movie is in the user’s watchlist.
However, for this to work, you need to write this AddRemove method in the MoviesController, which we will do in the next chapter.
Let's Recap!
In this chapter, you learned about some particular problem areas and pitfalls that you may face when working with scaffolded views in .NET MVC. Specifically, you learned:
The importance of ensuring the HTML input type matches the model item it represents.
The importance of maintaining consistency across all views in your project, both in data type representation, and the look and feel of the forms and pages themselves.
How ASP.NET handles form input validation within HTML pages.
After that, you learned how to make several changes to existing views to provide greater functionality for your users and to enable the adding and removing of movies to and from the user’s watchlist.
In the next chapter, we’ll talk about common changes you may have to make to scaffolded controllers, as well as the specific changes you need to make for your Watchlist application.