Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 77 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
77
Dung lượng
2,21 MB
Nội dung
CHAPTER 7 ■ SERVER CONTROL DATA BINDING 283 • System.Web.UI.WebControls.DataBoundControl: This can serve as a base class when displaying data in list or tabular form. The designer DataBoundControlDesigner class is configured on this base class via the Designer attribute. • System.Web.UI.WebControls.CompositeDataBoundControl: This class inherits from DataBoundControl and can serve as the base class for tabular data bound controls that are composed of other server controls. • System.Web.UI.WebControls.HierarchicalDataBoundControl: This one can serve as a base class to create data bound controls that work with classes that implement the IHierarchicalDataSource interface and classes that derive from the HierarchicalDataSourceControl and HierarchicalDataSourceView classes. There certainly may be scenarios where complete control is required and the preceding base classes are limiting in some way, in which case a control developer can always simply inherit from Control or WebControl. Otherwise, we recommend that developers consider these base classes as a first option, since inheriting from them can save time. In the next section, we take a look at a sample control that inherits from the DataBoundControl base class. The Repeater Control The case study we present to help explain data binding creates a replica of the Repeater control built into ASP.NET. The Repeater control is a data-bound server control that takes advantage of templates to generate the display for the data source. It is a complex control that requires a fair amount of source code, but this effort is worth the ease of use data binding provides to the user of a data bindable server control. The Repeater control includes five templates: HeaderTemplate, FooterTemplate, SeparatorTemplate, ItemTemplate, and AlternatingItemTemplate. We provided the first three templates types in our TemplateMenu control. For clarity, those three templates do not take advantage of data binding. We are adding data binding capabilities to the ItemTemplate and AlternatingItemTemplate templates. The ItemTemplate and AlternatingItemTemplate templates are applied to each row of data retrieved from the data source based on an alternating pattern. The SeparatorTemplate template is placed between the item templates to keep things looking nice. The diagram in Figure 7-2 shows how the templates determine the output of the control rendering process. Our Repeater control implements a fairly sophisticated system of events that provide rich functionality: ItemCommand, ItemCreated, and ItemDataBound. ItemCommand is an event raised by our Repeater control that aggregates bubbled command events raised by subordinate command controls such as an ASP.NET Button control. We discuss these events in detail in the section titled “Repeater Control Event Management” later in this chapter. The ItemCreated event is raised each time a RepeaterItem control is created. This gives the client of the event an opportunity to modify or change the final control output in the template dynamically. ItemDataBound gives the same opportunity, except it is raised after any data binding has been performed on a template. This event is limited to the ItemTemplate and AlternatingItemTemplate templates, because the header and footer templates do not support data binding. Cameron_865-2C07.fm Page 283 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 284 CHAPTER 7 ■ SERVER CONTROL DATA BINDING Figure 7-2. The Repeater control and its templates The RepeaterItem Container Control RepeaterItem is a building block used by the Repeater control to create its content. It is based on the System.Web.UI.Control base class and serves as the primary container for instantiating templates and working with events. The following code snippet shows how the RepeaterItem control is declared, inheriting from Control and implementing the INamingContainer interface to prevent name collisions on child controls: public class RepeaterItem : Control, INamingContainer { public RepeaterItem(int itemIndex, ListItemType itemType, object dataItem) { this.itemIndex = itemIndex; this.itemType = itemType; this.dataItem = dataItem; } } The private data members are instantiated by the constructor. These fields are exposed as public properties as well: private object dataItem; public object DataItem { I]ne]=j`ano =j]Pnqfehhk =jpkjekIknajk Pdki]oD]n`u ?dneopej]>anchqj` D]jj]Ikko Bn`nemqa?epa]qt I]npejOkiian H]qnaj_aHa^ed]j Ahev]^apdHej_khj Re_pkne]=odsknpd O]haoNalnaoajp]pera Ksjan Ksjan O]haoNalnaoajp]pera Kn`an=`iejeopn]pkn O]haoNalnaoajp]pera I]ngapejcI]j]can Ksjan Ksjan =__kqjpejcI]j]can O]haoNalnaoajp]pera =hbna`oBqppangeopa =j]PnqfehhkAil]na`]`koudah]`ko =jpkjekIknajkP]mqane] =nkqj`pdaDknj >anchqj`ooj]^^gl >h]qanOaa@aheg]paooaj >hkj`ao``ohlnaapbeho >he`k?kie`]olnal]n]`]o >kj]ll# >kppki)@khh]nI]ngapo >#o>aran]cao Da]`anPailh]pa EpaiPailh]pa =hpanj]pejcEpaiPailh]pa BkkpanPailh]pa J]ia Nala]pan?kjpnkh Pepha ?kil]ju Cameron_865-2C07.fm Page 284 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ SERVER CONTROL DATA BINDING 285 get { return dataItem; } set { dataItem = value; } } private int itemIndex; public int ItemIndex { get { return itemIndex; } } private ListItemType itemType; public ListItemType ItemType { get { return itemType; } } ItemIndex exposes the relative position of the RepeaterItem control with respect to its siblings underneath the parent Repeater control. ItemType borrows the ListItemType enumeration from the System.Web.UI.WebControl namespace to identify the purpose of the RepeaterItem control. The following code shows a reproduction of the enumeration definition in the System. Web.UI.WebControls namespace: enum ListItemType { Header, Footer, Item, AlternatingItem, SelectedItem, EditItem, Separator, Pager } The last property exposed by the RepeaterItem class is DataItem. For RepeaterItem child controls that are bound to a data source (i.e., ItemTemplate or AlternatingItemTemplate Cameron_865-2C07.fm Page 285 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 286 CHAPTER 7 ■ SERVER CONTROL DATA BINDING RepeaterItems), DataItem will reference a particular row in the collection that makes up the data source. This permits us to use the Container.DataItem syntax in a data-binding expression: <ItemTemplate> <% Container.DataItem[Name] %> </ItemTemplate> Command Events and the RepeaterItem Control The RepeaterItem control plays a key role in ensuring that Command events are bubbled up to the parent Repeater control so that it can raise an ItemCommand event to the outside world. The following code takes Command events that are bubbled and wraps the events in a custom RepeaterCommandEventArgs object to provide additional information on the event’s source: protected override bool OnBubbleEvent(object source, EventArgs e) { CommandEventArgs ce = e as CommandEventArgs; if (ce != null) { RepeaterCommandEventArgs rce = new RepeaterCommandEventArgs(this, source, ce); RaiseBubbleEvent(this, rce); return true; } else return false; } The OnBubbleEvent member function performs a typecast to validate that it is indeed a Command event, instantiates a RepeaterCommandEventArgs class, and then sends it on up to the Repeater control through the RaiseBubbleEvent method. The return value of true indicates to ASP.NET that the event was handled. Later on, we show the code in Repeater that handles the bubbled event and raises its own Command event. We create a custom EventArgs class to make working with the Repeater control easier, as shown in Listing 7-1. Instead of having to search through all the controls that are in the Repeater’s Control collection, we can narrow it down to just the RepeaterItem control of interest. Listing 7-1. The RepeaterCommand Event Class File using System; using System.Web.UI.WebControls; namespace ControlsBook2Lib.Ch07 { public delegate void RepeaterCommandEventHandler(object o, RepeaterCommandEventArgs rce); Cameron_865-2C07.fm Page 286 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ SERVER CONTROL DATA BINDING 287 public class RepeaterCommandEventArgs : CommandEventArgs { public RepeaterCommandEventArgs(RepeaterItem item, object commandSource, CommandEventArgs originalArgs) : base(originalArgs) { this.item = item; this.commandSource = commandSource; } private RepeaterItem item; public RepeaterItem Item { get { return item; } } private object commandSource; public object CommandSource { get { return commandSource; } } } } The source of the event is available in the RepeaterCommandEventArgs class via the CommandSource property. The RepeaterItem container control that houses the CommandSource property is reachable through the Item property. It allows us to identify and programmatically manipulate the exact block of content that was the source of the event. Our code for this control also defines a delegate named RepeaterCommandEventHandler to work with the custom EventArgs class. Listing 7-2 shows the full listing for the RepeaterItem control. Listing 7-2. The RepeaterItem Control Class File using System; using System.ComponentModel; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ControlsBook2Lib.Ch07 { Cameron_865-2C07.fm Page 287 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 288 CHAPTER 7 ■ SERVER CONTROL DATA BINDING public class RepeaterItem : Control, INamingContainer { [ToolboxItem(false)] public RepeaterItem(int itemIndex, ListItemType itemType, object dataItem) { this.itemIndex = itemIndex; this.itemType = itemType; this.DataItem = dataItem; } public object DataItem { get; set; } private int itemIndex; public int ItemIndex { get { return itemIndex; } } private ListItemType itemType; public ListItemType ItemType { get { return itemType; } } protected override bool OnBubbleEvent(object source, EventArgs e) { CommandEventArgs ce = e as CommandEventArgs; if (ce != null) { RepeaterCommandEventArgs rce = new RepeaterCommandEventArgs(this, source, ce); RaiseBubbleEvent(this, rce); return true; } else return false; } } Cameron_865-2C07.fm Page 288 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ SERVER CONTROL DATA BINDING 289 public delegate void RepeaterItemEventHandler(object o, RepeaterItemEventArgs rie); public class RepeaterItemEventArgs : EventArgs { public RepeaterItemEventArgs(RepeaterItem item) { this.item = item; } private RepeaterItem item; public RepeaterItem Item { get { return item; } } } } In the next section, we discuss the implementation details of our version of the Repeater server control. The Repeater Control Architecture Now that we have the main building block of our Repeater control ready for action, we can move on to the core logic of our control. As shown in the following code, Repeater inherits from System.Web.UI.WebControls.DataBoundControl and implements INamingContainer to prevent control ID conflicts like its RepeaterItem sibling. The ParseChildren attribute set to true on the Repeater class enables the use of template properties. PersistChildren is set to false to prevent child controls from being persisted as nested inner controls; they are instead persisted as nested elements. The Designer attribute associates a custom designer named RepeaterDesigner that provides template editing design-time support. We discuss RepeaterDesigner further in Chapter 11. [ToolboxData("<{0}:repeater runat=server></{0}:repeater>"), ParseChildren(true), PersistChildren(false), Designer(typeof(ControlsBook2Lib.Ch11.Design.RepeaterDesigner))] public class Repeater : DataBoundControl, INamingContainer { The heart of the architecture behind Repeater is two methods: CreateChildControls and PerformDataBinding. Both of these member functions create the control hierarchy for Repeater, but each does so as a result of two fundamentally different scenarios. First, here is the code for the DataBind method: Cameron_865-2C07.fm Page 289 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 290 CHAPTER 7 ■ SERVER CONTROL DATA BINDING public override void DataBind() { this.PerformSelect(); } Starting in ASP.NET 2.0, the PerformSelect method performs the work to load the data as listed here: protected override void PerformSelect() { if (!IsBoundUsingDataSourceID) { OnDataBinding(EventArgs.Empty); } GetData().Select(CreateDataSourceSelectArguments(), OnDataSourceViewSelectCallback); RequiresDataBinding = false; MarkAsDataBound(); OnDataBound(EventArgs.Empty); } Depending on whether the control is bound using an IDataSource control introduced in ASP.NET 2.0 or any other DataSource control determines how PerformSelect executes. The OnDataBinding call must occur before the GetData call if not bound with an IDataSource-based control, which is where the check on IsBoundUsingDataSourceID is necessary at the beginning of the method. The GetData method retrieves the DataSourceView object from the IDataSource associated with the data-bound control so OnDataBinding is called prior to GetData. Finally, the DataBound event is raised. The method GetData is called within PerformSelect and takes a callback method as a param- eter. The callback method is OnDataSourceViewSelectCallback, which calls PerformDataBinding to build out the control via the CreateControlHierarchy method. Once again, whether the control is bound to an IDataSource-based control or not determines how the control tree is built by passing in different parameters to CreateControlHierarchy. As you would guess, DataBind takes precedence as a control-loading mechanism when binding to a data source. It is called on the web form after the data source has been linked to the control. The first task of DataBind is to fire the data-binding event OnDataBinding. If the Repeater control is binding to a design-time data source, firing this event in DataBind is required for the control to see the selected design-time data source at runtime. Next, DataBind starts with a clean slate, clearing the current set of controls and any ViewState values that are lingering, after which the control is ready to track ViewState. As shown in the preceding code, once the table has been set, DataBind builds the child control hierarchy based on the data source through the CreateControlHierarchy method. It then sets the ChildControlsCreated property to true to let ASP.NET know that the control is populated. This prevents the framework from calling CreateChildControls after DataBind. Cameron_865-2C07.fm Page 290 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 7 ■ SERVER CONTROL DATA BINDING 291 We next discuss how CreateChildControls handles control creation. Here is the code for CreateChildControls: override protected void CreateChildControls() { Controls.Clear(); if (ViewState["ItemCount"] != null) { CreateControlHierarchy(false); } ClearChildViewState(); } You have already encountered CreateChildControls in all the composite controls samples so far in the book. It is called whenever the control needs to render itself outside of a DataBind. The code implementation uses the CreateControlHierarchy helper method to do the dirty work as in the DataBind method. The single difference is that the code in CreateChildControls checks the ViewState ItemCount property. If ItemCount is not null, this indicates that we need to re-create the control hierarchy using postback control ViewState values. Figure 7-3 illustrates the difference between DataBind and CreateChildControls. Figure 7-3. DataBind versus CreateChildControls We pass a Boolean value to CreateControlHierarchy to indicate whether it needs to use the data source to build up the control hierarchy or whether it should try to rebuild the control hierarchy from ViewState at the beginning of a postback cycle. For CreateChildControls, we pass in false to CreateChildHierarchy if ItemCount is present in ViewState. The data binding process is controlled by three properties: DataSourceID, DataMember, and DataSource. Notice that none of these properties are declared directly in our custom Repeater control. Our Repeater control inherits from DataBoundControl, where much of the data binding functionality is handled by the base class itself. The DataSourceID is set as the DataSource when using an IDataSource-based control first introduced in ASP.NET 2.0, such as the SqlDataSource class. DataSourceID appears in the Properties window, but DataSource does not, though DataSource is still a public property that can be set in code. Sa^>nksoan Sa^Oanran Nala]pan @]p])>ej`sepd @]p]Okqn_a ?na]pa?deh`?kjpnkho]j` NapnearaEjbkni]pekj bnkiReasOp]pa Ejepe]hNamqaop ?kjpnkhDPIH+ReasOp]pa ?kjpnkhDPIH+ReasOp]pa Lkop^]_g+ReasOp]pa Cameron_865-2C07.fm Page 291 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 292 CHAPTER 7 ■ SERVER CONTROL DATA BINDING In the next section, we dissect CreateControlHierarchy by breaking the code into bite- sized chunks as part of the discussion. The CreateControlHierarchy Method CreateControlHierarchy contains the most complicated logic in the Repeater control. It has logic that covers creating the header and footer section of the control, along with the data-bound item content. The first part of CreateControlHierarchy creates the header section of the control: private void CreateControlHierarchy(bool useDataSource) { items = new ArrayList(); IEnumerable ds = null; if (HeaderTemplate != null) { RepeaterItem header = CreateItem(-1, ListItemType.Header, false, null); } The preceding code checks for the presence of a HeaderTemplate template, and if it exists, it creates a header RepeaterItem via CreateItem. CreateItem is the code that handles the actual RepeaterItem creation and adds it to the Repeater’s Controls collection. The items field is an ArrayList containing the RepeaterItem collection for the RepeaterControl. It is declared as a private field under the Repeater class: private ArrayList items = null; You can think of this as a secondary collection of child controls like the Controls collection but one that is filtered to include just the RepeaterItem containers that represent data from the data source. After the header is created, CreateControlHierarchy creates the core data-oriented RepeaterItem child controls. The first step in the process is resolving the DataSource. If CreateControlHierarchy is called from the PerformDataBinding method, the useDataSource Boolean parameter will be set to true and the usingIDataSource parameter will be false or true depending on whether the control is bound to an IDataSource-based control. Otherwise, if CreateControlHierarchy is called from CreateChildControls, useDataSource and usingIDataSource will be set to false: private void CreateControlHierarchy(bool useData, bool usingIDataSource, IEnumerable data) We now move on to discuss how the Repeater control resolves its data source and builds up its control hierarchy as it data binds. The DataSourceHelper Class and Data Binding When building the control hierarchy as a result of data binding, we use a helper class named DataSourceHelper to resolve the DataSource to something that supports the IEnumerable inter- face. You can use this code directly to perform the same task in your data-bound custom server controls. Cameron_865-2C07.fm Page 292 Monday, February 18, 2008 4:07 PM Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... RepeaterCommandEventArgs rce = e as RepeaterCommandEventArgs; if (rce != null) { OnItemCommand(rce); return true; } else return false; } protected virtual void OnItemCommand(RepeaterCommandEventArgs rce) { RepeaterCommandEventHandler repeaterCommandEventDelegate = (RepeaterCommandEventHandler)Events[ItemCommandKey]; 311 Simpo PDF MergePage 312 Split Unregistered PM Cameron_8 65- 2C07.fm and Monday, February... override ControlCollection Controls { get { EnsureChildControls(); return base .Controls; } } private static readonly object ItemCommandKey = new object(); public event RepeaterCommandEventHandler ItemCommand { add { Events.AddHandler(ItemCommandKey, value); } remove { Events.RemoveHandler(ItemCommandKey, value); } } Simpo PDF MergePage 311 Split Unregistered PM Cameron_8 65- 2C07.fm and Monday, February 18,... Events.AddHandler(ItemCommandKey, value); } remove { Events.RemoveHandler(ItemCommandKey, value); } } The On-prefixed protected methods use standard event techniques to notify the delegates that subscribe to the event when it is fired The following OnItemCommand is mirrored by OnItemDataBound and OnItemCreated: protected virtual void OnItemCommand(RepeaterCommandEventArgs rce) { RepeaterCommandEventHandler... ItemCommand event, an ItemCreated event, and an ItemDataBound event We use the Events collection provided by System.Web.UI.Control to track registered client delegates The following code for the ItemCommand event is reproduced in a similar manner for the ItemCreated and ItemDataBound events: private static readonly object ItemCommandKey = new object(); public event RepeaterCommandEventHandler ItemCommand... repeaterCommandEventDelegate = (RepeaterCommandEventHandler) Events[ItemCommandKey]; Simpo PDF MergePage 303 Split Unregistered PM Cameron_8 65- 2C07.fm and Monday, February 18, 2008 4:07 Version - http://www.simpopdf.com C HAPTE R 7 ■ SERVE R CONTROL DA TA BINDIN G if (repeaterCommandEventDelegate != null) { repeaterCommandEventDelegate(this, rce); } } ItemCommand requires an extra step to handle the... RepeaterCommand events bubbled up from child RepeaterItem controls To wire into the event bubbling, it implements OnBubbleEvent: protected override bool OnBubbleEvent(object source, EventArgs e) { RepeaterCommandEventArgs rce = e as RepeaterCommandEventArgs; if (rce != null) { OnItemCommand(rce); return true; } else return false; } OnBubble traps the RepeaterCommand events and raises them as ItemCommand events... null) || (dataMember.Length < 1)) { propDesc = propDescCol[0]; } Simpo PDF MergePage 2 95 Split Unregistered PM Cameron_8 65- 2C07.fm and Monday, February 18, 2008 4:07 Version - http://www.simpopdf.com C HAPTE R 7 ■ SERVE R CONTROL DA TA BINDIN G else //If dataMember is set, try to find it in the property collection propDesc = propDescCol.Find(dataMember, true); if (propDesc == null) throw new Exception("ListSource... { ITypedList typedList = (ITypedList)list; PropertyDescriptorCollection propDescCol = typedList.GetItemProperties(new PropertyDescriptor[0]); if (propDescCol.Count == 0) throw new Exception("ListSource without DataMembers"); PropertyDescriptor propDesc = null; //Check to see if dataMember has a value, if not, default to first //property (DataTable) in the property collection (DataTableCollection) if... namespace ControlsBook2Web.Ch07 { Simpo PDF MergePage 317 Split Unregistered PM Cameron_8 65- 2C07.fm and Monday, February 18, 2008 4:07 Version - http://www.simpopdf.com C HAPTE R 7 ■ SERVE R CONTROL DA TA BINDIN G public partial class DataBoundRepeater : System.Web.UI.Page { protected System.Data.SqlClient.SqlDataAdapter dataAdapterEmp; protected System.Data.SqlClient.SqlCommand sqlSelectCommand1; protected... a DataTable We cast the result to IEnumerable and return it to the control so that we can continue the data binding process PropertyDescriptor, PropertyDescriptorCollection, and IListSource are all members of the System.ComponentModel namespace This namespace plays a critical role in performing dynamic lookups and enhancing the design-time experience of controls We focus on the design time support, . following OnItemCommand is mirrored by OnItemDataBound and OnItemCreated: protected virtual void OnItemCommand(RepeaterCommandEventArgs rce) { RepeaterCommandEventHandler repeaterCommandEventDelegate. RepeaterCommandEventHandler ItemCommand { add { Events.AddHandler(ItemCommandKey, value); } remove { Events.RemoveHandler(ItemCommandKey, value); } } The On-prefixed protected methods use standard event techniques. ItemCommand event is reproduced in a similar manner for the ItemCreated and ItemDataBound events: private static readonly object ItemCommandKey = new object(); public event RepeaterCommandEventHandler