分享
 
 
 

Mastering ASP.Net DataBinding

王朝asp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

source:

http://openmymind.net/databinding/index.html

begin:

Mastering ASP.Net DataBindingKarl Seguin ?karlseguin@hotmail.com

Table of Contents

Introduction

The Sample Program

Understanding DataItem

Formatting

Inline

OnItemDataBound

OnItemCreated

Nested Binding

Inline

OnItemDataBound

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.

IntroductionQuestions 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 ProgramThroughout 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 DataItemYou'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") %>

It's important to understand that DataItem is actually an object, and that when you use the DataBinder.Eval function, it basically needs to figure out what type of object it is and how to get "customerId" from it. That's because your DataSource can be different things, such as a dataset or dataview, an arraylist or hashtable, a custom collection and more. Binding happens on a row-by-row basis and DataItem actually represents the current row being bound. For a DataSet, DataTable or DataView DataItem is actually an instance of DataRowView (you might think that the DataItem for a DataSet or DataTable would be an instance of DataRow, but when you bind either of these, the DefaultView is actually used, therefore DataItem will always be a DataRowView). When you are binding to a collection, DataItem is an instance of the item within the collection. We can observe this more clearly with the following code:

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 first repeater we are binding to a DataSet, the ItemTemplate shows how to access values by casting DataItem to a DataRowView [5,6], the AlternateItemTemplate will output the same information but through DataBinder.Eval [9,10].

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

InlineWhile 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>

The second repeater makes use of directly embedded expressions to toggle the visibility of certain controls [15,19]. The first repeater, which is bound to all Orders, makes use of two functions: FormatDate [4] and FormatMoney [5]. These methods could look something like:

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: }

OnItemDataBoundWhile 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>

Notice that our previously code-cluttered ItemTemplate is now considerably cleaner - this is because we've pushed the logic to the itemDataBoundRepeater_ItemDataBound function in codebehind:

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: }

Since we are dealing with repeaters, e.Item returns a reference to the current RepeaterItem. If this was a datalist, it would return a reference to a DataListItem, or a DataGridItem if it were a datagrid. For the most part however, all three provide the same capabilities. The first thing to do is check the ItemType and make sure we are currently dealing with an AlternateItem or an Item [2]. Next get a reference to our literal [3], this is an extremely powerful capability which allows us to really keep our UI clean. As we saw in a previous section, we can cast DataItem directly to the individual item being bound (in this case Owner, but again, if we bound to a dataset, it would be a DataRowView) [5]. Finally all the pieces are in place to apply our presentation logic [6-10].

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

Which is both an unexpected behaviour and one very hard to cleanly deal with.

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.

OnItemCreatedAnother 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!" />

Here we have a repeater with both the OnItemCreated and OnItemDataBound events hooked [1]. Additionaly we have a single literal who's viewstate is disabled (if it was enabled we couldn't see the difference) [3]. And we have a button that'll do nothing but postback [7]. Our codebehind looks like:

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: }

When the page is first loaded, Page.IsPostBack returns false [2] and our repeater is bound to all orders [3,4]. Calling DataBind() causes the ItemCreated event to fire for the first row, followed by the ItemDataBound event - in our example each will fire, one after the other, 11 times (since there are 11 orders). As we can see, ItemCreated and ItemDataBound merely take the literal and append the text "ItemCreated" and "ItemDataBound" respectively. The difference happens when our button is clicked. This causes Page_Load to fire, but this time Page.IsPostBack evaluates to true, thus skipping the binding [3,4]. Only when the page enters its Begin PreRender stage will the ItemCreated event fire (again once for each row), but this time it won't be followed by the ItemDataBound.

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: }

When the page first loads, the above code will work fine. But if the page is postedback, e.Item.DataItem will be null, resulting in a runtime null reference error.

Nested BindingAnother 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"]));

And our Owner's have a Pets property which is a collection of all the pets they own.

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>

The important part being when we set the DataSource of our inner repeater [8]. The CreateChildView function our DataRowView is used, in conjuction with the name of our DataRelationship to return a DataView of all child records. Alternatively, using the DataBinder.Eval, we could simply use:

1: <asp:Repeater ID="orders" DataSource='<%# DataBinder.Eval(Container.DataItem, "CutomerOrders")%>' Runat="server">

Again, we use the CustomerOrders datarelation which we created, but let the DataBinder.Eval handle everything else.

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>

Or using DataBinder.Eval:

1: <asp:Repeater ID="pets" DataSource='<%# DataBinder.Eval(Container.DataItem, "Pets")%>' Runat="server">

OnItemDataBoundIf 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>

Notice that our inner repeater doesn't have a DataSource property [8], however our outer repeater does specify an OnItemDataBound function [1], let's look at it:

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: }

Basically the same thing is happening as we saw before, except this is happening out of the UI.

Handling EventsThe 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")%> &nbsp;&nbsp;&nbsp;

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: &nbsp;&nbsp;&nbsp; - &nbsp;&nbsp;&nbsp;

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>

In the above code, two linkbuttons can raise events, either deleting the customer [4-9] or adding an order [11-16]. Also note that the ItemCommand is hooked up [1]:

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: }

Depending on what the commandName is [3] we know different actions were requested. Its important to note that if you change the underlaying datasource (like deleting a row) and want that to be visible to the user, you need to rebind your repeater/datalist/datagrid. Also note that if you are caching your data, like I am here, you'll need to invalidate the cache so that the new data source (with the delete/added/updated rows) is used.

DownloadThis sample web application simply contains a number of pages which do various things with repeaters. It should provide a playground for trying different things and simply messing around with databinding:

C# Application

VB.Net Application

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有