How kind databind () is for Data Binding. We can't knock hundreds of times a day. But have you ever wondered how it works? Yes !!! (ROAR) today, let's talk about the databind thing with repeater. If you have read the basic application of label in the template engine I wrote, I pasted the implementation of a list label at the end, which means imitating repeater. But it doesn't matter if you haven't seen it. Today, let's take a look at the repeater of Microsoft.
The binding of controls is usually written in the page_load event method.
If (! Ispostback) {bindlist ();}
The bindlist method is used to bind controls. The purpose of this method is to use it in multiple places. Obviously, we usually bind them in this way.
Public void bindlist () {lst. datasource = datahelper. getlist (); lst. databind ();}
Then, based on what we wroteCodeCheck whether the binding is correct.
The first step is to assign a value to the datasource member to see what happened during the assignment?
Public Virtual Object datasource {[targetedpatchingoptout ("performance critical to inline this type of method authentication SS ngen image boundaries")] Get {return this. datasource;} set {If (value! = NULL )&&! (Value is ilistsource ))&&! (Value is ienumerable) {Throw new argumentexception (Sr. getstring ("invalid_datasource_type", new object [] {This. id});} This. datasource = value; this. ondatapropertychanged ();}}
From the above method, we can see that datasource is really not simple. A value assignment does a lot of work. In fact, get and set are two methods (get_datasource and set_datasource ), it's just better to write this in C.
If the specified datasource is not ienumerable (iteratable) and is not of the ilistsource type, an exception is thrown (parameter error: Invalid Data Source). This means that the ilistsource method is required, and it must be recycled (repeater must be a loop ).
Then the ondatapropertychanged () method is executed. Looking at the name, you can guess that this method is an underground party. He will tell some Members in the class to say, "Hey, dude, the data source has changed. Pay attention to all units !", At this time, when repeater is going to get data, he will ask this underground party: "Buddy, it's tight... "
Protected virtual void ondatapropertychanged () {If (this. _ throwondatapropertychange) {Throw new httpexception (Sr. getstring ("databoundcontrol_invaliddatapropertychange", new object [] {This. id});} If (this. _ inited) {This. requiresdatabinding = true;} This. _ currentviewvalid = false ;}
In the first sentence, an exception is thrown because the "Internal Organization" decides to kill people and has no chance. In the second sentence, _ inited, is it initialized? _ currentviewvalid indicates whether the current dataview has been verified? (The source of the data source is unknown)
The second sentence is to execute databind (), and databind will use the "underground organization" above.
Public override void databind () {If ((! This. isboundusingperformanceid |! Base. designMode) | (base. site! = NULL) {This. requiresdatabinding = false; this. ondatabinding (eventargs. Empty );}}
That is to say, use the performanceid to bind it? Obviously we are not. We directly specify the data source. If performanceid is specified, the datasource control named performanceid will be searched during render repeater. This is a method for specifying data sources at the front end. However, databind must be executed in the end. Let's take a look at the ondatabinding of repeater:
Protected override void ondatabinding (eventargs e) {base. ondatabinding (E); // executes the ondatabinding event of the parent control class this. controls. clear (); // clear the sub-control base. clearchildviewstate (); // clears the viewstate information of the Child control. This. createcontrolhierarchy (true); // create the sub-control base. childcontrolscreated = true; // Mark created}
The ondatabingding event of the parent class is simple:
Protected virtual void ondatabinding (eventargs e) {If (this. hasevents () {eventhandler handler = This. _ events [eventdatabinding] As eventhandler; If (handler! = NULL) {handler (this, e );}}}
check whether the databingding event method is subscribed and executed. Generally, data is bound to the itemtemplate sub-Control of repeater.
we will not check the cleared items. They are all removed. Let's take a look at createcontrolhierarchy:
/// <Summary> /// create a sub-control /// </Summary> /// <Param name = "usedatasource"> whether to use the data source </param> protected virtual void createcontrolhierarchy (bool usedatasource) {ienumerable DATA = NULL; // declare the data int dataitemcount =-1; // The number of data if (this. itemsarray! = NULL) {This. itemsarray. clear (); // check whether there is any template. If yes, clear it.} else {This. itemsarray = new arraylist (); // if it does not exist, initialize one.} If (! Usedatasource) // if the data source is not used, the capacity is directly initialized from viewstate and the pseudo data {dataitemcount = (INT) This. viewstate ["_! Itemcount "]; If (dataitemcount! =-1) {DATA = new dummydatasource (dataitemcount); this. itemsarray. capacity = dataitemcount;} else {DATA = This. getdata (); // If a data source is specified, icollection is2 = data as icollection; If (is2! = NULL) {This. itemsarray. capacity = is2.count ;}} if (Data! = NULL) // if the data is normal {int itemindex = 0; bool flag = This. separatortemplate! = NULL; // whether the delimiter template dataitemcount = 0; If (this. headertemplate! = NULL) // If a header exists, the header {This. createitem (-1, listitemtype. header, usedatasource, null); // apparently-1 is not counted as the number of rows, the type is the header, the data source is used, and the data used in the header row is actually null ...} foreach (Object obj2 in data) // traverses data {If (flag & (dataitemcount> 0) {This. createitem (itemindex-1, listitemtype. separator, usedatasource, null); // delimiter} listitemtype itemtype = (itemindex % 2) = 0 )? Listitemtype. item: listitemtype. alternatingitem; repeateritem = This. createitem (itemindex, itemtype, usedatasource, obj2); // This is created alternately between item and alter. itemsarray. add (item); dataitemcount ++; itemindex ++;} If (this. footertemplate! = NULL) {This. createitem (-1, listitemtype. footer, usedatasource, null); // create the bottom} If (usedatasource) {This. viewstate ["_! Itemcount "] = (Data! = NULL )? Dataitemcount:-1; // assign a value to itemcount of viewstate }}
From the preceding annotations, we can see that an item is created cyclically. These items are commonly used child controls such as headitemtemplate itemtemplate. Send data to them and enable them to bind data themselves. I did the same with my template engine :), but I did not copy him, haha.
The getdata method is used to obtain data:
Protected virtual ienumerable getdata () {performanceview view = This. connecttodatasourceview (); If (view! = NULL) {return view. executeselect (this. selectarguments);} return NULL ;}
The connecttodatasourceview () method is relatively long. The main task is to use the data source:
Private datasourceview connecttodatasourceview () {If (! This. _ currentviewvalid | base. designMode) // if the data source has not been verified, the value of the specified datasource will be _ currentviewvalid = false {If (this. _ currentview! = NULL) & this. _ currentviewisfromperformanceid) {This. _ currentview. performanceviewchanged-= new eventhandler (this. ondatasourceviewchanged);} idatasource source = NULL; string performanceid = This. performanceid; If (performanceid. length! = 0) // If the datasourceid data source is specified, findcontrol finds the corresponding datasource control {control = databoundcontrolhelper. findcontrol (this, performanceid); If (control = NULL) {Throw new httpexception (Sr. getstring ("datacontrol_datasourcedoesntexist", new object [] {This. ID, performanceid});} source = control as idatasource; If (Source = NULL) {Throw new httpexception (Sr. getstring ("datacontrol_performanceidmustbed Atacontrol ", new object [] {This. ID, performanceid}) ;}} if (Source = NULL) // if no data is found from the control, use the data source {source = new readonlydatasource (this. datasource, this. datamember);} else if (this. datasource! = NULL) // if both data sources exist, an exception is thrown {Throw new invalidoperationexception (Sr. getstring ("datacontrol_multipledatasources", new object [] {This. id});} performanceview view = source. getview (this. datamember); If (view = NULL) {Throw new invalidoperationexception (Sr. getstring ("datacontrol_viewnotfound", new object [] {This. id});} This. _ currentviewisfromperformanceid = This. isboundusingperformanceid; this. _ cur Lateral view = view; If (this. _ currentview! = NULL) & this. _ currentviewisfromperformanceid) {This. _ currentview. performanceviewchanged + = new eventhandler (this. ondatasourceviewchanged);} This. _ currentviewvalid = true;} return this. _ currentview ;}
let's take a look at the createitem method. Because the actual data shows where the data is, the general framework is completed here.
/// /// create the repeater's item /// /// row /// item type /// whether to bind data /// data of the row private repeateritem createitem (INT itemindex, listitemtype itemtype, bool databind, object dataitem) {repeateritem = This. createitem (itemind Ex, itemtype); // declare item repeateritemeventargs E = new repeateritemeventargs (item); // declare event parameters this. initializeitem (item); // assign values to various templates of repeater if (databind) {item. dataitem = dataitem; // If You Want To bind data, specify the data to the dataitem attribute} This. onitemcreated (E); // execute the event for creating an item (in fact, we have never used it) This. controls. add (item); // Add item if (databind) {item. databind (); // start Binding data ~ This. onitemdatabound (E); // Method for executing the Bound event item. dataitem = NULL; // unload data and wait for garbage collection} return item;}
the dateitem data of an item is the object to be reflected when databinder. eval ("XXX") is used. That is, the current data of each control. Yes, each control. We can see a lot of items in repeater...
the eval of databinder is a static method, and the code is very simple. It gets the value of dataitem by reflection. Let's look at his implementation:
Public static object eval (object container, string expression) {If (expression = NULL) {Throw new argumentnullexception ("expression");} expression = expression. trim (); If (expression. length = 0) {Throw new argumentnullexception ("expression");} If (Container = NULL) {return NULL;} string [] expressionparts = expression. split (expressionpartseparator); Return eval (container, expressionparts );} Private static object eval (object container, string [] expressionparts) {object propertyvalue = container; For (INT I = 0; (I <expressionparts. Length) & (propertyvalue! = NULL); I ++) {string propname = expressionparts [I]; If (propname. indexofany (indexexprstartchars) <0) {propertyvalue = getpropertyvalue (propertyvalue, propname);} else {propertyvalue = getindexedpropertyvalue (propertyvalue, propname);} return propertyvalue ;} public static string eval (object container, string expression, string format) {object obj2 = eval (container, expression); If (obj2 = NULL) | (obj2 = dbnull. value) {return string. empty;} If (string. isnullorempty (Format) {return obj2.tostring ();} return string. format (format, obj2);} public static object getdataitem (object container) {bool flag; return getdataitem (container, out flag );}
check the databind of repeateritem, which is actually the databind of the control class.
Public Virtual void databind () {This. databind (true);} protected virtual void databind (bool raiseondatabinding) {bool flag = false; If (this. isbindingcontainer) // is it the bound container {bool flag2; object dataitem = databinder. getdataitem (this, out flag2); // obtain the dataitem of the container. If it has been assigned a value before, internal execution also gets the member first, and the member cannot be obtained before reflection. If (flag2 & (this. Page! = NULL) // if data is obtained and the current page is not null {This. page. pushdatabindingcontext (dataitem); // I have not understood the usefulness of this sentence, that is, to send the data currently bound to a stack of the current page binding context, this data will be page. getdataitem () is used, so the eval of page can be used. But after the control is bound, it will be uninstalled? I am not sure if it is useful :) flag = true;} Try {If (raiseondatabinding) {This. ondatabinding (eventargs. empty);} This. databindchildren (); // bind the child control} finally {If (FLAG) {This. page. popdatabindingcontext (); // unmount the data after binding
The binding of child controls is actually a recursion of the entire process. No matter what child controls you have, let the child control come to databind () as follows:
Protected virtual void databindchildren () {If (this. hascontrols () {string errormsg = This. _ controls. setcollectionreadonly ("parent_collections_readonly"); try {int COUNT = This. _ controls. count; For (INT I = 0; I <count; I ++) {This. _ controls [I]. databind () ;}} finally {This. _ controls. setcollectionreadonly (errormsg) ;}} catch {Throw ;}}}
In this way, the entire binding process is like this. The repeater display is actually the display of each of his items.
After reading the CMS template engine, I will introduce nesting more here. My CMs template engine does not have the nested tag. If it is used, it will be similar to this method. However, this method does not pass the current dataitem to the Child control, because we will assign a value to the datasource of the child control in the itemdatabounded event method. However, I cannot customize the method in the template engine, so I have to pass dataitem in.
I think programming is very interesting. Sometimes you think about things and find that others have already implemented them, and they are doing exceptionally powerful things, but this is also a process of self-development. The reason for analyzing some of the source code of webform is that after the CMS template is written, it is found that something like webform is not as large as it is, but some ideas are surprisingly similar. Repeating the wheel may make it easier for us to understand something thoroughly. It's boring to just make a code assembler. What do you mean?