5
votes

I am using a ViewModel to retrieve entered data in controller action. But the ViewModel is getting empty values in it's properties. I am creating one partial view

and in that partial view I am creating drop down lists by binding the ViewModel and then I'm rendering that partial view in other View

Below is my code

My ViewModel :

public class LookUpViewModel
    {
        RosterManagementEntities rosterManagementContext = new RosterManagementEntities();
        public  LookUpViewModel()
        {

            tblCurrentLocations = from o in rosterManagementContext.tblCurrentLocations select o;
            tblStreams = from o in rosterManagementContext.tblStreams select o;   
        }

        [Required]
        public virtual IEnumerable<tblCurrentLocation> tblCurrentLocations { get; set; }

 [Required]
        public virtual IEnumerable<tblStream> tblStreams {  get;  set; }

My Partial View:

@model PITCRoster.ViewModel.LookUpViewModel

@Html.Label("Location")
@Html.DropDownListFor(M=>M.tblCurrentLocations, new SelectList(Model.tblCurrentLocations, "LocationId", "Location"), "Select Location")
@Html.ValidationMessageFor(M=>M.tblCurrentLocations)
<br />
@Html.Label("Stream")

@Html.DropDownListFor(M => M.tblStreams, new SelectList(Model.tblStreams, "StreamId", "Stream"), "Select Streams")
@Html.ValidationMessageFor(M => M.tblStreams)

My View in which I am rendering this above partial view

@{
    ViewBag.Title = "Resources";
}
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<h2>Resources</h2>

@using (Html.BeginForm("AddResource", "Resources", FormMethod.Post))
{

    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    Html.RenderPartial("_LookUpDropDowns", new PITCRoster.ViewModel.LookUpViewModel());

    <br />
    <input type="submit" value="Create" />
}

And this is my controller action method : [HttpPost]

public void AddResource(LookUpViewModel testName)
        {
            //code
        }

When I put a debugger on my controller action method control goes to that Action method. But ViewModel object has null in it. I tried accessing entered values using FormCollection object and I'm getting all the data as expected...

Below is my code for controller action with FormCollection

 [HttpPost]
        public void AddResource(FormCollection form)
        {
            var x = form["tblStreams"]; //I get value here..
        }

Can anybody explain me why I'm not getting values in ViewModel object ? Thank you...

2
You cant bind a dropdownlist to a collection of complex objects (a <select> only posts back a single value (the value of the selected option). Your view model requires properties to bind to - e.g. public int SelectedLocation { get; set; } and ditto for the selected Streamuser3559349
What are you expecting to happen, and what values are you looking for, the way this view model is coded it will go to you data store everytime it's used and just select tblCurrentLocations and 'tblStreams'. Also you drop downs are assigning the selected value to the same list you populate in the view model constructor3dd
@StephenMuecke Can you please explain me with some code?Dhananjay Sakhare

2 Answers

4
votes

You cannot bind a dropdownlist to a collection of complex objects - in your case IEnumerable<tblCurrentLocation> and IEnumerable<tblStream>

A <select> tag only posts back a single value (the value of the selected option) so in the POST method the DefaultModelBinder is attempting to so testName.tblCurrentLocations = "1" (assuming the value of the selected option is 1) which of course fails and the property is set to null

You need a view model containing properties that you want to bind to (and ideally will include the SelectList's used by the DropDownListFor() helper)

public class LookUpViewModel
{
  [Display(Name = "Location")]
  [Required(ErrorMessage = "Please select a location")]
  public int SelectedLocation { get; set; }
  [Display(Name = "Stream")]
  [Required(ErrorMessage = "Please select a stream")]
  public int SelectedStream { get; set; }
  public SelectList LocationList { get; set; }
  public SelectList StreamList { get; set; }
}

Then in the view

@Html.LabelFor(m => m.SelectedLocation)
@Html.DropDownListFor(m => m.SelectedLocation, Model.LocationList, "-Please select-")
@Html.ValidationMessageFor(m => m.SelectedLocation)

@Html.LabelFor(m => m.SelectedStream)
@Html.DropDownListFor(m => m.SelectedStream, Model.StreamList, "-Please select-")
@Html.ValidationMessageFor(m => m.SelectedStream)

and in the controller

public ActionResult Edit()
{
  LookUpViewModel model = new LookUpViewModel();
  ConfigureViewModel(model);
  return View(model);
}

[HttpPost]
public ActionResult Edit(LookUpViewModel model)
{
  if (!ModelState.IsValid)
  {
    ConfigureViewModel(model);
    return View(model);
  }
  // model.SelectedLocation will contain the value of the selected location
  // save and redirect
}

private void ConfigureViewModel(LookUpViewModel model)
{
  // populate your select lists
  var locations = from o in rosterManagementContext.tblCurrentLocations select o;
  model.LocationList = new SelectList(locations, "LocationId", "Location");
  .... // ditto for streams
}

Note as also indicated in Maximilian's answer, your view model should only contain properties your need for the view. Your controller is responsible for populating the values. A view model should never make a call to a database - it should not even be aware that one exists.

2
votes

This answer will not solve your problem directly, but first of all I recommend you to outsource the filling of the ViewModel at least to the controller (Because otherwise it will recreate the DBContext every time the constructor is called). A ViewModel should only contain data - no logic. And secoundly I would use the DBContext in an Using statement

ViewModel:

public class LookUpViewModel
    {    
        [Required]
        public virtual IEnumerable<tblCurrentLocation> tblCurrentLocations { get; set; }

        [Required]
        public virtual IEnumerable<tblStream> tblStreams {  get;  set; }

Controller:

public ActionResult Foo()
{
    LookUpViewModel ViewModel = new LookUpViewModel();
    using(RosterManagementEntities rosterManagementContext = new RosterManagementEntities())
    {
        ViewModel.tblCurrentLocations = from o in rosterManagementContext.tblCurrentLocations select o;
        ViewModel.tblStreams = from o in rosterManagementContext.tblStreams select o; 
    }

    return View(ViewModel);
}