Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Step-By-Step Implementation of MultiSelectList In .NET MVC

0.00/5 (No votes)
15 Dec 2015 1  
A simple, step-by-step guide to implementing a multi-select list in .NET MVC, intended for beginners to .NET.

Introduction

This article is for beginners to .NET MVC who, like me, have been frustrated by seemingly simple implementations of basic HTML structures that turn out to be not so simple upon their implementation in .NET - at least until you understand the concepts that are underpinning these constructs.

In this, we cover the implementation of a MultiSelectList, both with and without selected values, right from the implementation of the Model through to receiving the data back from the view. This also applies to SelectLists, meaning this article is relevant if you are looking to employ any of the following on a user form:

  • Dropdown Lists
  • List Boxes (without pre-selected values)
  • List Boxes (with pre-selected values) - Note that there is a lot of misinformation on Stack Overflow and other places when implementing a list with pre-selected values so be sure to review this section.
  • Checkboxes or Radio Buttons (with or without pre-selected values)

If you are an experienced coder who sees any flaws or way to improve the methods in this article, please do comment or message me.

Accompanying Source Code

I have created a sample solution in Visual Studio 2015 Community which illustrates each of the concepts listed in this article in more detail which you can access via this DropBox link. If you have any issues at all accessing it, please let me know. You may need to let Nuget update the missing packages for the project to run.

Download Accompanying Solution/Source Code

Background

