Reader Level:
ARTICLE

The NeFs Application Demo Part 2: Designing Application Oriented Server Controls

Posted by Abebe Assefa Articles | Networking October 31, 2002
This article is part II of a three part demo application dubbed Net-Worked Financial System written in C# and .NET framework.
  • 0
  • 0
  • 15914

Introduction

This article is part II of a three part demo application dubbed Net-Worked Financial System written in C# and .NET framework. The underlying object of the demo was to show how one could exploit the new architecture of .NET and C# to develop a server that serves both windows based clients as in the traditional two tier server-client architecture or web based clients as in a three-tier architecture.   

In the first phase of this exercise I have touched the main aspects of C# and .NET framework largely from windows based client development perspective. Web development is completely a different story. It requires a different approach, especially when one endeavors to develop windows based lookers-like client applications. In this article I present the possible design issues involved in developing basic server controls that are fundamental for building the Web Client.  The issue of using these controls and linking the web-client to NeFs server will be addressed in the final part of the demo application.
  
Requirement 

It helps a lot if you familiarize yourself (or are already familiar) with the following concepts to follow the logic behind the code and understand the coding. 

Simple and Composite ASP.NET server controls, State Management (View States), Type Converter, ControlBuilder, ControlDesigner, HtmlTextWriter, HtmlAttributes, HtmlStyleAttributes, Rendering and the different species of Rendering, Post-Back data and event handlers, Serialization, XML, Reflection, UI Type Editors, performance enhanced Event Handlers, Bubbled Events, Globalization, and the different control attributes (such as DesignerSerializationVisibility, Bindable, Browsable, etc.) 

At first glance the code seems a bit involving but if you approach disentangling it based on a structured flow of how server controls are rendered on a page it will become easier to understand. From ASP.NET server controls concept the sequence of events between initializing and disposing of the control are: Client Request Page --> Initialize-->Load View State-->Process Post Back Data-->Construct Controls ->Load-->Notify and Handle Post Back Changes -->Pre-render-->Save View State-->Render --> Dispose. (See below for brief discussion these steps.) 

Main Issues 

Unlike two tier traditional applications where clients have access to local resources, clients of web applications rely mainly on resources obtained from the server after a round-trip. The design of any web user interface therefore has to take into account this resource-consuming trip and minimize the number of round trips. The main areas of concern in many applications in general and web based applications in particular (even more so in the case of web oriented applications) are data capture, data display, data retrieve and store and the callback or event driven interactions amongst the various business and data access routines. 

The controls often used to accomplish these tasks are Grid, Dropdown and Text Box controls or a variety of these types of controls. The standard controls shipped with ASP.NET, though are great and sufficient to do various tasks; they do not quite address all application specific requirements. To address some of the specific requirements of the NeFs web-client and minimize the round trip and hence optimize the performance and code reusability, these base controls are developed with additional specific features. The unique features include horizontal-vertical scroll, adding editable blank rows for grid control; updating attached dataset for drop down control; and using text boxes for different data types with culture specific settings. 

Understanding the code 

It is beyond the scope of this article to cover the underlying concepts of ASP.Net server controls in detail. However, to facilitate our understanding of the included code, I will go through the main aspects of the code very briefly. 

Without loss of generality, the code can be divided into two major parts for the sake argument. 

  1. Code related to methods, properties and interfaces of ASP.Net framework; and
  2. Code included to support the re-implementation of the framework and to add additional control specific features.

Note that there is similarity in the over all architecture and design of the controls as all are based on the ASP.Net server controls' architecture. However the similarity ends here, as the actual implementation of the code is specific to each control and the specific functionality that it renders.      

The Lifecycle of ASP.Net Server control and the Code 

Initialization  

To initialize the control we can overwrite the OnInit() method  of  the Web control class. This method is not re- implemented in our controls. The framework using the default initializer initializes the controls. 

Load View State 

