Craig Roberts
Craig Roberts

Reputation: 171

Passing Model data from View to Controller

I am trying to pass the Model data from a View (and PartialView within the View) back to the Controller upon HttpPost. (Adapted from Pass SelectedValue of DropDownList in Html.BeginForm() in ASP.NEt MVC 3)

Why? I want to show a list of assets each with a DropDownList and number of options. Upon submission of form to read the selected items from DropDownList.

My 2 (simplified) models:

public class Booking
{
    public int BookingID { get; set; }
    public int StoreID { get; set; }
    ...
    public IEnumerable<AssetShort> Assets { get; set; }
}

and

public class AssetShort
{
    public int AssetID { get; set; }
    ....
    public int SelectedAction { get; set; }
    public IEnumerable<SelectListItem> ActionList { get; set; }
}

In my Booking Controller > Create I build the List:

public ActionResult Booking(int id)
    {
     // get myBag which contains a List<Asset>
     //  booking corresponds to 'id'

        var myAssets = new List<AssetShort>();
        foreach (var a in myBag.Assets)
        {
            var b = new AssetShort();
            b.AssetID = a.ID;
            b.SelectedAction = 0;
            b.ActionList = new[]
                { 
            new SelectListItem { Selected = true, Value = "0", Text = "Select..."},
            new SelectListItem { Selected = false, Value = "1", Text = "Add"},
            new SelectListItem { Selected = false, Value = "2", Text = "Remove"},
            new SelectListItem { Selected = false, Value = "3", Text = "Relocate"},
            new SelectListItem { Selected = false, Value = "4", Text = "Upgrade"},
            new SelectListItem { Selected = false, Value = "5", Text = "Downgrade"}
                };
            myAssets.Add(b);
        };

        var model = new BookingRequirementsViewModel
            {
                BookingID = booking.ID,
                StoreID = booking.StoreID,
                Assets = myAssets.ToList(),
            };
        return View(model);

My View:

@model uatlab.ViewModels.BookingRequirementsViewModel
@{
    ViewBag.Title = "Booking step 2";
}
<h4>Your booking ref. @Model.BookingID</h4>
@using (Html.BeginForm("Booking2", "Booking", FormMethod.Post))
{
<fieldset>
  @Html.AntiForgeryToken()
  @Html.HiddenFor(model => model.StoreID)

  @Html.Partial("_Assets", Model.StoreAssets)

  <input type="submit" value="Cancel" class="btn btn-default" />
  <input type="submit" value="Next" class="btn btn-default" />
</fieldset>
}

The Partial View includes

@foreach (var item in Model)
{
    <tr>
        <td>@item.Name</td>
        <td>@item.Number</td>
        <td>@Html.DropDownListFor(modelItem=>item.SelectedAction, item.ActionList)</td>
    </tr>
}

So, all this works fine in the browser and I can select dropdowns for each asset listed but when I submit the only value posted back is the StoreID as it is in a "HiddenFor".

The booking2 controller has the model for a parameter:

public ActionResult Booking2(BookingRequirementsViewModel model)
{
    //loop through model.Assets and display SelectedActions
}

Let me make it clear what the problems is - in Booking2 controller the Model is null when viewed in Debug mode and I get error "Object reference not set to an instance of an object."

Any ideas please how to pass back the Model to controller from view?

Regards Craig

Upvotes: 0

Views: 2018

Answers (1)

user3559349
user3559349

Reputation:

You need to create an EditorTemplate for AssetShort. I also suggest moving ActionList to the BookingRequirementsViewModel so your not regenerating a new SelectList for each AssetShort

The models you have posted aren't making sense. Your controller has var model = new BookingRequirementsViewModel { ..., Assets = myAssets.ToList() }; but in the view you refer to @Html.Partial("_Assets", Model.StoreAssets)? Are these 2 different properties. I will assume that StoreAssets is IEnumerable<AssetShort>

/Views/Shared/EditorTemplates/AssetShort.cshtml

@model AssetShort
<tr>
  <td>@Html.DispayFor(m => m.Name)</td>
  ....
  <td>
    @Html.DropDownListFor(m => m.SelectedAction, (IEnumerable<SelectListItem>)ViewData["actionList"], "--Please select--")
    @Html.ValidationMessageFor(m => m.SelectedAction)
  </td>
</tr>

In the main view

@model uatlab.ViewModels.BookingRequirementsViewModel
....
@using (Html.BeginForm()) // Not sure why you post to a method with a different name
{
  ....
  @Html.HiddenFor(m => m.StoreID)
  @Html.EditorFor(m => m.StoreAssets, new { actionList = Model.ActionList })
  ....
}

In the controller

public ActionResult Booking(int id)
{
  ....
  var model = new BookingRequirementsViewModel
  {
    BookingID = booking.ID,
    StoreID = booking.StoreID,
    Assets = myBag.Assets.Select(a => new AssetShort()
    {
      AssetID = a.ID,
      SelectedAction = a.SelectedAction, // assign this if you want a selected option, otherwise the "--Please select--" option will be selected
      ....
    })
  };
  ConfigureViewModel(model); // Assign select list
  return View(model);
}

And a separate method to generate the SelectList because it needs to be called in the GET method and again in the POST method if you return the view. Note use the overload of DropDownListFor() to generate the option label (null value) as above, and there is no point setting the Selected property (the value of SelectedAction determines what is selected, not this)

private ConfigureViewModel(BookingRequirementsViewModel model)
{
  model.ActionList = new[]
  {
    new SelectListItem { Value = "1", Text = "Add"},
    ....
    new SelectListItem { Value = "5", Text = "Downgrade"}
  };
}

and the POST

public ActionResult Booking(BookingRequirementsViewModel model)
{
  if (!ModelState.IsValid)
  {
    ConfigureViewModel(model); // Re-assign select list
    return View(model);
  }
  // save and redirect
}

I recommend also making SelectedAction nullable with the [Required] attribute so you get client and server side validation

public class AssetShort
{
  public int AssetID { get; set; }
  ....
  [Required]
  public int? SelectedAction { get; set; }
}

Upvotes: 1

Related Questions