views:

845

answers:

3

I'm fairly new (ok, REALLy new) to generics but I love the idea of them. I am going to be having a few drop-down lists on a view and I'd like a generic way to take a list of objects and convert it to a list of SelectListItems

What I have now:

public static IEnumerable<SelectListItem> ToSelectListItems(
     this IEnumerable<SpecificObject> items, long selectedId)
    {
        return
            items.OrderBy(item => item.Name)
            .Select(item =>
                    new SelectListItem
                    {
                        Selected = (item.Id == selectedId),
                        Text = item.Name,
                        Value = item.Id.ToString()
                    });
    }

Trouble is, I'd need to repeat that code for each drop-down as the objects have different fields that represent the Text property of the SelectListItem

Here is what I'd like to accomplish:

public static IEnumerable<SelectListItem> ToSelectListItem<T>(this IEnumerable<T> items, string key, string value, int SelectedId) {
    // I really have no idea how to proceed from here :(
}
+1  A: 

In order for this to work as written, your type T will need to implement some interface which provides Name and Id properties:

 public interface ISelectable
 {
     string Name { get; }
     int Id { get; }
 }

With this in place, you can do:

public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IEnumerable<T> items, long selectedId) 
     where T : ISelectable
{
    return
        items.OrderBy(item => item.Name)
        .Select(item =>
                new SelectListItem
                {
                    Selected = (item.Id == selectedId),
                    Text = item.Name,
                    Value = item.Id.ToString()
                });
}

This is required in order to use the Name and Id properties within your extension method... You could, instead, provide a different means of receiving these (ie: passing delegates), but that may or may not increase the maintenance cost in your scenario.

Reed Copsey
+4  A: 

You could pass in delegates to do the comparisons, and property retrieval. Something like this:

public static IEnumerable<SelectListItem> ToSelectListItem<T>(this IEnumerable<T> items, 
    int selectedId, Func<T,int> getId, Func<T, string> getName, 
    Func<T, string> getText, Func<T, string> getValue)
{
    return
        items.OrderBy(item => getName(item))
        .Select(item =>
                new SelectListItem
                {
                    Selected = (getId(item) == selectedId),
                    Text = getText(item),
                    Value = getValue(item)
                });
}

Then you would use it like so:

var selected = specificObjects.ToSelectListItem(10, s => s.Id, s=> s.Name, s => s.Name, s => s.Id.ToString());
Abe Heidebrecht
You'd also need a getName delegate (per Bruno's answer) -- the item.Name reference in the OrderBy clause currently won't compile.
itowlson
Hmm, I want to mark both this and the next questions as answers :). Both are exactly what I'm looking for!
Dan
@itowlson - thanks, I missed that one.
Abe Heidebrecht
+5  A: 

Well, you could do something like this:

public static IEnumerable<SelectListItem> ToSelectListItems(
     this IEnumerable<T> items, 
     Func<T,string> nameSelector, 
     Func<T,string> valueSelector, 
     Func<T,bool> selected)
{
     return items.OrderBy(item => nameSelector(item))
            .Select(item =>
                    new SelectListItem
                    {
                        Selected = selected(item),
                        Text = nameSelector(item),
                        Value = valueSelector(item)
                    });
}
bruno conde
Cancel that, had to do ToSelectListItems<T> :)
Dan
How would I call this and, particularly use the 'selected' delegate?
Dan