The protected methods of the base class namely, LoadViewState, SaveViewState and TrackViewState are re-implemented in all the three controls. These methods serve to manage the state of the controls during round-trip. Custom type converters are used to convert non-storable data types into strings and string arrays. Note that one could equally use serialization to store custom data types in the view state. Here we used type converters to take the performance advantage that TypeConverters have over serialization. 

A typical implementation of a custom type converter class involves inheriting from ASP.NET's TypeConverter base class and re-implementing the appropriate methods. The typical methods re-implemented from the base class are shown in Listing 1 below. 

Listing 1:   TypeConverter Methods 

public override bool CanConvertFrom(ITypeDescriptorContext context,Type source){ ... }
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture,
object value){ ...}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destType){...}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture,
object value, Type destType){...}
public override bool GetPropertiesSupported(ITypeDescriptorContext context){...}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext
context,
object value,Attribute [] attributes){...} 

The methods that return Boolean simply inform the ASP.Net framework if the underlying method is supported in the implementation. ConvertFrom and ConvertTo are used to convert the underlying data type to and from string while GetProperties is used to filter attributes that are displayed on custom UIType editor at design time. Using a simple marker attribute class NeFsSystem.WebControlDesigner.PropertyIdentifier we identify the relevant properties of the control we want to display. This is implemented in NeFsGridColumn where we used NeFsColumnSerializerTypeConverter and in DropDown Items where we used NeFsPropertyFilterTypeConverter class. 

The type converter of a class (a class decorated with the appropriate type converter attribute) is accessed via the ComponentModel.TypeDescriptor's class GetConverter method. The type converter of the double object for instance is listed in Listing 2 below: 

Listing 2: Example of getting type converter 

TypeConverter dblConveter = TypeDescriptor.GetConverter(typeof(double));

For more detailed illustration see the code in the NeFsWebGrid's LoadViewState method. Here the most relevant properties of the grid are stored in the ViewState. A type converter for the TextBox, the row and the column objects are provided in the code. However, to enhance performance they are not implemented. One can easily re-introduce the converters if there is a need to store these objects in the ViewState. In the same vein the datasource of the controls can be stored in the ViewState. The WebCtlUtilities.DataSetToXml and WebCtlUtilities.XmlToDataSet methods have been introduced to accomplish this task. But for the sake of performance they are not implemented. 

A call to any of the type converter methods requires a System.ComponentModel.ITypeDescriptorContext type argument. To pass this argument to the methods, the NeFsWebGrid class, where type converters are heavily used, implements the interface ITypeDescriptorContext.  This enables to pass the 'this' instance of the class to the converter method there by providing a direct access to the properties of the instance of the object. 

The implementation of the LoadViewState method for the dropdown control uses the Triplet special class of .Net to save the state of the control. In addition to the standard implementation, we used the dropdown's LoadViewState method to re-initialize the attached datasource by calling the OnInitDataSource method that raises the initDataSourceEvent. This will enable the control to reinstate the state of the attached datasource without reverting to ViewState. Here it is assumed that the client is responsible for managing the state of the dataset, for instance using Session variables.

The Text Box control's LoadViewState method only loads the state of the text; the other states are directly stored and restored locally in the appropriate properties.  We override the Load and Save ViewState methods and provide the appropriate type converters if the control requires storing non-storable data types in a view state. Failing to do so will throw an HttpException at run time.

Constructing the controls

The construction of a control, especially a data bound control, requires implementing the following methods. (In non-data bound controls rendering the control will be suffice to construct the output.)  Note that in the case of data bound controls we still need the Rendering method to render the output using HtmlTextWriter. The main methods involved in constructing the state of the control are listed in Listing 4 below:

Listing 4: Constructing the Control

protected override void CreateChildControls(){...}
protected override void OnDataBinding(EventArgs e){...}
protected void CreateControlHierarchy(bool basedOnDataSource){...}

