Dictionary<T> of List<T> and ListViews in ASP.NET
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.
Related videos on Youtube
George Stocker
Updated on February 25, 2020Comments
-
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 asBar
), and I'm pulling them from the Data Access/Business Logic layer as Dictionary that holds aFoo
and its associatedBars
. I'd like to spit these items out in on the Webpage into aListView
that holds theFoo.Name
on the left, and theList<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):
- Is it possible to use a Dictionary in this way?
- Any pointers on where I'm going wrong? (Resources, hints, etc. would help)
- Is there a better way to do this?
- If this question is clear as mud, please comment so I can figure out how to improve it.
-
GEOCHET about 15 years+1 for the very well written question. I could barely find anything to hack and slash!
-
Manu about 15 yearsWhere does FooDictionary come from and why do you assign FooDictionary = fooDictionary; and lv.DataSource = FooDictionary;?
-
George Stocker about 15 yearsManu: 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?