1

I have a class that I populate when a user navigates to a certain page, /Home/About, within my MVC4 application. I populate a class with data and I would like to have in my view that data in a drop down list.

My class looks like this: (UPDATED)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

public class WorkSection : List<WorkSection>
{
    [Required]
    [Display(Name = "WorkSection")]
    public int ID { get; set; }
    public string Code { get; set; }

    public SelectList WorkSections { get; set; }

    public WorkSection()
    {
        // Default Constructor
    }

    public WorkSection(int id, string code)
    {
        this.ID = ws_id;
        this.Code = code;
    }
}

How do I take this populated List of type WorkSection and make that the data source for my drop down list? I would like to display the Code and Source field in a concatenated fashion, like "Code:Source" within the drop down list with the ID as the selected item's value.

UPDATE for ActionResult, where code will be called from on /Home/About

    public ActionResult About()
    {
        WorkSection model = new WorkSection();
        OracleConnection con = new OracleConnection();
        con.ConnectionString = "omitted";

        try
        {
            con.Open();
        }
        catch (Exception ex)
        {
            throw ex;
        }

        try
        {
            OracleDataReader reader = null;
            // Work Section
            OracleCommand cmd = new OracleCommand("SELECT ID, CODE FROM MyTable ORDER BY CODE", con);

            reader = cmd.ExecuteReader();

            while (reader.Read())
            {
                model.Add(new WorkSection()
                {
                    ID = Int16.Parse(reader["ID"].ToString()),
                    Code = reader["CODE"].ToString()
                });
            }

            model.WorkSections = BuildSelectList(model.WorkSections, m => m.ID, m => m.Code);

            con.Close();
            con.Dispose();
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return View(model);
    }
Brian Evans
  • 235
  • 1
  • 4
  • 21

2 Answers2

2

First up, we need a view model to encapsulate the data for the view:

public class TestViewModel
{
    [Required]
    [Display(Name = "Work section")]
    // This represents the selected ID on the dropdown
    public int WorkSectionId { get; set; }
    // The dropdown itself
    public SelectList WorkSections { get; set; }
    // other properties
}

Next up, we need a way to populate the SelectList. I wrote a custom method a while ago to do just that:

private SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
    Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
    object selectedValue = null)
{
    var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
    var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;

    return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}

This uses expression trees for type-safety, ensuring problems are caught at compile-time, rather than run-time. SelectList also uses one property for the text key and one for the value key. In your situation, this obviously creates a problem, because you want to combine Code and Source to form the text key. In order to get around that, you'll need to create a new property in WorkSection that combines both:

public string CodeSource
{
    get { return this.Code + ":" + this.Source; }
}

That way, you can use that to create the SelectList as normal. To do that, your action might like something like:

public ActionResult Index()
{
    var workSections = // ... fetch from database

    TestViewModel model = new TestViewModel();
    model.WorkSections = BuildSelectList(workSections, m => m.ID, m => m.CodeSource);

    return View(model);
}

You can use that in the view like so:

@Html.DropDownListFor(m => m.WorkSectionId, Model.WorkSections, "--Please Select--")
@Html.ValidationMessageFor(m => m.WorkSectionId)

One final note on BuildSelectList. The method has saved me a lot of time when dealing with dropdowns in general. So much so that I now define it as a public method on a base controller, which I then derive all of my controllers from. However, if you want to do that, you'll want to mark it with the [NonAction] attribute so it doesn't interfere with routing.

Update per comments

public class BaseController : Controller
{
    [NonAction]
    public SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
        Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
        object selectedValue = null)
    {
        var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
        var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;

        return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
    }
}

Then you'd derive your controllers from BaseController instead:

public HomeController : BaseController
{
    // 
}
John H
  • 14,422
  • 4
  • 41
  • 74
  • John, talk to me more about that.....creating a public method on a base controller. I imagine I create a controller that every other controller inherits from, for example MyController : BaseController? This seems like a really good idea, just want to make sure I get it right. – Brian Evans Nov 12 '13 at 03:23
  • @BrianEvans Yeah, that's it mate. Give me a sec and I'll add an example so you can see it. – John H Nov 12 '13 at 03:25
  • @BrianEvans There we go. – John H Nov 12 '13 at 03:28
  • Ok John, I have updated my class/entity above. I am left with one error: 'System.Web.Mvc.SelectListItem' does not contain a definition for 'ID' and no extension method 'ID' accepting a first argument of type 'System.Web.Mvc.SelectListItem' could be found (are you missing a using directive or an assembly reference?) – Brian Evans Nov 12 '13 at 03:46
  • @BrianEvans Is `WorkSection` a model that you're passing directly to your view? My example relied on it being contained in a view model, which is then passed to the view. – John H Nov 12 '13 at 03:50
  • I am updating the code above with the entire ActionResult About() where this code will be called from.... – Brian Evans Nov 12 '13 at 03:52
  • @BrianEvans Alright, there's a few things going on here. Firstly, you should really be using view models in your views. For a really good answer that covers why, see [here](http://stackoverflow.com/a/4878956/729541). That means your view should really be strongly-typed to something like `WorkSectionViewModel`, rather than `WorkSection`, directly. The initial downside of this is you have to map from models to view models, and vice versa. However, [AutoMapper](http://automapper.codeplex.com/) is used for exactly that purpose. – John H Nov 12 '13 at 04:00
  • @BrianEvans It's 4am here, so I should really go, but I do have a working project setup so you can take a look at it. Would you like me to upload it somewhere before I go, so you can see how it all fits together? – John H Nov 12 '13 at 04:02
  • @BrianEvans You're welcome. :) It's all sent, so you should delete your email address from here so you don't get spammed to death. Hope that helps. – John H Nov 12 '13 at 04:10
1

@Hmtl.DropdownListFor(m=>m.YourNameForSelectedWorkSectionId, Model.WorkSections.Select(x => new SelectListItem { Text = x.Code +":"+x.Source, Value = x.ID}))

felickz
  • 4,292
  • 3
  • 33
  • 37