The OnDataBinding method is invoked when the client calls the DataBind() method. The OnDataBinding method simply calls the CreateControlHierarchy method with the argument set to true. In the CreateControlHierarchy method we loop through the data source and attach the values to the control's Item appropriately.

The ASP framework usually calls the CreateChildControls method immediately after the LoadViewState method has been processed. This is simply to re-instate the control with values restored from the LoadViewState method. This method calls the CreateControlHierarchy method with the argument set to false. Note that the CreateChildControls method can also be called from other properties if we want to reconstruct the control. We can achieve the same thing equivalently using the EnsureChildControls() method from properties where reconstructing the control deems necessary every time the value of the property changes.

Process postback data

At this stage of the control's lifecycle, the control analyzes the incoming data and updates the state of its properties appropriately. To do so the control need to implement the System.Web.UI.IPostBackDataHandler interface.  The NeFsWebTextBox and the NeFsWebDropDownList implement this interface. Since the grid is a composite control (a composite of the Textbox controls) the actual postback data analysis is carried out by the instances of the textboxes. The IPostBackDataHandler interface has tow methods. These methods are listed in Listing 3 below:

Listing 3 : The IPostBackDataHandler interface methods

bool IPostBackDataHandler.LoadPostData(string postDataKey,NameValueCollection postData){...}
void IPostBackDataHandler.RaisePostDataChangedEvent(){...}

The LoadPostData method compares the value retrieved from NameValueCollection using postDataKey with the value restored from the ViewState. If the values are the same it returns false otherwise it returns true. In the case of the dropdown control we loop through all the dropdown items and set the selected value of the new selected item to true if the selection is different from what has been saved in the ViewState.  It is at this stage of the life cycle of the dropdown control that we update the corresponding value of the attached datasource appropriately and raise the afterUpdateAttachedFieldEvent and the beforeUpdateAttachedFieldEvent events.

Load

At this point in the cycle the controls are initialized and reinstated. Note that the values of the controls at this stage reflect the data from the client. In this particular demo the Load method is not implemented. But if there is a need to access the client data and raise events or further process data this is the appropriate step to do so.

Notify and Handle Post Back Changes

The LoadPostData method notifies the ASP.NET framework if there is a change in the value of the control by returning true. If the case is this, the RaisePostaDataChangedEvent() method is called. In order for this change to take place in our control we need to override and re-implement this method. This method has been implemented in NeFsWebTextBox and NeFsWebDropDownList control. In both cases we handle the post back changes and raise the appropriate events for the client of the control to take further action as required. For instance in the LoadPostData method of the NeFsWebTextBox control we compare the new value with the old value, we verify that the data in the data source has already been programmatically modified (otherwise we end up overwriting the modified data value if we do not do this) and finally return true if there is a change in value. In the RaisePostaDataChangedEvent() method we handle the events and raise the instance of the delegate or simple bubble the event.

Prerender

Here we do any update required before the output is rendered to the client. Note that any change made at this stage will be saved in the ViewState unlike in the Render stage where we lose any change that we made to the control. This method is frequently used to register the client script using System.Web.UI.Page.RegisterClientScriptBlock for further client side actions such as setting focus to the control, validation, responding to key presses ...etc.

Save View State

The control saves its state using the appropriate conversion routines at this stage. The NeFsWebGrid control uses the relevant type converters to do the conversion from non-storable data type to string. (See the Load View State step above). The dropdown control saves the state of its selected dropdown item in Triplet object. The TextBox control saves the state of the properties locally.

Render

At this stage of the control we construct the output that we would like to be returned to the client. We use the System.Web.UI.HtmlTextWriter class to write a sequential series of Html characters on the web forms page. Here we could use Render, RenderContents, AddAttributesToRender methods. (For a detailed account of these methods refer to the ASP.NET documentation.)

Dispose

This is the final stage of the life cycle of the control. Here we dispose any overhead-heavy resource references by overriding the Dispose() method.

