views:

311

answers:

3

This is what my data model classes look like:

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Position Position { get; set; }
}

public class Position
{
    public string Title { get; set; }
}

I have a Create view where I want to have two text boxes for first name and last name, and then a dropdown box that has the position title. I tried doing it this way:

View (only the relevant part):

<p>
    <label for="Position">Position:</label>
    <%= Html.DropDownList("Positions") %>
</p>

Controller:

//
// GET: /Employees/Create

public ActionResult Create()
{
    ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);

    return View();
}

//
// POST: /Employees/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd)
{
    try
    {
        employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
        _employees.AddEmployee(employeeToAdd);

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

However, when I click submit, I get this exception:

System.InvalidOperationException was unhandled by user code
Message="There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Positions'."

I'm pretty sure I'm doing this wrong. What is the correct way of populating the dropdown box?

+1  A: 

In the Create() (WITH POST ATTRIBUTE) employee since the ViewData["Positions"] is not set you are getting this error. This value should form part of your post request and on rebinding after post should fetch it from store or get it from session/cache if you need to rebind this..

Remember ViewData is only available for the current request, so for post request ViewData["Positions"] is not yet created and hence this exception.

You can do one quick test... override the OnActionExecuting method of the controller and put the logic to fetch positions there so that its always availlable. This should be done for data that is required for each action... This is only for test purpose in this case...

 // add the required namespace for the model...
 protected override void OnActionExecuting(ActionExecutingContext filterContext)
 {
    // add your logic to populate positions here...
    ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);

 }

There may be other clean solutions to this as well probably using a custom model binder...

rajesh pillai
+2  A: 

You can store:

(string)ViewData["Positions"]};

in a hiddn tag on the page then call it like this

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd, string Positions)
{
Hmm, storing things in hidden fields sounds like something from Webforms. Is there no better way of doing this? It might work for only one or two fields but imagine if you have a form with 15 different fields that get their data from a data model. That would get really ugly.
Daniel T.
I think the action method looks great, but I don't believe the hidden input should be necessary. `string Positions` should get bound for you automatically based on whatever was selected in the dropbox.
Funka
A: 

I believe that ViewData is for passing information to your View, but it doesn't work in reverse. That is, ViewData won't be set from Request.Form. I think you might want to change your code as follows:

// change following
employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
// to this?
employeeToAdd.Position = new Position {Title = (string)Request.Form["Positions"]};

Good luck!

Funka
P.S. you are missing an `s` in your `<label for="Position">`
Funka
What is the for attribute for anyway?
Daniel T.
it is supposed to match up the label with the input. This is for greater accessibility, for people using screen-readers, for example, so they know what they are supposed to be filling out. Did the change I suggested work?
Funka
I should note that I added this answer only because your original code looked like it only had a minor error that I had spotted. However, the solution offered by luke101 ("auto-binding" via the action method parameters) would be preferable, rather than having to deal directly with `Request.Form`.
Funka
I'll try it out tomorrow when I go back to work, but I'm concerned about what I should do in situations where there's multiple dropdown boxes that require inputs. The example I provided was just a very simple example of what I'm trying to do; on the actual form, there needs to be around 6-8 dropdown/combo boxes pulling data from different tables.
Daniel T.