Using Editor Templates Multiple Times on the Same Page in MVC

banner5

In the last post we created a star rating control that can be easily used in model binding and in displaying a record on our page. The rating control used FontAwesome and some CSS hacks to change radio buttons (which are usable from screen readers) into stars. This gave us a reusable rating control in MVC that is accessible and looks pretty darn okay, and meets our needs for model binding.

image_thumb6

But what if you wanted to build out a list of elements that you wanted to rate? Rather than just one movie, you wanted users to be able to rate your whole collection?  Understanding the mechanics of the binding engine are important if you want to POST a list of entities back to an MVC controller.

Getting the Basics

One of the earliest things that attracted me to the MVC Framework was model binding, and the freedom to stop fishing the in the Form object for parameters that may or may not exist. Over time, many of us built libraries of code that we’d suck in to each project so that we could do things like wrap the extraction of a list of checkbox or radio values up into some kind of abstraction. We don’t need to do that in MVC.

You’ll recall that a list of checkboxes in HTML is quite straightforward. The simplest approach might be something like this:

<form method="POST" action="Index3">

    <input type="checkbox" name="movies" title="The Last Starfighter" value="The Last Starfighter" id="movie1" />
    <label for="movie1">The Last Starfighter</label><br />

    <!-- ... -->

    <input type="checkbox" name="movies" title="Amelie" value="Amelie" id="movie2" />
    <label for="movie2">Amelie</label><br />

    <button type="submit">Save</button>
</form>
And to “catch” that data, we need an action (the target of the form above) with the same name that accepts a List<string> with the name of the checkbox. The value attribute of the checkbox will be added to the list parameter in your action. Your action will look something like the following:
[HttpPost]
public ActionResult Index3(List<string> movies)
{
    return View();
}
The model binder allows any of the selected checkboxes to have a landing spot in your list. You don’t have to check for nulls, or iterate through the form collection to discover all that might have been submitted. It’s worth noting here, as well, that if your value attribute were all integers in your form, you could just as easily have the framework bind to a List<int> in your action signature. So, that’s great for simple values, but… ## …What About Binding Complex Objects? I’m so glad you asked. ![Smile](https://jcblogimages.blob.core.windows.net/img/2014/09/wlEmoticon-smile.png) [![image](https://jcblogimages.blob.core.windows.net/img/2014/09/image_thumb1.png "image")](https://jcblogimages.blob.core.windows.net/img/2014/09/image2.png)If we now look back at our rating control, the control itself is actually ready to go as-is, but we’d need to get a bit more code into our view page (not the partial, but more like, the list of the movies) to get it to tick correctly.  Here’s what we’ll do: * Change our index action to return a list * Iterate over the list in our view * Pass in some secret magic sauce to the HtmlHelper that renders our stars control We’ll load up some fake data to push to the view. You would typically want to get this data from a database or an API call of some kind. For now, we’ll go with this:
public ActionResult Index()
{
    var movies = new List<Movie>
    {
        new Movie{Title = "The Last Starfighter", Rating = 4},
        new Movie{Title = "Flight of the Navigator", Rating = 3},
        new Movie{Title = "Raiders of the Lost Ark",  Rating = 4},
        new Movie{Title = "Amelie",  Rating = 5}
    };

    return View(movies);
}
Great, now we just need to update our view. Basically, you can take everything out of the form and just replace it with the following, a foreach over the collection we’re passing in:
    @foreach (var movie in Model)
    {
        <div class="row">
            <div>
                <p>@movie.Title</p>
                <input type="hidden" value="@movie.MovieId" name="@string.Format("movies[{0}].MovieId", i)" />
                <input type="hidden" value="@movie.Title" name="@string.Format("movies[{0}].Title", i)" />
                <p>@Html.EditorFor(p => movie.Rating, "StarRating", string.Format("movies[{0}].Rating", i++))</p>
            </div>
        </div>
    }
Also, don’t forget to change the @model on your page to the collection of Movie as so:
@model List<YourApplication.Models.Movie>
Finally, update the code block at the top of the page to declare a variable that we will use as a counter over our collection:
@{
    ViewBag.Title = "My Awesome List of Awesome Movies";
    var i = 0; // movie control index
}
We’re moving along now! There’s two crucial parts in how all this will work when it gets rendered on the client and eventually hits the model binder on the way back in when the form is submitted. The first is the way I’ve constructed the name attribute on our controls. For instance:
name="@string.Format("movies[{0}].MovieId", i)"
In the above code, on the first iteration, the name would be rendered as “movies[0].MovieId”. The prefix “movies” is what our parameter needs to be called on our action (for the POST). The index notation [0] tells MVC which element this object is in our list, and finally, the “MovieId” is simply the property we want to pass in.  _**Note**: While I’m passing in the ID and the Title as hidden params, I’m only doing so to show the model binder in action. You should always test/cleanse these values in your controller to prevent over-posting of data, where a malicious user could modify the object without your consent._ The second noteworthy point is the overload that I’m calling on the EditorFor method:
@Html.EditorFor(p => movie.Rating, "StarRating", string.Format("movies[{0}].Rating", i++))
That’s important because I’m passing the similarly generated name (as above) as the name of the HTML field name. Remember that [in the last post](http://jameschambers.com/2014/09/using-font-awesome-in-an-accessible-bindable-star-rating-control/) the EditorTemplate contained references to the ViewData.TemplateInfo.HtmlFieldPrefix? That is normally provided for you by default, but in the case of a list of objects, you need to provide it on your own. That code, in the Shared\EditorTemplates folder, was referenced like this:
var htmlField = ViewData.TemplateInfo.HtmlFieldPrefix;
…and it was sprinkled throughout the control template. Finally, we’re going to need to get that POST action in line.  Update your controller method to look like this:
[HttpPost]
public ActionResult Index(List<Movie> movies)
{
    return View(movies);
}

Now, when the form is submitted each set of indexed form fields will be used to new up an instance of our Movie class and placed in the collection. I’ve set a breakpoint so that you can see it in action:

image

You’d obviously want to do more than just cycle the movies through the controller, so rather than just returning the list you could do your validation, update your database, or aggregate stats from this response to a running average. So much fun to be had.

Summary & Next Steps

I always like to encourage folks to try things out and see if they can get it working on your own. It all starts with File –> New Project. Be sure to check out my Bootstrap series or purchase a copy of my book for more examples like this, and happy coding!