The Other part of the Code

The second category of the code fragment deals with the specific feature of the controls and provides various supports to the implementation of ASP server framework discussed above. Most of the code that provides design time support to the controls uses the design time framework of Visual studio .NET. This mainly includes Type editors, Control builders and Control designers. The other segment of the code which is largely included in NeFsSystem.NeFsWebUtilities namespace provides both design and runtime routines to facilitate the construction of the controls appropriately. (See code for details.)

One main point that requires mentioning at this point concerns the issue of globalization. Going Internet with an application, unless we specifically targeted a specific culture, requires taking into account the different culture of our clients. Culture setting in this demo is handled via the NeFsTextBox control's Culture property. (Note that in practice the setting can be controlled programmatically at run time via this control or via a more generic approach that applies to all controls and allow the end-user to control the culture setting.)  One thing that we should be careful is in the implementation of the culture, in particular if we are handling different cultures for different instances of the controls on the same page or composite control such as the grid control. To handle this the WebCtlUtilities class is aggregated and instantiated by the NeFsTextBox. Each time there is a change in culture setting this change will be passed to the Utilities class that provides various support methods to process the culture. Therefore handling culture sensitive values requires asking the instance of the WebCtlUtilities, attached to the textbox, for the specific culture and doing the process appropriately.  For instance if we are converting the DateTime string value of the text box to DateTime object using DateTime.Parse(string value) method  this will return a wrong value unless the culture setting of the control and the current threads' CultureInfo are the same.  The correct way of addressing this, in this particular scenario, is to use the [txtboxinstanceobject].GetUtilityInstanceObject().GetDateTime(value) method. This method appropriately converts the string to DateTime reflecting the underlying culture attached to the textbox control. (It is worth going through the code and appreciate how it works to get a good grasp of the code.)

Control Design and Implementation

The three base controls included in this article controls are organized in to five assemblies. These are:

  1. NeFsWebControlsGenericSupport,
  2. NeFsWebDataGrid,
  3. NeFsWebGridDataSupport,
  4. NeFsWebDropDownBox,
  5. NeFsWebTextBox.

NeFsWebControlsGenericSupport

This assembly provides common and specialized services to the other assemblies.

NeFsWebDataGrid

This assembly is relatively more complicated than the others. The grid is implemented using template technology of ASP Server Controls. The design of the grid is depicted in the following diagram:

DemoNe11.gif

The implementation of the grid control imitates the ITemplate structure of ASP.NET server controls to a certain extent. The main NeFsWebGrid class inherits from Web control and implements INamingConatiner (a marker interface that enables to identify the child controls by assigning unique ID during round trip) and the ITypeDescriptorContext interface that enables the control to pass the 'this' instance of NeFsWebGrid class while type converting the relevant objects during loading and saving the ViewState of the grid.

The NeFsWebGrid class aggregates and instantiates the NeFsRow and NeFsGridColumn classes. The NeFsGridColumn object can also be instantiated and added to a specialized collection object of NeFsColumnCollection class programmatically (or using NeFsColumnTypeUIEditors.cs at design time). The collection object will be passed to the NeFsGridDynTemplate constructor and this in turn will be passed to the NeFsWebGrid object via the DynamicTemplate property or added to the column collection via AddParsedSubObject while parsing the child controls of the grid by the ControlBuilder.

During constructing the grid, the instance of the row will be passed to the NeFsGridDynTemplate via the InstantiateIn method. The instance of the row includes user set attributes of the grid and instances of NeFsTextBox control for each cell. The NeFsGridDynTemplate adds the column attributes and calls the BindControl() method of the instance of the row and adds the required cells to a TableRow and the TableRow to the instance of the grid table passed to the instance of the row from NeFsWebGrid object.  Vertical, Horizontal, Row Header, Footer and Column Header will be added appropriately depending on the state of the relevant switch properties of the NeFsWebGrid class.  This is accomplished by calling the AddHorizontalScroll, AddVerticalScroll, includeRowHeader methods of NeFsRow class appropriately.

