在绑定数据时经常会用到这个句程序:<%# DataBinder.Eval(Container.DataItem,"xxxx")%>或者<%# DataBinder.Eval(Container,"DataItem.xxxx")%>
今天又学到一种,而且微软也说这种方法的效率要比以上两种高。
<%# ((DataRowView)Container.DataItem)["xxxx"]%>
很有用的,这样可以在前台页面做好多事情了。
还要记住要这样用必须要在前台页面导入名称空间System.Data,否则会生成错误信息。
<%@ Import namespace="System.Data" %>
这种用法其实和<%# ((DictionaryEntry)Container.DataItem).Key%>是一个道理。
关键是Container这个东西,它比较神秘。它的名称空间是System.ComponentModel。对于它我还需要进一步理解。
DataBinder.Eval(Container.DataItem,"Name")和Container.DataItem("Name")有什么区别?
DataBinder是System.Web里面的一个静态类,它提供了Eval方法用于简化数据绑定表达式的编写,但是它使用的方式是通过Reflection等开销比较大的方法来达到易用性,因此其性能并不是最好的。而Container则根本不是任何一个静态的对象或方法,它是ASP.NET页面编译器在数据绑定事件处理程序内部声明的局部变量,其类型是可以进行数据绑定的控件的数据容器类型(如在Repeater内部的数据绑定容器叫RepeaterItem),在这些容器类中基本都有DataItem属性,因此你可以写Container.DataItem,这个属性返回的是你正在被绑定的数据源中的那个数据项。如果你的数据源是DataTable,则这个数据项的类型实际是DataRowView。
相关文章:
Mastering ASP.Net DataBinding
Karl Seguin ? karlseguin@hotmail.comTable of Contents
- Introduction
- The Sample Program
- Understanding DataItem
- Formatting
- Nested Binding
- Handling Events
- Download
This article is available at Code Project. Check it out to make comments, discuss or rate the article
I'd like to thank Jean-Claude Manoli for developing his C# Code format, which i used in writing this tutorial.
Introduction
Questions regarding databinding, in one form or another, are probably the most asked in the aspnet newsgroups. Its clear everyone loves the idea of databinding but that more advanced functionality, such as event handling, conditional formatting and fine-tuning, aren't straightforward. The goal of this tutorial is shed light on some of the more common and frequently asked questions about the capabilities of databinding.The Sample Program
Throughout this tutorial we'll use two separate data sources. The first will be your every-day dataset, the other will be a strongly-typed custom collection containing strongly-typed objects.Our dataset will contain two tables, Customers and Orders:
Customer Structure | Order Structure | ||||||
Name | Type | Description | Name | Type | Description | ||
---|---|---|---|---|---|---|---|
CustomerId1 | Int32 | Unique customer identifier | OrderId | Int32 | Unique order identifier | ||
Name | String | Name of the customer | CustomerId1 | Int32 | Identifier of the custom who placed the order | ||
Zip | String | Customer's primary ZIP or Portal code | Ordered | DateTime | Date the order was placed on | ||
Enabled | Boolean | Whether the customer is currently active/enabled | Amount | Decimal | Dollar value of the order |
1A DataRelation exists between the Customer.CustomerId and Order.CustomerId columns.
Our business entities will consist of an Owner and a Pet class:
Owner Structure | Pets Structure | ||||||
Name | Type | Description | Name | Type | Description | ||
---|---|---|---|---|---|---|---|
OwnerId | Int32 | Unique owner identifier | PetId | Int32 | Unique pet identifier | ||
YearOfBirth | Int32 | The year the owner was born in | Name | String | Name of the pet | ||
FirstName | String | Owner's first name | IsNeutured | Boolean | Whether or not the pet is neutured | ||
LastName | String | Owner's last name | Type | PetType | Indicates the type of pet (Dog, Cat, Fish, Bird, Rodent, Other) | ||
Pets | PetCollection | Collection of pets the owner has |
Understanding DataItem
You've undoudbtedly made frequent use of the DataItem property, namely when using the DataBinding syntax to output a value:1: <%# DataBinder.Eval(Container.DataItem, "customerId") %>
1: <%@ Import namespace="System.Data" %> 2: <%@ Import namespace="BindingSample" %> 3: <asp:Repeater id="dataSetRepeater" Runat="server"> 4: <ItemTemplate> 5: <%# ((DataRowView)Container.DataItem)["customerId"] %> - 6: <%# ((DataRowView)Container.DataItem)["Name"] %> <br /> 7: </ItemTemplate> 8: <AlternatingItemTemplate> 9: <%# DataBinder.Eval(Container.DataItem, "customerId") %> - 10: <%# DataBinder.Eval(Container.DataItem, "Name") %> <br /> 11: </AlternatingItemTemplate> 12: </asp:Repeater> 13: 14: <br><br> 15: 16: <asp:Repeater id="collectionRepeater" Runat="server"> 17: <ItemTemplate> 18: <%# ((Owner)Container.DataItem).OwnerId %> - 19: <%# ((Owner)Container.DataItem).FirstName %> <br /> 20: </ItemTemplate> 21: <AlternatingItemTemplate> 22: <%# DataBinder.Eval(Container.DataItem, "OwnerId") %> - 23: <%# DataBinder.Eval(Container.DataItem, "FirstName") %> <br /> 24: </AlternatingItemTemplate> 25: </asp:Repeater>
In the second repeater we bind to a custom collection, again the ItemTemplate shows how to cast DataItem to the right type and access the fields directly [18,19] while the AlternateItemTemplate shows how the same is accomplished with DataBinder.Eval [22,23].
In both cases the ItemTemplate and AlternateItemTemplate will output the exact same information. The only difference is how the information is retrieved. DataBinder.Eval is far less performant, but has the benefit of being ignorant of the underlying structure, making it both quicker to develop and more likely to resist future changes. The goal here isn't to discuss the merits of these approaches, but simply show what DataItem truly is in order to build a proper foundation of understanding.
Formatting
Inline
While binding its possible to do simple formatting directly in the databinding expression or by calling functions which reside in codebehind.1: <asp:Repeater id="dataSetRepeater" Runat="server"> 2: <ItemTemplate> 3: <%# DataBinder.Eval(Container.DataItem, "OrderId")%> - 4: <%# FormatDate(DataBinder.Eval(Container.DataItem, "Ordered"))%> - 5: <%# FormatMoney(DataBinder.Eval(Container.DataItem, "Amount"))%> <br /> 6: </ItemTemplate> 7: </asp:Repeater> 8: 9: <br ><br > 10: 11: <asp:Repeater id="collectionRepeater" Runat="server"> 12: <ItemTemplate> 13: <%# DataBinder.Eval(Container.DataItem, "OwnerId") %> - 14: <asp:literal ID="see" Runat="server" 15: Visible='<%# (int)DataBinder.Eval(Container.DataItem, "Pets.Count") > 0 %>'> 16: see pets 17: </asp:Literal> 18: <asp:literal ID="nopets" Runat="server" 19: Visible='<%# (int)DataBinder.Eval(Container.DataItem, "Pets.Count") == 0 %>'> 20: no pets 21: </asp:Literal> 22: <br /> 23: </ItemTemplate> 24: </asp:Repeater>
1: protected string FormatDate(object date) { 2: if (date == DBNull.Value){ 3: return "n/a"; 4: } 5: try{ 6: return ((DateTime)date).ToShortDateString(); 7: }catch{ 8: return "n/a"; 9: } 10: } 11: protected string FormatMoney(object amount) { 12: if (amount == DBNull.Value){ 13: return String.Format("{0:C}", 0); 14: } 15: return String.Format("{0:C}", amount); 16: }
OnItemDataBound
While the above method is suitable for quick and simple problems, it lacks in elegance and capacity. Indeed, the 2nd example shows a serious lack of grace and dangerously blends presentation logic with UI. Avoiding burdening your presentation layer with any code is a practice worth eternal vigilence. To help accomplish this, the repeater, datalist and datagrid all expose a very powerful and useful event: OnItemDataBound.OnItemDataBound fired for each row being bound to your datasource (in addition to when other templates are bound (header, footer, pager, ..)), it not only exposes the DataItem being used in binding, but also the complete template. OnItemDataBound starts to fire as soon as the DataBind() method is called on the repeater/datalist/datagrid.
Using OnItemDataBound lets us exercise fine control over exactly what happens during binding in a clean and robust framework. For example, reworking the 2nd repeater from above, we get:
1: <asp:Repeater OnItemDataBound="itemDataBoundRepeater_ItemDataBound" id="itemDataBoundRepeater" Runat="server"> 2: <ItemTemplate> 3: <%# DataBinder.Eval(Container.DataItem, "OwnerId") %> - 4: <asp:Literal ID="see" Runat="server" /> <br /> 5: </ItemTemplate> 6: </asp:Repeater>
1: protected void itemDataBoundRepeater_ItemDataBound(object source, RepeaterItemEventArgs e) { 2: if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item){ 3: Literal lit = (Literal)e.Item.FindControl("see"); 4: if (lit != null){ 5: Owner owner = (Owner)e.Item.DataItem; 6: if (owner.Pets.Count == 0){ 7: lit.Text = "no pets"; 8: }else{ 9: lit.Text = "see pets"; 10: } 11: } 12: } 13: }
An alternative to using e.Item.FindControl() is to refer to the controls by position via e.Item.Controls[INDEX]. While this may be considerably faster, it really makes the UI inflexible to basic changes (else you face constantly changing the code). Additionally, white spaces and newlines are actually controls. So in the above code, you'd get:
1: e.Item.Controls[0] //"/r/n 1 - /r/n " 2: e.Item.Controls[1] //is the actual "see" literal
When it comes to OnItemDataBound, the sky is the limit. Here we've only shown a basic example of what can be done and though we will see other, more complex examples, we won't cover every possibility.
OnItemCreated
Another useful event exposed by these controls is OnItemCreated. The key difference between the two is that OnItemDataBound only fires when the control is bound - that is when you are posting back and the control is recreated from the viewstate, OnItemDataBound doesn't fire. OnItemCreated on the other hand fires when a control is bound AS WELL AS when the control is recreated from the viewstate. The following example shows this subtle difference:1: <asp:Repeater OnItemCreated="repeater_ItemCreated" OnItemDataBound="repeater_ItemDataBound" id="repeater" Runat="server"> 2: <ItemTemplate> 3: <asp:Literal EnableViewState="False" ID="event" Runat="server" /> <br /> 4: </ItemTemplate> 5: </asp:Repeater> 6: 7: <asp:Button ID="btn" Runat="server" Text="Click Me!" />
1: private void Page_Load(object sender, EventArgs e) { 2: if (!Page.IsPostBack){ 3: repeater.DataSource = CustomerUtility.GetAllOrders(); 4: repeater.DataBind(); 5: } 6: } 7: protected void repeater_ItemDataBound(object source, RepeaterItemEventArgs e) { 8: if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item){ 9: Literal lit = (Literal)e.Item.FindControl("event"); 10: if (lit != null){ 11: lit.Text += " - ItemDataBound"; 12: } 13: } 14: } 15: protected void repeater_ItemCreated(object source, RepeaterItemEventArgs e) { 16: if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item){ 17: Literal lit = (Literal)e.Item.FindControl("event"); 18: if (lit != null){ 19: lit.Text += "ItemCreated"; 20: } 21: } 22: }
The really important thing to keep in mind is that when ItemCreated fires because of databinding, e.Item.DataItem will what you expect - a reference to the individual row being bound. However, when ItemCreated is fired from being re-created from the viewstate, e.Item.DataItem will be NULL. If you think about it this makes sense, the entire datasource isn't stored in the viewstate, only the individual controls and their values, as such its impossible to have access to the individual rows of data originally used when binding. Of course, this can lead to very buggy code. For example, if we took our previous ItemDataBound example and moved it to the ItemCreated event:
1: protected void itemCreatedRepeater_ItemCreatedobject source, RepeaterItemEventArgs e) { 2: if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item){ 3: Literal lit = (Literal)e.Item.FindControl("see"); 4: if (lit != null){ 5: Owner owner = (Owner)e.Item.DataItem; 6: if (owner.Pets.Count == 0){ 7: lit.Text = "no pets"; 8: }else{ 9: lit.Text = "see pets"; 10: } 11: } 12: } 13: }
Nested Binding
Another common requirement is to nest controls within each other. Both of our sample data has a 1 to many relationship and are therefore ideal candidates. Our Customers dataset has a DataRelation set up between the Customer's customerId and the order's customerId:1: ds.Relations.Add(new DataRelation("CustomerOrders", ds.Tables[0].Columns["CustomerId"], ds.Tables[1].Columns["CustomerId"]));
The two ways that we'll look at nesting repeaters is via inline binding and using OnItemDataBound.
Inline
1: <asp:Repeater id="dataSetCasting" Runat="server"> 2: <HeaderTemplate> 3: <ul> 4: </HeaderTemplate> 5: <ItemTemplate> 6: <li><%# ((DataRowView)Container.DataItem)["Name"]%> 7: <ul> 8: <asp:Repeater ID="orders" DataSource='<%# ((DataRowView)Container.DataItem).CreateChildView("CustomerOrders")%>' Runat="server"> 9: <ItemTemplate> 10: <li><%# ((DataRowView)Container.DataItem)["Amount"]%></li> 11: </ItemTemplate> 12: </asp:Repeater> 13: </ul> 14: </li> 15: </ItemTemplate> 16: <FooterTemplate> 17: </ul> 18: </FooterTemplate> 19: </asp:Repeater>
1: <asp:Repeater ID="orders" DataSource='<%# DataBinder.Eval(Container.DataItem, "CutomerOrders")%>' Runat="server">
Nesting with custom collections is even easier. Since owners have a property called Pets which is a custom collection of all the pets they own, we can simply:
1: <asp:Repeater id="collectionCasting" Runat="server"> 2: <HeaderTemplate> 3: <ul> 4: </HeaderTemplate> 5: <ItemTemplate> 6: <li><%# ((Owner)Container.DataItem).FirstName%> 7: <ul> 8: <asp:Repeater ID="pets" DataSource="<%# ((Owner)Container.DataItem).Pets%>" Runat="server"> 9: <ItemTemplate> 10: <li><%# ((Pet)Container.DataItem).Name%></li> 11: </ItemTemplate> 12: </asp:Repeater> 13: </ul> 14: </li> 15: </ItemTemplate> 16: <FooterTemplate> 17: </ul> 18: </FooterTemplate> 19: </asp:Repeater>
1: <asp:Repeater ID="pets" DataSource='<%# DataBinder.Eval(Container.DataItem, "Pets")%>' Runat="server">
OnItemDataBound
If something is doable using inline ASPX, it's doable via onItemDataBound. Deciding which method to use often depends on which you feel is cleaner and more flexible. We'll only look at one example, since it's basically the same as the above code, except the binding logic is moved to codebehind:1: <asp:Repeater OnItemDataBound="dataSetCasting_ItemDataBound" id="dataSetCasting" Runat="server"> 2: <HeaderTemplate> 3: <ul> 4: </HeaderTemplate> 5: <ItemTemplate> 6: <li><%# ((DataRowView)Container.DataItem)["Name"]%> 7: <ul> 8: <asp:Repeater ID="orders" Runat="server"> 9: <ItemTemplate> 10: <li><%# ((DataRowView)Container.DataItem)["Amount"]%></li> 11: </ItemTemplate> 12: </asp:Repeater> 13: </ul> 14: </li> 15: </ItemTemplate> 16: <FooterTemplate> 17: </ul> 18: </FooterTemplate> 19: </asp:Repeater>
1: protected void dataSetCasting_ItemDataBound(object s, RepeaterItemEventArgs e) { 2: if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem){ 3: Repeater rpt = (Repeater)e.Item.FindControl("orders"); 4: if (rpt != null){ 5: rpt.DataSource = ((DataRowView)e.Item.DataItem).CreateChildView("CustomerOrders"); 6: rpt.DataBind(); 7: } 8: } 9: }
Handling Events
The last thing to discuss is how to handle events raised by controls inside your repeater/datalist/datagrid. Events raised from controls inside your repeater bubble up to the repeater and are exposed via the OnItemCommand event. LinkButtons and Buttons have a CommandArgument and CommandName property which lets the OnItemCommand handler figure out which button was clicked, for example:1: <asp:Repeater OnItemCommand="eventRepeater_ItemCommand" id="eventRepeater" Runat="server"> 2: <ItemTemplate> 3: <%# DataBinder.Eval(Container.DataItem, "Name")%> 4: <asp:LinkButton ID="delete" 5: Runat="server" 6: CommandName="Delete" 7: CommandArgument='<%# DataBinder.Eval(Container.DataItem, "CustomerId") %>'> 8: Delete Customer 9: </asp:LinkButton> 10: - 11: <asp:LinkButton ID="addOrder" 12: Runat="server" 13: CommandName="Add" 14: CommandArgument='<%# DataBinder.Eval(Container.DataItem, "CustomerId") %>'> 15: Add Order 16: </asp:LinkButton> 17: <br /> 18: </ItemTemplate> 19: </asp:Repeater>
1: protected void eventRepeater_ItemCommand(object s, RepeaterCommandEventArgs e) { 2: int customerId = Convert.ToInt32(e.CommandArgument); 3: switch (e.CommandName.ToUpper()){ 4: case "DELETE": 5: CustomerUtility.DeleteCustomer(customerId); 6: BindEventRepeater(false); 7: break; 8: case "Add": 9: //doesn't actually do antyhing right now. 10: break; 11: } 12: }