Dictionary<T> of List<T> and ListViews in ASP.NET

17,411

Solution 1

Your problem arises because it doesn't make sense to databind ddlListOfBars in DataBindFooList(), because there isn't just one DropDownList to databind. When you call lv.DataBind(), the ListView creates a copy of your ItemTemplate for each Foo, each containing a ddlListOfBars. You need to tell it to bind each DropDownList to the right List<Bar> as it goes. You can do this by setting the datasource of ddlListOfBars with data binding expressions rather than in the code behind:

<ItemTemplate>
  <%#Eval("Key.Name") %>
  <asp:DropDownList
        ID="ddlListOfBars"
        runat="server"
        DataSource='<%#Eval("Value")%>'
        DataValueField="ItemValue"
        DataTextField="ItemName" />
</ItemTemplate>

Note that you also need to use "Key.Name" to get to the name property of your Foo. The individual dictionary items that you're binding to are KeyValuePair<Foo,List<Bar>>, which has a Key property and a Value property to access the Foo and List<Bar> respectively.

Edit
If you've got a lot going on in your ItemTemplate then it can be useful to move the contents out into a user control. The user control can have a strongly-typed property to access the DataItem, and has strongly-typed access to its child controls. This gets you the flexibility of the ItemDataBound event without all the the casting and FindControl() noise. I doubt I'd bother in this case, but it would go something like

<asp:ListView ID="lvFooList" runat="server">
<LayoutTemplate>
  <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</LayoutTemplate>
  <ItemSeparatorTemplate>
     <hr />
  </ItemSeparatorTemplate>
<ItemTemplate>
  <uc:ListViewContents DataItem='<%# Container.DataItem %>' />
</ItemTemplate>

ListViewContents.ascx...

<asp:Label ID="lbName" runat="server"/>
<asp:DropDownList ID="ddlListOfBars" runat="server"></asp:DropDownList>

ListViewContents.ascx.cs...

public KeyValuePair<Foo,List<Bar>> DataItem
{
    get; set;
}

protected override void OnDataBinding(EventArgs e)
{
    base.OnDataBinding(e);

    lbName.Text = DataItem.Key.Name;

    ddlListOfBars.DataTextField = "ItemName";
    ddlListOfBars.DataValueField = "ItemValue";
    ddlListOfBars.DataSource = DataItem.Value;
    ddlListOfBars.DataBind();   
}

Solution 2

Is something like this what you want:

   <asp:ListView ID="lvFooList" runat="server">
    <LayoutTemplate>
      <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
    </LayoutTemplate>
      <ItemSeparatorTemplate>
         <hr />
      </ItemSeparatorTemplate>
    <ItemTemplate>
      <asp:Label ID="lbName" runat="server"/>
      <asp:DropDownList ID="ddlListOfBars" runat="server"></asp:DropDownList>
    </ItemTemplate>
   </asp:ListView>

Then I wrote a very quick nasty test

    public class Foo
    {
        public string Name;
    }

    public class Bar
    {
        public string ItemName { get; set; }
        public string ItemValue { get; set; }
    }

    protected void Page_Load(object sender, EventArgs e)
    {

        var fooKey1 = new Foo() {Name = "foo1"};
        var barList1 = new List<Bar>()
               {
                   new Bar() {ItemName = "bar1", ItemValue = "barV1"},
                   new Bar() {ItemName = "bar2", ItemValue = "barV2"}
               };
        var fooKey2 = new Foo() {Name = "foo2"};
        var barList2 = new List<Bar>()
               {
                   new Bar() {ItemName = "bar3", ItemValue = "barV3"},
                   new Bar() {ItemName = "bar4", ItemValue = "barV4"}
               };

        var dicFooBar = new Dictionary<Foo, List<Bar>>() {{fooKey1, barList1}, {fooKey2, barList2}};

        lvFooList.ItemDataBound += lvFooList_ItemDataBound;
        lvFooList.DataSource = dicFooBar;
        lvFooList.DataBind();
    }

    void lvFooList_ItemDataBound(object sender, ListViewItemEventArgs e)
    {
        var dataItem = (ListViewDataItem)e.Item;
        var fooBarList = (KeyValuePair<Foo, List<Bar>>)dataItem.DataItem;

        ((Label) dataItem.FindControl("lbName")).Text = fooBarList.Key.Name;

        var ddlListOfBars = (DropDownList) dataItem.FindControl("ddlListOfBars");
        ddlListOfBars.DataTextField = "ItemName";
        ddlListOfBars.DataValueField = "ItemValue";
        ddlListOfBars.DataSource = fooBarList.Value;
        ddlListOfBars.DataBind();
    }