The OnBubbleEvent method is overridden to capture the bubbled events from the instances of the NeFsTextBox control and the four command buttons added to facilitate scrolling through the dataset. The events from NeFsTextBox will be fired only if user modifies the data in the cell.  If there is data modification, this will be automatically reflected in the underlying dataset. Note that we are not doing any resource consuming looping to do the update.   Instead we are enabling the NefsTextBox control to recognize its position in the table, and on data post back if the data in the cell is modified we return the position and using this row-column position we modify the cell in the dataset appropriately.  Similarly if one of the command buttons is clicked this event is captured by the overridden bubbled event. Depending on the clicked button, the program automatically computes the start and end row and column of the next segment of the dataset to be displayed or the required blank row to be added. If the case is adding blank editable row, the program first modifies the underlying dataset accordingly before displaying the blank row. The start and end values will be used by the CreateControlHierarchy method to pass the required rows and columns to the BindControl method of the row object. (See code for more.)

Since the NeFsGridColumn class inherits from the NeFsTextBox, the attributes of the column can directly be applied to the cells.

Note: for add the blank row functionality of the grid to work you need to set the DynamicTable property of the Grid to true.

NeFsWebDropDownBox

The main reason for including the dropdown list box is to add a functionality that enables the control to modify the required field of an attached dataset appropriately. It is not unusual to have a static option data in an application that drives the method of implementation of the underlying business or data access logic. For instance to mention one in the finance industry we have a day-count convention such as ACT/360, ACT/365,ACT/ACT (ACT= Actual) ...etc. These options will influence the way the financial period is calculated and hence impact the underlying value of the instrument under consideration. For the sake of rationalization most of this and other conventions such as date roll, payment method, Reset values and others are stored and accessed from a specific table and the selected values are stored with the relevant instrument or security in a separate table.

Enabling the base generic server controls to do most of the task of capturing, displaying and processing data will optimize the performance of the application in addition to the advantages of reusability and neatness that it introduces. Adding attached data source in the dropdown list box accomplishes this object by allowing the static options to be loaded from one table and update the corresponding value of the attached data source when user updates the selection. Note that we do not have to do nothing except passing the source of the attached table and the relevant information that enables the control which field and row requires update when the list box selected item is updated. All what we need at the end of the session or as frequently as we require, is just grab the attached dataset and update the corresponding table in the database (for instance using the functionality included in the NeFsClientDataAccessComponent assembly.) as the dataset has already been updated for us by the control.

The dropdown list box aggregates the DropDownItem class included in  NeFsWebDropDownCtlItem.cs file of  the NeFsWebControlsGenericSupport assembly.

The Dropdown ListBox includes two attributes PersistenceMode attribute with PersistenceMode.InnerProperty value, this attribute enables the control to persist the item in HTML format in the Html Code window of the page.  The other attribute is a TypeConverter attribute (NeFsPropertyFilterTypeConverter) that allows the control to pick properties marked with PropertyIdentifier marker object to be displayed in UIType Editor (NeFsdropDownDesignTimeEditor.cs) at design time.

The DropDownItem class inherits from WebControl and overrides the Render method where the HtmlTextWriterTag.Option is rendered with HtmlTextWriterAttribute.Selected attribute that enables the control to display the selected field. The other overridden methods are the Load, Save and Track ViewState methods. Using the special Triplet class of .NET we store the state of the selected field value, text and selection status properties of each instance of the DropDownItem class.

The dropdown list box instantiates DropDownItem objects and populates the relevant text and field values form the data source and renders the HtmlTextWriterTag.Select to allow the selection mechanism at run time.

To update the attached field use the following properties

AttachedValueFieldName:  a field name that enables the control to identify the field text to be modified. Note this field needs to be a primary key i.e.; unique and non-replicable;

