This is defined in the Control class as the type StateBag, and allows server controls to store and retrieve values that are automatically round-tripped and recreated during a postback..
Trang 2The following HTML is rendered by this control:
<html>
<body>
<form name="ctrl0" method="post" action="myfirstcontrol.aspx" id="ctrl0">
<input type="hidden" name=" VIEWSTATE"
value="dDwtMTM0MTQ1NDExNjt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjs+O2w8dDxwPGw8TnVtYmVyOz
47bDxpPDQ4Pjs+Pjs7Pjs+Pjs+Pjs+" />
The Number is 48 (<a href="javascript: doPostBack('ctrl1','inc')"'>Increase Number</a> or
<a href="javascript: doPostBack('ctrl1','dec')">Decrease Number)</a>
<input type="hidden" name=" EVENTTARGET" value="" />
<input type="hidden" name=" EVENTARGUMENT" value="" />
<script language="javascript">
<!
function doPostBack(eventTarget, eventArgument) {
var theform = document.ctrl0;
Trang 3function doPostBack(eventTarget, eventArgument) {
var theform = document.ctrl0;
Trang 4Now we have covered handling postback and events, let's look at how a control can persist state during postback using viewstate
Using postback data to manage the state of a control is a good technique when it can be used, but there are some drawbacks The most obvious one is that only certain HTML elements like input can use postback If you had a label control that needed to remember its value, you couldn't use postback Also, postback is only really designed to contain a single item of data For example, our textbox control needs to remember its last value so it can raise a TextChanged event when the value changes To maintain this additional state, one option would be to use hidden fields When a control renders its output, it could also output hidden fields with other values that need to be remembered When a postback occurs, these values would be retrieved into the LoadPostData method This approach would work for a single control, but could be problematic when there are potentially many instances of the same control on a page For example, what would you call the hidden fields? How could you ensure the names do not clash with names a page developer may have used?
To resolve the problems of managing state ASP.NET has a feature called viewstate In a nutshell, viewstate is a hidden input field that can contain state for any number of server controls This hidden field is automatically managed for you, and as a control author you never need to access it directly
Introducing the StateBag
All server controls have a property called ViewState This is defined in the Control class as the type StateBag, and allows server controls to store and retrieve values that are automatically round-tripped and recreated during a postback
During the save state stage of a page, the ASP.NET framework enumerates all server controls within a page and persists their combined state into a hidden field called VIEWSTATE If you view any rendered ASP.NET containing a form element you will see this field:
<input type="hidden" name=" VIEWSTATE" value="dDwtMTcxOTc0MTI5NDs7Pg==" />
Trang 5When a postback occurs, ASP.NET decodes the VIEWSTATE hidden field and automatically repopulates the viewstate for each server control as they are created This reloading of state occurs during the load state stage of a page for controls that are declared on an ASP.NET page If a control is dynamically created, either on a page or within another composite control, the state will be loaded at the point of creation ASP.NET keeps track of what viewstate hasn't been processed, and when a new control is added to the Controls property of a Control (remember a page is a control), it checks to see if it has any viewstate for the control If it has, it is loaded into the control at that point
To see viewstate in action, we will change our textbox control to store its current value in viewstate, rather than the _value field By doing this, when LoadPostData is called to enable our textbox control to retrieve its new value, we can compare it to the old value held in viewstate If the values are different we will return true, causing a TextChanged event to be raised in RaisePostDataChangedEvent If the values are the same, we will return false so
RaisePostDataChangedEvent is not called, and no event is raised
The StateBag class implements the IDictionary interface, and for the most part is used just like the Hashtable class with a string key All items stored are of the type System.Object, so any type can be held in the viewstate, and casting
is required to retrieving an item
In our earlier textbox control we used a string member variable _value to hold the current value of our textbox We'll delete that variable and rewrite the property to use viewstate:
public string Text
Trang 6ViewState["value"] = value;
}
}
Since we have deleted the _value member variable and replaced it with this property, we need to change all references
to it, with the Text property We could directly reference the ViewState where we previously used _value, but it's good practice to use properties to encapsulate our usage of viewstate, making our code cleaner and more maintainable (For example, if we changed the viewstate key name used for the text value, we'd only have to do it in one place.)
With this new property in place, we can revise the LoadPostData to perform the check against the existing value as discussed:
bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection
of a label to reflect our textbox value when our event is raised:
<script runat="server" language="C#">
Trang 7private void OnNameChanged( object sender, EventArgs e )
<ASP:Label runat="server" EnableViewState="false" id="status" />
During the save state stage of a page, the ASP.NET page framework will not persist viewstate for the controls with an EnableViewState property of false This change to the page will therefore make our label forget its value during each postback
Setting EnableViewState to false does not prevent a control from remembering state using postback As such, should you need to reset the value of a textbox, you'd have to clear the Text property in a page's init/load event With all these changes made, if we enter a value of "Wrox Press" and press the postback button, we will see that during the first postback our event is fired, and our label control displays the value:
Trang 8If we click the postback button again, the textbox control will use its viewstate to determine that the postback value has not changed, and it will not fire its TextChanged event Since the label control does not remember its state, as we disabled viewstate for it, the value-changed message will not appear during the second postback since the label will default back to its original blank value:
Our textbox control is now pretty functional for a simple control: it can remember its value during postback, can raise events when its text changes, and can have style properties applied in the same way as other web controls using the various style attributes:
<Wrox:MyTextBox id="name" runat="server"
BackColor="Green"
ForeColor="Yellow"
Trang 9 Init - called when a control has to be constructed and its properties have been set
Load - called when a control's viewstate is available
DataBinding - called when a control bound to a data source should enumerate its data source and build its control tree
PreRender - called just before the UI of a control is rendered
Unload - called when a control has been rendered
Disposed - called when a control is destroyed by its container
These events behave just like any other event For example, we could catch the PreRender event of our TextBox and restrict its length to seven characters by adding an OnPreRender attribute to our control declaration:
<P>Enter a value: <Wrox:MyTextBox id="name" runat="server"
Trang 10private void OnPreRender( object sender, EventArgs e )
protected override void OnInit(EventArgs e)
{ base.OnInit(e);
if ( _text == null )
_text = "Here is some default text";
}
Trang 11Event Optimization in C# Using the EventHandlerList
When an event is declared within a class definition, additional memory must be allocated for an object instance at run-time for the field containing the event As the number of events a class supports increases, the amount of memory consumed by each and every object instance increases Assuming that a control supports 10 events (the 6 built-in ones and 4 custom events), and assuming an event declaration requires roughly 16 bytes of memory, each object instance will require 160 bytes of memory If nobody is interested in any of these events, this is a lot of overhead for a single control
To only consume memory for events that are in use, ASP.NET controls can use the EventHandlerList class
The EventHandlerList is an optimized list class designed to hold delegates The list can hold any number of delegates, and each delegate is associated with a key The Control class has an Events property that returns a reference to an instance of the EventHandlerList This instantiates the class on demand, so if no event handlers are in use, there is almost no overhead:
protected EventHandlerList Events
The EventHandlerList class has two main methods:
void AddHandler( object key, Delegate handler );
Trang 12void RemoveHandler( object key, Delegate handler );
AddHandler is used to associate a delegate (event handler) with a given key If the method is called with a key for which
a delegate already exists, the two delegates will be combined and both will be called when an event is raised
RemoveHandler simply performs the reverse of AddHandler
Using the Events property, a server control should implement support for an event using a property declared as the type event:
private static readonly object _textChanged = new object();
public event EventHandler TextChanged
{
add { Events.AddHandler(EventPreRender, value); }
remove { Events.RemoveHandler(EventPreRender, value); }
}
Since this property is declared as an event, we have to use the add and remove property accessor declarations, rather than get and set When add or remove are called, the value is equal to the delegate being added or removed, so we use this when calling AddHandler or RemoveHandler
As Visual Basic NET does not support the add/remove accessor, we can't use optimized event handlers in Visual Basic NET
To create a unique key for our events, which we know will not clash with any events defined in our base classes, we define
a static, read-only member variable called _textChanged, and instantiate it with an object reference We could use other techniques for creating the key, but this approach adds no overhead for each instance of our server control, and is also the technique used by the built-in ASP.NET server controls By making the key value static, there is no per-object overhead
Checking and raising an event using the Events property is done by determining if a delegate exists for the key associated with an event If it does, we raise it to notify one or more subscribed listeners:
void IPostBackDataHandler.RaisePostDataChangedEvent()
Trang 13Tracking ViewState
When adding and removing items from viewstate, they are only persisted by a control if its viewstate is being tracked This tracking only occurs after the initialization phase of a page is completed This means any changes a server control makes
to itself, or to another control before this phase, and the OnInit event has been raised, will not be saved
Types and ViewState
We mentioned earlier that the StateBag class used to implement viewstate allows any type to be saved and retrieved from it While this is true, this does not mean that you can use any type with it Only types that can be safely persisted can be used As such, types that maintain resources such as database connections or file handles should not be used ViewState is optimized and designed to work with the following types:
Int32, Boolean, String, and other primitive types
Arrays of Int32, Boolean, String, and other primitive types
ArrayList, Hashtable
Types that have a type converter A type converter is a class derived from
System.ComponentModel.TypeConverter that can convert one type into another For example, the type converter for the Color class can convert the string "red" into the enumeration value for red ASP.NET requires
a type converter that can convert a type to and from a string
Types that are serializable (marked with the serializable attribute, or support the serialization interfaces)
Trang 14 Pair, Triplet (defined in System.Web.UI, and respectively hold 2 or 3 of the other types listed)
ViewState is converted from these types into a string by the Limited Object Serialization (LOS) formatter class (System.Web.UI.LosFormatter)
The LOS formatter used by ASP.NET encodes a hash code into viewstate when a page is generated This hash code is used during postback to determine if the static control declarations in an ASP.NET page have changed (for example, the number and ordering of server controls declared within an ASP.NET page) If a change is detected, all viewstate is discarded, since viewstate cannot reliably be processed if the structure of a page has changed This limitation stems from the fact that ASP.NET automatically assigns unique identifiers to controls, and uses these identifiers to associate viewstate with individual given controls If a page structure changes, so do the unique identifiers assigned to controls, so the viewstate/control relationship is meaningless In case you're wondering, yes, this is one technical reason why ASP.NET only allows a page to postback to itself
More on Object Properties and Template UI
Earlier, we discussed how the default control builder of a server control would automatically map sub-elements defined within a server-control declaration to public properties of that control For example, given the following server-control declaration:
<Wrox:ICollectionLister id="SessionList" runat="server">
The control builder of the ICollectionLister control shown here would try to initialize the object properties
HeadingStyle and ItemStyle, determining the type of the object properties by examining the meta data of the ICollectionLister class using reflection As the HeadingStyle element in this example has a Font sub-element, the control builder would determine that the HeadingStyle object property has an object property of Font
The ICollectionLister server control is a simple composite control we've created that can enumerate the contents of any collection class implementing the ICollection For each item in the collection it creates a Label control, and sets the text of the label using the ToString method of the current item in the collection This causes a linebreak because for each item in the collection, the label starts with "<BR>" The control also has a fixed heading of "ICollection Lister
Trang 15Control" which is also created using a label control
The ICollectionLister control has three properties:
DataSource - a public property of the type ICollection When CreateChildControls is called this property is enumerated to generate the main output of the control
HeadingStyle - a public property of the type Style This allows users of the control to specify the style attributes used for the hard-coded heading text The Style.ApplyStyle method is used to copy this style object into the Label control created for the header
ItemStyle - a public property of the type Style This allows users of the control to specify the style attributes used for each of the collections that is rendered The Style.ApplyStyle method is used to copy this style object into the Label control created for each item
The code for this server control is shown here:
Trang 16{
get { return _datasource; }
set { _datasource = value; }
}
Style _headingStyle = new Style();
public Style HeadingStyle
{
get{ return _headingStyle; }
}
Style _itemStyle = new Style();
public Style ItemStyle
Trang 17// Create the heading, using the specified user style
Trang 18<script runat="server" language="C#">
void Page_Load( object sender, EventArgs e )
Trang 19<Font Size="18"/>
</HeadingStyle>
<ItemStyle ForeColor="Green" Font-Size="12"/>
</Wrox:ICollectionLister>
The output from this page (if viewed in color) is a blue header with green text for each item in the collection:
For controls that have fixed style and layout requirements, initializing them using object properties like we have in the ICollectionLister control is a good approach You will have seen the same approach used throughout the standard ASP.NET server controls, such as the data grid and data list However, for a control to provide ultimate flexibility, it's better to enable the user of the control to define what the UI of a control looks like by using templates Again, you'll have seen this in earlier chapters, with controls like the data grid
Supporting template properties in a server control is relatively straightforward, although when using them within a data-bound control things can initially seem a little complex, since the way child controls are created has to be handled
Trang 20slightly differently
Let's introduce templates by rewriting our ICollectionLister control to support a heading and item template We need to make the following changes to our code:
Change the HeadingStyle and ItemStyle properties to the ITemplate type
Make the HeadingStyle and ItemStyle properties writeable This has to be done since the objects implementing the ITemplate interface are dynamically created by the ASP.NET page and then associated with our server control
Use the TemplateContainer attribute to give the control builder a hint about the type of object within which our templates will be instantiated This reduces the need for casting in databinding syntax
Our changed code is shown here:
ITemplate _headingStyle;
[TemplateContainer(typeof(ICollectionLister))]
public ITemplate HeadingStyle
{
get{ return _headingStyle; }
set{ _headingStyle = value; }