Seems to do what you want, but my code is just quick test code, so be warned. It did render as expected though.

Share:
17,411

Related videos on Youtube

George Stocker
Author by

George Stocker

Updated on February 25, 2020

Comments

  • George Stocker
    George Stocker about 4 years

    Preamble

    I'm asking this question because even though I've read through a lot of ListView resources, I'm still not 'getting' it.

    Background

    I have a bunch of Foo's that have a list of items associated with them (known as Bar), and I'm pulling them from the Data Access/Business Logic layer as Dictionary that holds a Foo and its associated Bars. I'd like to spit these items out in on the Webpage into a ListView that holds the Foo.Name on the left, and the List<Bar> on the right in a dropdownlist. (Shown with my beautiful ASCII art below):

    ListView

    ------------------------------------------------------------------
    |           Name Of Item          |  DropDownList (of List<T>)   |
    |---------------------------------|  _____________________       |
    |                foo1             |  |     bar1      | v |       |
    |                                 |  |_______________|___|       |  
    ------------------------------------------------------------------
    |                                 |  DropDownList (of List<T>)   |
    |                                 |  _____________________       |
    |                foo2             |  |     bar2      | v |       |
    |                                 |  |_______________|___|       |
    ------------------------------------------------------------------
    

    Alright, here's what's going on. This is a ListView; The items are pulled from a database into a Dictionary<Foo, List<Bar>>. I'm trying to get the Key Value from the dictionary to show up under 'Name of Item', and am trying to get the `List<T> Bar' to show up as a DropDownList on the right side of the ListView.

    Class Diagrams

    -----------------          -----------------
    |   Foo         |          |  Bar          |
    -----------------          -----------------
    |  Id           |          |  ItemName     |
    |  Name         |          |  ItemValue    |
    |  BarID        |          |               |
    -----------------          -----------------
    

    So to recap, I want to place the Dictionary.Key "Name" into the left side of the ListView, and the Dictionary.Value (which happens to be a list) into a DropdownList on the right side.

    So that, for every Key/Value pair, there'd be a Name and a dropdown list that would house each Key's Value.

    But I'm running into problems (obviously), and am hoping someone can tell me what I'm doing wrong.

    Code Behind:

      protected Dictionary<Foo, List<Bar>> FooDictionary
            {
                get; 
                set; 
            }
    
    protected void DataBindFooList(List<int> SelectedFoos)
            {
                System.Web.UI.WebControls.ListView lv = lvFooList;
    
    try
                {
                    Dictionary<Foo, List<Bar>> fooDictionary =
                        new Dictionary<Foo, List<Bar>>();
    
                    foreach (int Foo in SelectedFoos)
                    {
                     // Build List of Foos to add as Dictionary.Keys
                     fooDictionary.Add(fooScalar, Bar)                     
                    }
                    FooDictionary = fooDictionary;
                    lv.DataSource = FooDictionary;
                    lv.DataBind();
                    ddlListOfBars.DataSource = FooDictionary;
                    ddlListOfBars.DataValueField = "ItemValue";
                    ddlListOfBars.DataTextField = "ItemName";
                    ddlListOfBars.DataBind();
                }
                catch (Exception ex)
                {
                    SetMessage(divFooMsg, "Unable to retrieve Foos: " + 
                    ex.Message, true, true);
                }
    

    The ListView Code:

    <asp:ListView ID="lvFooList" runat="server">
       <LayoutTemplate>
          <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
       </LayoutTemplate>
          <ItemSeparatorTemplate>
             <hr />
          </ItemSeparatorTemplate>
       <ItemTemplate>
          <%#Eval("Name") %>
          <asp:DropDownList ID="ddlListOfBars" runat="server"></asp:DropDownList>
        </ItemTemplate>
       </asp:ListView>
    

    The Question(s):

    1. Is it possible to use a Dictionary in this way?
    2. Any pointers on where I'm going wrong? (Resources, hints, etc. would help)
    3. Is there a better way to do this?
    4. If this question is clear as mud, please comment so I can figure out how to improve it.
    • GEOCHET
      GEOCHET about 15 years
      +1 for the very well written question. I could barely find anything to hack and slash!
    • Manu
      Manu about 15 years
      Where does FooDictionary come from and why do you assign FooDictionary = fooDictionary; and lv.DataSource = FooDictionary;?
    • George Stocker
      George Stocker about 15 years
      Manu: There's a lot of voodoo going on in that foreach statement. Essentially it goes through and creates a dictionary from the Foo and the Bars associated with that foo. I just showed the end product for ease. Shouldn't the datasource be the FooDictionary so it can get the name?