AttachedTextFieldName: This is the field that requires update;

CurrentAttachedFieldValue: is basically the ID that identifies the row;

Note that the values can be passed to a number of instances of the dropdown list boxes programmatically a systematic fashion.  The other possibility, which is not included here, is to allow the control to be attached to more than one data source so that a number of updates can be done in one go.

AttachedDataSource: is a dataset object that contains the table;

AttachedDataMemeberName: is the data member usually the name of the table in the dataset.

Using the ListSource property we include the source of the static options. Note that we have used IEnumerable source, this will allow us to use SqlDataReader object more efficiently. As we are not going to update the options in the list box this seems to be a reasonable choice. 

NeFsTextBox

The NeFsTextBox control has more functionality than the standard textbox control shipped with ASP.NET. This functionality includes culture setting functionality (which is more relevant for culture sensitive data types), text type setting property (literal, currency, numeric), formatting numeric types (decimal, percentage, integer), precision setting mechanism. In addition to its use in its own right, the NeFsTextBox together with NeFsGridColumn object serves to set the attributes of the column of the grid as required in line with the textbox attributes just mentioned.

The NeFsTextBox inherits from WebControls.TextBox and implements the INamingContainer and IPostBackDataHandler interfaces. The text type attributes are validated during attaching the text using the Text property. If the text is not valid it returns an error message, which is directly rendered in the textbox control to be displayed to the user.  The change in the text is captured by the LoadPostData method. If there is a change in value during postback the LoadPostdata method returns true and this will enable the framework to call the RaisePostDataChangedEvent method.  If the control is data bound the corresponding field in the dataset will be updated. To allow the client handle further actions we trigger the TextUpdated event. For instance the DataGrid control, as discussed above, uses this event to update the data field in the data set.

Compiling and Dependence Issue

Compile the source files in the following order avoiding dependence problem.

  1. NeFsWebControlsGenericSupport;
  2. NeFsWebTextBox;
  3. NeFsWebGridDataSupport;
  4. NeFsWebDataGrid;
  5. NeFsWebDropDownBox;  (you can compile this at any time so long as it is after the first one.)

The required referenced assemblies are listed below. Use this as a reference if you ran into difficulties when building the projects.

NeFsWebControlsGenericSupport

.Net Assemplies: System.dll, System.Data.dll, System.Design.dll, System.Drawing.dll, System.Windows.Forms.dll, System.Xml.dll.

NeFsWebTextBox

NeFs Assemblies:  NeFsWebControls.dll
Net Assemplies:  System.dll, System.Data.dll, System.Design.dll, System.Drawing.dll, System.Web.dll, System.Xml.dll

NeFsWebGridDataSupport

NeFs Assemblies: NeFsWebControls.dll, NeFsWebTextBox.dll 
Net Assemplies: System.dll, System.Data.dll, System.Design.dll, System.Drawing.dll, System.Windows.Forms.dll, System.Xml.dll.

NeFsWebDataGrid

NeFs Assemblies:  NeFsWebControls.dll, NeFsWebTextBox.dll, NeFsWebGridDataSupport
Net Assemplies:  System.dll, System.Data.dll, System.Design.dll, System.Drawing.dll, System.Windows.Forms.dll, System.Xml.dll

NeFsWebDropDownBox

NeFs Assemblies:  NeFsWebControls.dll 
Net Assemplies: System.dll, System.Data.dll, System.Design.dll, System.Drawing.dll, System.Windows.Forms.dll, System.Xml.dll

Conclusion

This article addressed some of the issues that we confront when attempting to develop web based client application. In doing so a detailed account of how one can develop ASP.NET server controls have been explained. The controls can further be performance enhanced by adding client side script such as setting focus, adding validation script at client side, responding to key presses and so on by registering the client script using System.Web.UI.Page.RegisterClientScriptBlock in the PreRender method of the controls.

COMMENT USING