Selected values in Html.DropDownListFor in a dynamically-sized "2D array layout"

585 Views Asked by At

I have a view that contains quite many select elements (in the form of @Html.DropDownList or @Html.DropDownListFor). The problem is that they are arranged in a table-like manner and doubly-indexed (the number of rows and columns changes depending on the data).

It is only possible to use single-indexed properties/fields to bind to the selected value of the DropDownListFor helper, and the number of properties I'd need varies, so I wonder:

Is there an ASP.NET MVC way to get the selected values in the controller?

That is, I would now use jQuery to build some (maybe JSON) data to send to the controller manually. But first I'd like to know if there is something else I could try :)

Example project: View:

@model DropDownMatrixTest.Models.MatrixViewModel

@using (Html.BeginForm("Foo", "Home", FormMethod.Post))
{
  <table>
    <tbody>
      @for (int i = 0; i < 10; i++)
      {
        <tr>
          <td>@i-th row:</td>
          @for (int j = 0; j < 4; j++)
          {
            <td>
              @Html.DropDownListFor(m => m.SelectedValues[@i, @j],
                Model.Foos.Select(x => new SelectListItem
                {
                  Text = x.Text,
                  Value = x.Id.ToString()
                }))
            </td>
          }
        </tr>
      }
    </tbody>
  </table>
  <button type="submit">Submit</button>
}

ViewModel:

public class MatrixViewModel
{
  public IEnumerable<Foo> Foos { get; set; }
  public int[,] SelectedValues { get; set; } // I know this wouldn't work
}

Controller methods:

public ActionResult Index()
{
  MatrixViewModel vm = new MatrixViewModel
  {
    Foos = Enumerable.Range(1, 10).Select(x => new Foo { Id = x, Text = "Foo " + x })
  };
  return View(vm);
}

[HttpPost]
public ActionResult Foo(MatrixViewModel vm)
{
  // Here is where I'd like to get the selected data in some form
  return View("Index", vm);
}
1

There are 1 best solutions below

8
On BEST ANSWER

Create view models to represent you table/matrix structure

public class CellVM
{
  public int SelectedValue { get; set; }
}
public class RowVM
{
    public RowVM()
    {
        Columns = new List<CellVM>();
    }   
    public RowVM(int columns)
    {
        Columns = new List<CellVM>();
        for(int i = 0; i < columns; i++)
        {
            Columns.Add(new CellVM());
        }
    }
    public List<CellVM> Columns { get; set; }
}
public class MatrixVM
{
    public MatrixVM()
    {
        Rows = new List<RowVM>();
    }
    public MatrixVM(int columns, int rows)
    {
        Rows = new List<RowVM>();
        for(int i = 0; i < rows; i++)
        {
            Rows.Add(new RowVM(columns));
        }
        // initialize collection with the required number of rows
    }
    public List<RowVM> Rows { get; set; }
    public IEnumerable<SelectListItem> Foos { get; set; }
}

In the GET method, initialize a new instance of MatrixVM and populate the SelectList

MatrixVM model = new MatrixVM(4, 4)
{
  Foos = Enumerable.Range(1, 10).Select(x => new SelectListItem(){ Value = x.ToString(), Text = "Foo " + x })
};
return View(model);

And in the view

@model MatrixVM
@using (Html.BeginForm())
{
  <table>
    <tbody>
      @for(int r = 0; r < Model.Rows.Count; r++)
      {
        <tr>
          @for(int c = 0; c < Model.Rows[r].Columns.Count; c++)
          {
            <td>
              @Html.DropDownListFor(m => m.Rows[r].Columns[c].SelectedValue, Model.Foos)
            </td>
          }
        </tr>
      }
    <tbody>
  </table>
  <input type="submit" />
}

Side note: The example creates a single SelectList in the controller which is efficient and suitable for a create view, but if your editing existing values, you will need to generate a new SelectList and set the Selected property in each iteration due to a limitation in DropDownListFor() when used in a loop