8.4
|
8.5 Validation and Data BindingDepending on the features of your custom controls, it may make sense to add support for either validation or data binding. This section describes how to build controls that support these two capabilities. 8.5.1 Supporting Validation
You can add validation support for custom controls that collect information from the
Listing 8-36 A Control That Supports Validation
[ValidationProperty("Text")]
public class CustomTextBox : Control
{
public CustomTextBox()
{ ViewState["Text"] = ""; }
public string Text
{
get { return (string) ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
protected override void Render(HtmlTextWriter output)
{
output.AddAttribute(HtmlTextWriterAttribute.Name,
UniqueID);
output.AddAttribute(HtmlTextWriterAttribute.Value,
Text);
output.RenderBeginTag(HtmlTextWriterTag.Input);
output.RenderEndTag();
}
}
Listing 8-37 Client to Custom Control Supporting Validation
<%@ Page Language='C#' %>
<%@ Register TagPrefix='eadn'
Namespace='EssentialAspDotNet.CustomControls'
Assembly='Validation' %>
<html>
<body>
<form runat=server>
<eadn:ValidatableControl id='ctb' runat='server' />
<asp:RequiredFieldValidator runat='server'
ControlToValidate='ctb'>*
</asp:RequiredFieldValidator>
<input type='submit' value='submit' />
</form>
</body>
</html>
8.5.2 Data-Bound Controls
Many controls in the
WebControls
hierarchy support the concept of data binding. You can add data binding support to your custom controls as well by exposing a
DataSource
property. When your control
To begin with, it is important to understand how
Figure 8-5. Interaction between a Page and a Data-Bound Control
8.5.3 Implementing a Data-Bound ControlThe first step in creating a data-bound control is to define a DataSource property. This involves adding a public property called DataSource to your class of type object . Next you need to override the virtual function OnDataBinding , inherited from the Control base class. Your implementation of OnDataBinding should iterate across the data source that was assigned through your control's property, saving the data to a local data structure for future rendering. Note that it is not safe to assume that the data source will be around during your Render method, so you must take this step to ensure that you save the data that your control needs to be able to render itself during the OnDataBinding method call.
If your control expects only a list of items, not a tabular set of data, to be bound to it, you should expose another property, called
DataTextField
. This field will contain the index into a rowset that the client expects you to dereference when you perform the data binding. If you have the capability of associating values with the data items, you can also expose a property called
DataValueField
(most of the data-bound list controls in the base class hierarchy expose both of these fields). You should also be as
Clients of data-bound controls expect the controls to retain their state across post-backs. This lets page authors populate a control once if
IsPostBack
is
false
, and avoid additional round-trips to the database if the page is posted to again. As the author of a data bound control, you are responsible for making sure that your control's state is retained across a post-back. Typically, you do this by using the
ViewState
mechanism described earlier in this chapter. Because data-bound controls usually need to persist collections of data into view state, it is typically most efficient to override the
LoadViewState
and
SaveViewState
Listing 8-38 shows a sample control that supports data binding to all the different data source types with state retention. It renders itself as an item list and caches the contents of the data source in an ArrayList . This control defines two helper functions that should be useful for any implementer of data-bound controls. The first is GetResolvedDataSource , which takes the data source as an object and returns a reference to an IEnumerable interface. This function accounts for the fact that the data source may be a collection class, an IDataReader , a DataView , a DataTable , or a DataSet , and returns the enumeration interface on the correct element of the data source. The second helper function is GetDataItem , which takes an item pointed to by an enumerator and indexes it with the m_DataTextField value if is a rowset, or simply returns the object as a string if not. This is necessary to accommodate items stored in simple collections and items stored in tabular data sets. Listing 8-38 A Data-Bound Control
public class DataBoundControl : Control
{
private ArrayList _cachedData = new ArrayList();
private object _dataSource;
private string _dataTextField;
private string _dataValueField;
public string DataTextField
{
get { return _dataTextField; }
set { _dataTextField = value; }
}
public string DataValueField
{
get { return _dataValueField; }
set { _dataValueField = value; }
}
public override object DataSource
{
get {return _dataSource;}
set {_dataSource = value;}
}
public IEnumerable GetResolvedDataSource(object ds)
{
if (ds is IEnumerable)
return (IEnumerable)ds;
else if (ds is DataTable)
return (IEnumerable)(((DataTable)ds).DefaultView);
else if (ds is DataSet)
{
DataView dv = ((DataSet)ds).Tables[0].DefaultView;
return (IEnumerable)dv;
}
else if (ds is IList)
return (IEnumerable)((IList)ds);
else
return null;
}
protected string GetDataItem(object item)
{
string ret;
if (item is DataRowView)
{
DataRowView drv = (DataRowView)item;
ret = drv[_dataValueField].ToString();
}
else if (item is DbDataRecord)
{
DbDataRecord ddr = (DbDataRecord)item;
ret = ddr[_dataValueField].ToString();
}
else
ret = item.ToString();
return ret;
}
protected override void OnDataBinding(EventArgs e)
{
base.OnDataBinding(e);
if (DataSource != null)
{
IEnumerable ds = GetResolvedDataSource(_dataSource);
IEnumerator dataEnum = ds.GetEnumerator();
while (dataEnum.MoveNext())
_cachedData.Add(GetDataItem(dataEnum.Current));
}
}
protected override void Render(HtmlTextWriter htw)
{
htw.RenderBeginTag(HtmlTextWriterTag.Ul); // <ul>
foreach (string s in _cachedData)
{
htw.RenderBeginTag(HtmlTextWriterTag.Li); // <li>
htw.Write(s);
htw.RenderEndTag(); // </li>
}
htw.RenderEndTag(); // </ul>
}
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
// Load State from the array of objects that
// was saved in SaveViewState
object[] vState = (object[])savedState;
if (vState[0] != null)
base.LoadViewState(vState[0]);
if (vState[1] != null)
_cachedData = (ArrayList)vState[1];
}
}
protected override object SaveViewState()
{
object[] vState = new object[2];
vState[0] = base.SaveViewState();
vState[1] = _cachedData;
return vState;
}
}
|