4

I'm trying to create a view that will need 2 dropdown lists with MVC 3. In my only other MVC app we used the Telerik controls that used the Ajax method to populate data. Now on this project we don't use third party controls so I will be using the MVC SelectList for dropdowns. I've been reading a lot of articles on how to populate a SelectList but none of them say the same thing twice, always a different way to create the model, some use ViewData or ViewBag to hold the collections and pass to the view, etc.. no consistency.

What is the best accepted method to populate a dropdown in an MVC view that uses the model itself for the data, not ViewData. And when the user makes a selection from the list, submits and the HttpPost action is called, how do I access the selected value from the Model property of the select list property?

This is my current Model:

public class TemporaryRegistration {
    [Required]
    [Email(ErrorMessage = "Please enter a valid email address.")]
    [Display(Name = "Email address")]
    public string Email { get; set; }

    [Required]
    [Integer]
    [Min(1, ErrorMessage = "Please select an entity type.")]
    [Display(Name = "Entity Type")]
    public IEnumerable<SelectListItem> EntityType { get; set; }

    [Required]
    [Integer]
    [Min(1, ErrorMessage = "Please select an associated entity.")]
    [Display(Name = "Associated Entity")]
    public IEnumerable<SelectListItem> AssociatedEntity { get; set; }
}

This is my current view, it's only using TextBoxFor where I need to use the dropdowns, how do I turn them into dropdowns?

@model Web.Models.TemporaryRegistration

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
    <legend>Create New ELM Select User</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email)
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.EntityType)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.EntityType)
            @Html.ValidationMessageFor(model => model.EntityType)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.AssociatedEntity)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.AssociatedEntity)
            @Html.ValidationMessageFor(model => model.AssociatedEntity)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

This is my current Post action: How do I get the selected values out?

[HttpPost]
public ActionResult CreateUser(TemporaryRegistration registrationModel) {
    string newRegistrationGUID = string.Empty;
    if (!ModelState.IsValid) {
        return View();
    }

    TemporaryRegistrationEntity temporaryRegistration = null;
    temporaryRegistration = new TemporaryRegistrationEntity(registrationModel.Email, registrationModel.EntityType, registrationModel.AssociatedEntity);
    newRegistrationGUID = temporaryRegistration.Save();
    return Content("New registration was created with GUID " + newRegistrationGUID);
}
CD Smith
  • 6,597
  • 7
  • 40
  • 66

2 Answers2

9

Continuing from comment...

Let's say you have a Model called Toy. Toy has properties like Name, Price, and Category:

public class Toy()
{
    public string Name;
    public double Price;
    public string Category
}

Now you want to build a form View to add a Toy, and people need to be able to select a category from a drop down of possibilities... but you don't want to do this via ViewData or ViewBag for some reason.

Instead of passing the Model to the View, create a ToyViewModel that has Name, Price, Category... but also has a collection of categories to populate the drop down:

public class ToyViewModel()
{
    public string Name;
    public double Price;
    public string Category

    public ICollection<string> Categories;
}

Now your controller does this:

public ActionResult GetToyForm()
{
    var viewModel = new ToyViewModel();
    viewModel.Categories = _service.GetListOfCategories();
    return View(viewModel);
}

Your View is bound to the ViewModel, and you use the model.Categories collection to populate your dropdown. It should look something like:

@Html.DropDownListFor(model => model.Category, model.Categories)

When you submit it, your controller does something like:

[HttpPost]
public ActionResult CreateToy(ToyViewModel _viewModel)
{
    var model = new Toy();
    model.Name = _viewModel.Name
    // etc.

    _service.CreateToy(model);

    // return whatever you like.
    return View();
}

It is good practice to make ViewModels for binding to Views so you can tailor them to the needs of your presentation layer, while having your Models remain close to the data layer and business logic.