This article assumes some background knowledge of .NET and MVC, but not much. You should be able to spin up a new .NET MVC project (I'm using Visual Studio Community 2015), and be able to create some basic models and scaffold out some controllers and views from there. If you need help with any of these things, there are many good tutorials out there, but the one I recommend is this one - free from Plurasight (and Microsoft).

If you have any trouble understanding any of the concepts in this article, go ahead and post a comment and I will help you out as much as possible.

Implementation

This section will be broken out into the logical steps required to implement the multi-select list that we want. Our goal is to implement a multi-select list on the Player Create view, which will allow users to select/assign multiple teams to a new player they are creating.

The article will follow a specific structure:

  1. The Models - This section will take a look at the models that we are using with focus on the two important properties to the implementation: Team and Player and the database relationship that underpins the goal of this article.
  2. Implementing the View Model - We will be implementing a view model for the Player Create view to use. It will contain everything the view requires to fulfill the needs of our domain model, and therefore provides the benefits of flexibility along with separation from the domain models.
  3. Implementing the Controller Requirements - The guts of the activity will be implementing components of the controller required to parse the data into something that is usable by .NET's HTML helper methods.
  4. Implementing the View - The last part of the exercise is actually implementing the ListBox that we want to display to the users.
  5. Receiving the Data from the ViewModel - Here, we will look at what to do with the data once the user submits the form back to your [HttpPost] controller action.

1. The Models

We will start with our basic MVC setup. I have a couple of simple models - a Player and a Team. I have created a many-to-many relationship using Entity Framework and Code First (if you don't know how to do this, I suggest giving this tutorial a look but, again, just comment and I will help you out as best I can), so that a Player can be on many Teams, and Teams can have many Players assigned.

Here are the models and the database context:

public class Team
{
    public Team()
    {
        this.Players = new List<Player>();
    }

    [Key]
    public Guid TeamId { get; set; }

    [Required]
    [Display(Name = "Team Name")]
    [StringLength(128, ErrorMessage = "Team Name can only be 128 characters in length.")]
    public string Name { get; set; }

    public virtual ICollection<Player> Players { get; set; }
 }
public class Player
{
    public Player()
    {
        this.Teams= new List<Team>();
    }

    [Key]
    public Guid PlayerId { get; set; }

    [Required]
    [Display(Name = "Player Name")]
    [StringLength(128, ErrorMessage = "Player's name can only be 128 characters in length.")]
    public string Name { get; set; }

    public virtual ICollection<Team> Teams { get; set; }
 }
public class AppDbContext : DbContext
{
    public AppDbContext()
        : base("AppDbContext")
    {
    }

    public DbSet<Team> Teams { get; set; }
    public DbSet<Player> Players { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Team>()
            .HasMany(i => i.Players)
            .WithMany(i => i.Teams)
            .Map(i =>
            {
                i.MapLeftKey("TeamId");
                i.MapRightKey("PlayerId");
                i.ToTable("Team_Player");
            });
    }
 }

You will notice a couple of important details about these models and the DbContext that we have initialized:

  • We instantiate the collection of Teams and Players in the model itself (see the this.Players = new List<Player>(); line and equivalent in the Team model)
  • The override of the OnModelCreating class lets us define how we want the relationship between Teams and Players created. Because we want a many-to-many relationship, what we have defined will create us a join table in the database called "Team_Player" which will contain two primary keys in columns called "TeamId" (it will use the TeamId property of our Team model) and "PlayerId" (it will use the PlayerId property of our Player model. This is a separate topic in itself; refer to the link at the beginning of this section of this article for more details on this.

From this point on, we will skip some steps such as the actual creation of the controller and views, and assume that these are already in place but require modifications. Specifically, we will assume that no infrastructure in the view or the controller exists for the multi-select list that we want to implement.

2. Implementing the View Model

For the view model, there are a couple of requirements that we need to meet in order to pass data in between the controller and the view, and then parse in the controller to fulfill the requirements of our Player model.

  • We need an instance of .NET's MultiSelectList which we will populate with Teams' information
  • We need an instance of .NET's List<> which will use to house the Ids of the Teams the user selects (making it a List<Guid> of TeamIds)

Therefore, our view model will look like the below:

public class CreatePlayerViewModel
{
    [Required]
    [Display(Name = "Player Name")]
    [StringLength(128, ErrorMessage = "Players name can only be 128 characters in length.")]
    public string Name { get; set; }

    public List<string> TeamIds { get; set; }

    [Display(Name = "Teams")]
    public MultiSelectList Teams { get; set; }
}
You will notice that the property in our view model that houses the TeamIds in a List of strings and not Guids. This is to make it easier to pass the Ids back and forth between the view and controller.

3. Implementing the Controller Requirements

There are a couple of different ways we can achieve what we need to in the controller. We are going to review two options, with the first trying to explain the concept and the second being the much more condensed version.

Finally, we will demonstrate how to add selected items to the list, such that may be used in an Edit method, where you want the user to see which Teams are already assigned to the Player by highlighting them in the ListBox.

Option 1

First, we need to create an instance of our appDbContext, then use LINQ to store the teams in a variable (specifically, a List).

appDbContext db = new appDbContext();

var teams = db.Teams.ToList();

Then, we need to instantiate a list of SelectListItems - i.e., the objects that populate a SelectList or MultiSelectList.

List<SelectListItem> items = new List<SelectListItem>();

Then, we will loop through each of the team objects in our teams List, and will create a new SelectListItem for each one, subsequently adding the SelectListItem to our items list.

foreach (var team in teams)
{
    var item = new SelectListItem
    {
        Value = team.TeamId.ToString(),
        Text = team.Name
    };

    items.Add(item);
}

Then, we can instantiate a new MultiSelectList (or SelectList) and add our items list to it. To do this, we are going to use one of the numerous constructors for the MultiSelectList that you can review here. The one we are going to use is MultiSelectList(IEnumerable, string, string) which, for us, translates to MultiSelectList(ouritems List, the "Value" field which contains our Id, and our "Text" value which contains the text that we want to show the user in the actual list. Note that I am applying the OrderBy() method to our items list, which is going to order the list by the "Text" field in the SelectListItems list, which for us is the Name property of the Team instance.

MultiSelectList teamsList = new MultiSelectList(items.OrderBy(i => i.Text), "Value", "Text");

Then, we just need to assign this to the Teams property on our CreatePlayerViewModel, which we need to first instantiate, before passing it back to our view.

CreatePlayerViewModel model = new CreatePlayerViewModel { Teams = teamsList };

return View(model);
Option 2

The second of our two options is much simpler as we get to skip many of the above steps, jumping straight to the instantiating of the MultiSelectList. Note that we are using the same constructor as Option 1.

MultiSelectList teamsList = new MultiSelectList(db.Teams.ToList().OrderBy(i => i.Name), "TeamId", "Name");

Then, we just need to instantiate the view model, set the Teams property and return it in our view.

CreatePlayerViewModel model = new CreatePlayerViewModel { Teams = teamsList };
Adding Selected Items to the List

Adding selected items is a little tricky, as you may be tempted to do something like the first Option outlined above, do some filtering on the teams list and add the "Selected" property to the SelectListItem generation, like the below:

foreach (var team in teams)
{
    var item = new SelectListItem
    {
        Value = team.TeamId.ToString();
        Text = team.Name;
        Selected = true;
    };

    items.Add(item);
}

This will not work, because the MultiSelectList (or SelectList) will only allow you to tell it which items are selected on its instantiation - it will not learn from the Selected property of the SelectListItems.

We will use an example goal to demonstrate this. We want to "select" a series of teams that the Player is already assigned to.

The first thing we need to do is decide which constructor of the MultiSelectList object we are going to use. Again, referring to Microsoft's documentation here, we will use MultiSelectList(IEnumerable, string, string, IEnumerable), which is the same as before with the addition of the last IEnumerable.

This IEnumerable needs to store the Values that we want to tell the list to select. They need to correspond to the Values in the first IEnumerable param - i.e., the TeamId property within our teams list of Teams.

To do this, we need to first create a separate list of teams that we will call playerTeams, using LINQ to filter the teams list to what we want. We will assume that we have received via some parameter the Id of the Player that we want to filter on, call it playerId.

var playerTeams = teams.Where(t => t.Players.Contains(player)).ToList();

We need the Ids of each of these playerTeams in some enumerable object, for which we will use an array of strings (note that we will need to convert the Guid Id into a string).

// First, initialize the array to number of teams in playerTeams
string[] playerTeamsIds = new string[playerTeams.Count];

// Then, set the value of platerTeams.Count so the for loop doesn't need to work it out every iteration
int length = playerTeams.Count;

// Now loop over each of the playerTeams and store the Id in the playerTeamsId array
for (int i = 0; i < length; i++)
{
    // Note that we employ the ToString() method to convert the Guid to the string
    playerTeamsIds[i] = playerTeams[i].TeamId.ToString();
}

Now, we have the IEnumerable containing the values we need that our MultiSelectList constructor requires. We can go ahead and instantiate the MultiSelectList now and return it in our view (note that we are assuming that we're in some kind of Edit method, which is the only scenario that really makes sense to want to pre-select things in the MultiSelect list).

MultiSelectList teamsList = new MultiSelectList(db.Teams.ToList().OrderBy(i => i.Name), 
	"TeamId", "Name", playerTeamsIds);

EditPlayerViewModel model = new EditPlayerViewModel {  Teams = teamsList }; 

4. Implementing the View

For the view, we will be using one of .NET MVC's Html helper methods = the ListBoxFor method - within our CreatePlayer form. This will look like the following:

@Model MultiSelectListSample.Models.CreateUserViewModel

@using (Html.BeginForm("Create", "Players", 
	FormMethod.Post, new { @class = "form-horizontal" }))
{
    @Html.AntiForgeryToken()
    <div class="form-group">
        @Html.LabelFor(i => i.Teams, new { @class = "control-label col-md-3" })
        <div class="col-md-9">
            @Html.ListBoxFor(i => Model.TeamIds, Model.Teams, 
            	new { @class = "form-control" })
            @Html.ValidationMessageFor(i => i.Teams, 
            	"", new { @class = "text-warning" })
        </div>

Looking at the ListBoxFor helper above, we first see that the ListBox is for the TeamIds property of the view property (this is the <Model> parameter of the method). What this means is that when the user selects some teams and then submits the form, in your Create [HttpPost] method, you will receive the Ids of the teams that the user has selected in that List. We will look at this below.

The next parameter the ListBoxFor method wants is the MultiSelectList (or SelectList), which we find in Model.Teams (remember, this is the teamsList MultiSelectList that we created).

The last property is the htmlattributes where we can apply the class that we want, or any other attributes such as Id, etc.

5. Receiving the Data from the View Model

Once the user submits the form, you will receive the model back to your [HttpPost] Create method, which would be structured to accept a CreateViewModel parameter:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name, TeamIds")] CreatePlayerViewModel model)
{
    ...
}

Note that we are only interested in the TeamIds property, not the Teams property - this is only used to instantiate the MultiSelectList and contains none of the data we want here.

Now that we have the TeamIds back from the view, we can create our new Player. First, we will instantiate the Player with just the PlayerId and the Name property, seeing as how our Player model doesn't require the Teams property contain data.

if (ModelState.IsValid)
{
    Player player = new Player
    {
        PlayerId = Guid.NewGuid(),
        Name = model.Name
    };
    
    ...
}

Now, we need to find the Teams that belong to the TeamIds that we have received back from the model, and assign them to the player that we have just created. Assuming we have instantiated an instance of our appDbContext called db somewhere above in our controller:

if (model.TeamIds != null)
{
    foreach (var id in model.TeamIds)
    {
        // Convert the id to a Guid from a string
        var teamId = Guid.Parse(id);
        // Retrieve team from database...
        var team = db.Teams.Find(teamId);
        // ... and add it to the player's Team collection
        try
        {
            player.Teams.Add(team);
        }
        catch (Exception ex)
        {
            return View("Error", new HandleErrorInfo(ex, "Players", "Index"));
        }
    }
}
// Add new Player to db & save changes
try
{
    db.Players.Add(player);
    db.SaveChanges();
}
catch (Exception ex)
{
    return View("Error", new HandleErrorInfo(ex, "Players", "Index"));
}

Now, we have added each of the Teams that the user selected to our new Player, player.

Note that if you're in an edit method, where the Player already has some Teams assigned, these Teams will also come back from View. After adding any Teams to the Player, you will want to remove these.

To do this, we just need to create a list of Teams which contains all the Teams in the db, less the teams the user wants to assign to the Player. We will employ the Except() method to achieve this difference. We can then loop through this list and call the Remove() method on each one.

// Check if any teams were selected by the user in the form
if (model.TeamIds.Count > 0)
{
    // First, we will instantiate a list to store each of the teams in the EditPlayerViewModel for later comparison
    List<Team> viewModelTeams = new List<Team>();
    // Now, loop over each of the ids in the list of TeamIds
    foreach (var id in model.TeamIds)
    {
        // Retrieve the team from the db
        var team = db.Teams.Find(Guid.Parse(id));
        if (team != null)
        {
            // We will add the team to our tracking list of viewmodelteams and player teams
            try
            {
                player.Teams.Add(team);
                viewModelTeams.Add(team);
            }
            catch (Exception ex)
            {
                return View("Error", new HandleErrorInfo(ex, "Players", "Index"));
            }
        }
    }
    // Now we will create a list of all teams in the db, which we will "Except" from the new player's list
    var allTeams = db.Teams.ToList();
    // Now exclude the viewModelTeams from the allTeams list to create a list of teams that we need to delete from the player
    var teamsToRemove = allTeams.Except(viewModelTeams);
    // Loop over each of the teams in our teamsToRemove List
    foreach (var team in teamsToRemove)
    {
        try
        {
            // Remove that team from the player's Teams list
            player.Teams.Remove(team);
        }
        catch (Exception ex)
        {
            // Catch any exceptions and error out
            return View("Error", new HandleErrorInfo(ex, "Players", "Index"));
        }
    }
}
// Save the changes to the db
try
{
    db.Entry(player).State = EntityState.Modified;
    db.SaveChanges();
}
catch (Exception ex)
{
    return View("Error", new HandleErrorInfo(ex, "Players", "Indes"));
}

Note that you can read how Except() works here.

Conclusion

This concludes the article on how to implement MultiSelectLists (also applies to SelectLists). It is the author's hope that in reading and following along with the code in the sample solution provided, the reader is able to save some time in implementing something that isn't exactly straightforward to implement and, to the beginner, can be a frustrating and time-consuming experience to learn.

As always, if you are an experienced coder and you see any flaws or areas of improvement, please do place a comment below.

Points of Interest

The most challenging part of this for me was creating a MultiSelectList with pre-selected values. There was a lot of information on Stack Overflow where answers were suggesting that users loop over a list of SelectListItems and mark their "Selected" property as true, then adding these items to a MultiSelectList or SelectList. This fails, however, as the "SelectedValues" property of a MultiSelectList can only be set on the MultiSelectList's instantiation.

I burned a lot of time trying to figure it out, which is what inspired me to write this article to help other newbies to .NET save some time and frustration!

History

  • 14th December, 2015: First copy
  • 15th December, 2015: Minor edits
  • 16th December, 2015: Edits and addition of sample solution

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here