Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.
like image 747
George Stocker Avatar asked Feb 24 '09 21:02

George Stocker


2 Answers

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();   
}
like image 119
stevemegson Avatar answered Nov 04 '22 16:11

stevemegson


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.

like image 4
Andrew Barrett Avatar answered Nov 04 '22 15:11

Andrew Barrett