The process of Unbinding a control is very similar – the same process in reverse as shown in Listing 4.
Listing 4 – Unbinding data from the control back into the data source.
public static void ControlUnbindData(Page WebPage,
IwwWebDataControl ActiveControl) {
string BindingSourceObject = ActiveControl.BindingSourceObject;
string BindingSourceProperty = ActiveControl.BindingSourceProperty;
string BindingProperty = ActiveControl.BindingProperty;
if (BindingSourceObject == null || BindingSourceObject.Length == 0 ||
BindingSourceProperty == null || BindingSourceProperty.Length == 0)
return;
object loBindingSource = null;
if (BindingSourceObject == "this" || BindingSourceObject.ToLower() == "me")
loBindingSource = WebPage;
else
loBindingSource = wwUtils.GetPropertyEx(WebPage,BindingSourceObject);
if (loBindingSource == null)
throw(new Exception("Invalid BindingSource"));
// Retrieve the new value from the control
object loValue = wwUtils.GetPropertyEx(ActiveControl,BindingProperty);
// Try to retrieve the type of the BindingSourceProperty
string lcBindingSourceType;
string lcDataColumn = null;
string lcDataTable = null;
// *** figure out the type of the binding source by reading the value
if (loBindingSource is System.Data.DataSet) {
// *** Split out the datatable and column names
int lnAt = BindingSourceProperty.IndexOf(".");
lcDataTable = BindingSourceProperty.Substring(0,lnAt);
lcDataColumn = BindingSourceProperty.Substring(lnAt+1);
DataSet Ds = (DataSet) loBindingSource;
lcBindingSourceType =
Ds.Tables[lcDataTable].Columns[lcDataColumn].DataType.Name;
}
else if(loBindingSource is System.Data.DataRow) {
DataRow Dr = (DataRow) loBindingSource;
lcBindingSourceType = Dr.Table.Columns[BindingSourceProperty].DataType.Name;
}
else if (loBindingSource is System.Data.DataTable) {
DataTable dt = (DataTable) loBindingSource;
lcBindingSourceType = dt.Columns[BindingSourceProperty].DataType.Name;
}
else {
// *** It's an object property or field - get it
MemberInfo[] loInfo =
loBindingSource.GetType().GetMember(BindingSourceProperty,
wwUtils.MemberAccess);
if (loInfo[0].MemberType == MemberTypes.Field) {
FieldInfo loField = (FieldInfo) loInfo[0];
lcBindingSourceType = loField.FieldType.Name;
}
else {
PropertyInfo loField = (PropertyInfo) loInfo[0];
lcBindingSourceType = loField.PropertyType.Name;
}
}
// *** Convert the control value to the proper type
object loAssignedValue;
if ( lcBindingSourceType == "String")
loAssignedValue = loValue;
else if (lcBindingSourceType == "Int16")
loAssignedValue = Int16.Parse( (string) loValue, NumberStyles.Integer );
else if (lcBindingSourceType == "Int32")
loAssignedValue = Int32.Parse( (string) loValue, NumberStyles.Integer );
else if (lcBindingSourceType == "Int64")
loAssignedValue = Int32.Parse ( (string) loValue, NumberStyles.Integer)
else if (lcBindingSourceType == "Byte")
loAssignedValue = Convert.ToByte(loValue);
else if (lcBindingSourceType == "Decimal")
loAssignedValue = Decimal.Parse( (string) loValue,NumberStyles.Any);
else if (lcBindingSourceType == "Double")
loAssignedValue = Double.Parse( (string) loValue,NumberStyles.Any);
else if (lcBindingSourceType == "Boolean") {
loAssignedValue = loValue;
else if (lcBindingSourceType == "DateTime")
loAssignedValue = Convert.ToDateTime(loValue);
else // Not HANDLED!!!
throw(new Exception("Field Type not Handled by Data unbinding"));
/// Write the value back to the underlying object/data item
if (loBindingSource is System.Data.DataSet) {
DataSet Ds = (DataSet) loBindingSource;
Ds.Tables[lcDataTable].Rows[0][lcDataColumn] = loAssignedValue;
}
else if(loBindingSource is System.Data.DataRow) {
DataRow Dr = (DataRow) loBindingSource;
Dr[BindingSourceProperty] = loAssignedValue;
}
else if(loBindingSource is System.Data.DataTable) {
DataTable dt = (DataTable) loBindingSource;
dt.Rows[0][BindingSourceProperty] = loAssignedValue;
}
else if(loBindingSource is System.Data.DataView) {
DataView dv = (DataView) loBindingSource;
dv[0][BindingSourceProperty] = loAssignedValue;
}
else
wwUtils.SetPropertyEx(loBindingSource,BindingSourceProperty,loAssignedValue);
}
This code starts by retrieving the Control Source object and the value contained in the control held by the BindingProperty
field. This is most likely the Text field, but could be anything the user specified, such as Checked
for a CheckBox
or SelectedValue
for a ListBox
or DropDownList
. The ControlSource
is also queried for its type by retrieving the current value. The type is needed so we can properly convert the type back into the type that the control source expects. This involves String to type conversion including the proper type parsing so you can use things like currency symbols for decimal values etc. The Parse method is quite powerful for this sort of stuff. Finally once the value has been converted Reflection is used one more time to set the value into the binding source field based on the type of object we're dealing with. DataSets
, Tables
and Rows
write to the Field
collection, while objects and properties are written natively to the appropriate member.
These two methods are the core of the binding operations and they are fully self contained to bind back controls. This process lets us bind individual controls. These methods are then called by each control's BindData()
and UnbindData()
methods respectively as shown in Listing 2.
The next thing we need to do is bind all the controls on a form so we don't have to individually bind them. This is pretty easy in concept. We know all of our controls implement the IwwWebDataControl
interface, so it's fairly easy to walk the Web form's Controls collection (and child collections) and look for any controls that implement the IwwWebDataControl
interface and then call the BindData()
method. Listings 5 and 6 show the FormBindData() and FormUnbindData() methods that do just that.
Listing 5 – Binding all controls on a form
static void FormBindData(Control Container, Page WebForm) {
// *** Drill through each control on the form
foreach( Control loControl in Container.Controls) {
// ** Recursively call down into any containers
if (loControl.Controls.Count > 0)
wwWebDataHelper.FormBindData(loControl, WebForm);
// ** only work on those that support interface
if (loControl is IwwWebDataControl ) {
IwwWebDataControl control = (IwwWebDataControl) loControl;
try {
//*** Call the BindData method on the control
control.GetType().GetMethod("BindData",
wwUtils.MemberAccess).Invoke(control,
new object[1] { WebForm } );
}
catch(Exception) {
// *** Display Error info
try {
control.Text = "** Field binding Error **";
}
catch(Exception) {;}
}
}
}
}
As you can see FormBindData()
runs through the controls collection and checks for the IwwWebControl
interface. Note that this method is recursive and calls itself if it finds a container and drills into them. This makes sure the entire form databinds. When a control is found the BindData()
method of the control is called dynamically using Reflection.
When an error occurs the Text of the control is set to Field binding error so you can immediately see the error without throwing an exception on the page. This is handy as you don't get errors individually. This is likely to be a developer error – not a runtime error so this handling is actually preferable.
The unbinding works in a similar fashion as shown in Figure 6.
Listing 6 – Unbinding all controls into their datasource
public static BindingError[] FormUnbindData(Page WebForm)
{
BindingError[] Errors = null;
FormUnbindData(WebForm,WebForm,ref Errors);
return Errors;
}
static BindingError[] FormUnbindData(Control Container, Page WebForm,
ref BindingError[] Errors) {
// *** Drill through each of the controls
foreach( Control loControl in Container.Controls) {
// ** Recursively call down into containers
if (loControl.Controls.Count > 0)
FormUnbindData(loControl, WebForm,ref Errors);
if (loControl is IwwWebDataControl ) {
IwwWebDataControl control = (IwwWebDataControl) loControl;
try {
// *** Call the UnbindData method on the control
control.GetType().GetMethod("UnBindData",
wwUtils.MemberAccess).Invoke(control,
new object[1] { WebForm } );
}
catch(Exception ex) {
// *** Display Error info
try
{
BindingError loError = new BindingError();
control.BindingErrorMessage = loError.Message;
// … more error handling code here
if (Errors == null) {
Errors = new BindingError[1];
Errors[0] = loError;
}
else {
// *** Resize the array and assign Error
int lnSize = Errors.GetLength(0);
Array loTemp =
Array.CreateInstance(typeof(BindingError),
lnSize + 1);
Errors.CopyTo(loTemp,0);
loTemp.SetValue(loError,lnSize);
Errors = (BindingError[]) loTemp;
}
}
catch(Exception) {;} // ignore additional exceptions
}
}
}
return Errors;
}
This code is very similar to the FormBindData()
method. The difference here is that we call the UnbindData
method and that we deal with errors on unbinding differently. It's much more likely that something goes wrong with binding back then binding as users can enter just about anything into a textbox like characters for numeric data or non data formats for date fields. This scenario throws an exception in the control's bindback code which has handled here.
Error Display
This method creates an array of BindingError
objects which contains information about the error. You can configure custom binding error messages by setting a binding error message on the control (see Figure 4). Otherwise the following code assigns a generic error message to the property with this code (omitted in Figure 6):
Listing 7 – Assigning binding error messages when unbinding
BindingError loError = new BindingError();
if (wwUtils.Empty(control.BindingErrorMessage))
{
if ( control.UserFieldName != "")
loError.Message = "Invalid format for " + control.UserFieldName;
else
loError.Message = "Invalid format for " + loControl.ID.Replace("txt","");
}
else
loError.Message = control.BindingErrorMessage;
// *** Assign the error message to the control
// *** this will cause the control to render it
control.BindingErrorMessage = loError.Message;
loError.ErrorMsg = ex.Message;
loError.Source = ex.Source;
loError.StackTrace = ex.StackTrace;
loError.ObjectName = loControl.ID;
if (Errors == null)
{
Errors = new BindingError[1];
Errors[0] = loError;
}
else
{
// *** Resize the array and assign Error
int lnSize = Errors.GetLength(0);
Array loTemp = Array.CreateInstance(typeof(BindingError),lnSize + 1);
Errors.CopyTo(loTemp,0);
loTemp.SetValue(loError,lnSize);
Errors = (BindingError[]) loTemp;
}
This array of binding errors if any is returned from the Unbind operation. A couple of helper methods exist to turn the array into HTML. The code for the Inventory example we saw earlier then looks something like this:
…
BindingError[] Errors = wwWebDataHelper.FormUnbindData(this);
if (Errors != null)
{
this.ShowErrorMessage( wwWebDataHelper.BindingErrorsToHtml(Errors) );
return;
}
if (!Inventory.Save())
…
In addition each of the control contains some custom code to display error information as shown in Figure 5.
Figure 5 – Binding errors can be automatically flagged and converted into an HTML display (top).
The code that accomplishes that has a few dependencies that I've not had time to abstract away at this point so some of this is hardcoded into the control:
protected override void Render(HtmlTextWriter writer)
{
// *** Write out the existing control code
base.Render (writer);
// *** now append an error icon and ‘tooltip’
if (this.BindingErrorMessage != null && this.BindingErrorMessage != "" )
writer.Write(" <img src='images/warning.gif' alt='" +
this.BindingErrorMessage + "')'>");
}
As you can see it's quite easy to add additional output to controls. This extensibility model is just very flexible and easy to work with.
Comments