one.beat.consumer
  • 9,414
  • 11
  • 55
  • 98
  • Thanks for the clarification, that's exactly the type of architecture I'm using. Requirements actually have me using 3 different models, the ViewModel, a Business Entity model that is translated from the ViewModel at post time and a DTO for lightweight network traffic. – CD Smith Jan 06 '12 at 14:22
  • 1
    @one.beat.consumer I fail to see how this is in any way different from the original post. The view model in the original post is `TemporaryRegistration` and the entity/persistence model is `TemporaryRegistrationEntity`. The only difference from the original in this answer at all is that yours leaves out the metadata and uses the **exact** same pattern for including categories I had used, which was what the question was about... – Nick Larsen Jan 06 '12 at 14:39
  • Thanks, both of you together helped me see the whole picture and it looks like I'm doing it the correct way. TemporaryRegistrationEnitity is indeed my business model whereas TemporaryRegistration is merely the viewmodel. I'm renaming it to TemporaryRegistrationViewModel for clarity. – CD Smith Jan 06 '12 at 14:45
  • @NickLarsen `1.` for me it wasn't clear how we was using his models, and `2.` the difference between our posts is i provided explanation about modeling and you did not. I began explaining this in comments on your answer thread because I felt it was important but did not want to "steal your answer". Obviously there was not enough room. If you want the credit, I'm fine with that. – one.beat.consumer Jan 06 '12 at 19:16
  • 1
    For the record, I +1'd your answer *and* your first comment long before I posted anything of my own. – one.beat.consumer Jan 06 '12 at 19:17
  • @one.beat.consumer I'm not upset, marking an answer as correct is up to the asker. I don't think it's necessary to explain to someone what they are already doing, of course you did because it wasn't clear to you how he was using his models. – Nick Larsen Jan 06 '12 at 20:37
2

The EntityType doesn't need to be a list unless you are accepting multiple values back (such as a list box would send). When you display the drop down list (which generally only selects a single value) on the view, you just need to provide the choices for it some other way, such as sending the list in another property of the view model.

public class TemporaryRegistration {
        [Required]
        [Email(ErrorMessage = "Please enter a valid email address.")]
        [Display(Name = "Email address")]
        public string Email { get; set; }

        [Required]
        [Integer]
        [Min(1, ErrorMessage = "Please select an entity type.")]
        [Display(Name = "Entity Type")]
        public int EntityType { get; set; }

        public IEnumerable<SelectListItem> EntityTypes { get; set; }

        [Required]
        [Integer]
        [Min(1, ErrorMessage = "Please select an associated entity.")]
        [Display(Name = "Associated Entity")]
        public IEnumerable<SelectListItem> AssociatedEntity { get; set; }
}

Then use the drop down list helper.

@Html.DropDownListFor(model => model.EntityType, model.EntityTypes)
Nick Larsen
  • 18,631
  • 6
  • 67
  • 96
  • 2
    Ok, so when I post back, I'm assuming that you mean that EntityType in the model will be set to the selected value of EntityTypes? And, in my Get method action on the controller I assume I just need to instantiate a new viewmodel, get a collection of EntityTypes and set them as the value of myModel.EntityTypes so they get passed in to the view? – CD Smith Jan 05 '12 at 19:20
  • @CDSmith - NickLarsen is right, but the key here is you should be creating a ViewModel to the view... this view model should have your Model properties and a List/Collection of the options/choices used to populate the drop down. Passing your model directly is sort of bad practice. – one.beat.consumer Jan 05 '12 at 19:26
  • @one.beat.consumer.. you mean in my Get action method correct? Can you give an example what you mean? I think I know where you are going with that comment but would like clarifation... thanks – CD Smith Jan 05 '12 at 19:30
  • @one.beat.consumer What do you mean by passing directly? Which is which? – CD Smith Jan 05 '12 at 20:57
  • I'm guessing he means that there should be 2 models, 1 for the view, and 1 for the form. The form model should contain only the fields that can be returned by the form, the view model should contain everything that is needed to build the view as you like it. There are many ways of getting the form onto the view, generally by making the form model a property of the view model. Of course it's up to @one.beat.consumer to confirm his intention. – Nick Larsen Jan 05 '12 at 21:37
  • I posted a complete example for you as an answer - but yes, essentially two "models" - one for View/Presentation, and one for the Logic/Persistence layers. Look up "MVVM architecture" or search "MVC with ViewModels" if you want more examples of why this is important - mainly "separation of concerns" and "single responsibility" dogma practice. – one.beat.consumer Jan 06 '12 at 00:57