Custom DB Web Controls
Ik laat in dit artikel zien hoe we Custom ASP.NET DBWeb Web Controls kunnen maken met Delphi 2006. Dit artikel bevat informatie die ik ook tijdens mijn sessie op het Software Developer Event van 15 september zal presenteren.
DB Web Controls
ASP.NET DB Web controls zijn te vinden in de Enterprise editie van Delphi for .NET (bijvoorbeeld Delphi 2006, wat ik in dit artikel zal gebruiken). Een ASP.NET DB Web control is een beetje te vergelijken met een VCL data-aware control. Via een DataSource kan de visuele component zich voorzien van data, door een tablename en optioneel fieldname property. En waar we vanaf Delphi 1.0 met behulp van een TDataSource de data-aware componenten zoals TDBGrid en TDBEdit kunnen voorzien van data, zo kunnen we in Delphi for .NET met een DBWebDataSource de DBWeb controls zoals de DBWebGrid en DBWebTextBox voorzien van data.
Custom DB Web Control
Voor alle .NET custom controls moeten we eerst een Delphi for .NET Package project starten om uiteindelijk tot een .NET Assembly te komen. Speciaal voor custom DB Web controls heeft Delphi een DBWeb Custom Control wizard, te vinden in de New ASP.NET Files category in de Object Repository (zei figuur 1).

Fig. 1: Object Repository
Hiermee krijg je de New DBWeb Control Wizard, waar je behalve de naam van de nieuwe control, ook een aantal keuzemogelijkheden hebt, zoals in figuur 2 te zien is. De default keuze geeft je een nieuwe DB Web control die aan een hele DataTable bindt. Dat is iets dat je bij een DataGrid bijvoorbeeld ziet. Het alternatief is een DB Web control die aan een DataColumn (alsmede een DataTable) bindt, zoals een TextBox control. De laatste keuze is ook mogelijk in combinatie met een loopup link (zoals in een DBLookupCombobox).

Fig. 2: New DBWeb Control Wizard
Bij een keuze voor de DataTable zal het nieuwe control een DBDataSource en TableName property hebben, en bij keuze voor de DataColumn zal er behalve de DBDataSource en TableName ook een ColumnName property zijn. Dit wordt afgedwongen door respectievelijk het IDBWebDataLink en IDBWebColumnLink interface uit de Borland.Data.Web namespace.
Tijdens het SDE van 15 september zal ik een aantel verschillende nieuwe DB Web controls bouwen, waaronder een DBWebDataTableInfo die de DataTable binding doet, en een DBWebSpinEdit die de DataColumn binding doet. De DBWebSpinEdit kan overigens op verschillende manieren aangepakt worden, en legt nog een klein probleem bloot met de door de New DB Web Control Wizard gegenereerde code, maar daarover meer tijdens het SDE. Het voorproefje in dit artikel gaat slechts over de DBWebDataTableInfo.
DBWebDataTableInfo
Voor de DBWebDataTableInfo moeten we dus de default keuze op Bind to DataTable laten staan. De dan gegenereerde unit bevat de class DBWebDataTableInfo, afgeleid van System.Web.UI.WebControls.WebControl, met de interfaces IPostBackDataHandler en IDBWebDataLink. De IDBWebDataLink zorgt dat we de DBDataSource en TableName properties tot onze beschikking hebben.
Delegate
Als we de gegenereerde source code van de DBWebDataTableInfo bekijken, dan valt al snel op dat er een FMyDelegate veld in voorkomt, in commentaar. Het idee is dat we of deze default FMyDelegate gebruiken, of zelf een veld definiëren dat de waarde alsmede de (re)presentatie van de waarde voor zijn rekening neemt. De FMyDelegate is standaard een TextBox control, maar in ons geval wil ik eigenlijk liever een (read-only) Label gebruiken. Dus alle plaatsen waar de FMyDelegate staat moeten we die uit het commentaar halen, maar wel van een TextBox in een Label veranderen.
In de class declaratie van de DBWebDataTableInfo is dat als volgt:
strict private FMyDelegate: &Label; // BS
Vervolgems moeten we in de constructor het FMyDelegate veld als type Label aanmaken, als volgt:
constructor DBWebDataTableInfo.Create;
begin
inherited;
FMyDelegate := &Label.Create; // BS
FDataLink := DBWebDataLink.Create(self);
// Standard DB Web interface should always be defined
FIDataLink := (FDataLink as IDBWebDataLink);
end;
En in GetText moeten we alleen het gebruik van FMyDelegate uit het commentaar halen.
function DBWebDataTableInfo.GetText: string;
var
sw: StringWriter;
tw: HtmlTextWriter;
begin
sw := StringWriter.Create;
tw := HtmlTextWriter.Create(sw);
DataBind();
FMyDelegate.RenderControl(tw); // BS
Result := sw.ToString();
end;
De implementatie van GetText verzorgt meteen de HTML rendering voor ons. Straks gaan we daar in de DataBind een stukje extra informatie aan toevoegen, maar eerst nog een laatste stap die noodzakelijk is voor read-only DB Web controls. We moeten voor read-only controls de aanroep naar de RegisterHiddenField in de OnPreRender method in commentaar zetten of helemaal verwijderen. In ons geval is de DBWebDataTableInfo inderdaad read-only, dus moeten we de OnPreRender als volgt aanpassen:
procedure DBWebDataTableInfo.OnPreRender(args: EventArgs);
begin
inherited OnPreRender(args);
// You need to register the hidden field to identify the key for
// read-write controls only. Remove this call if control is read-only.
// Page.RegisterHiddenField(DBWebDataSource.IdentPrefix +
// DBWebConst.Splitter + IDataLink.TableName, self.ID);
DataBind;
end;
Daarna komen we uiteindelijk bij de DataBind aan, waar de inhoud van het DB Web control wordt weggeschreven naar de FMyDelegate’s Text property. In de DataBind method moeten we de data verzorgen, maar de HTML opmaak wordt over het algemeen in de GetText method gedaan. In dit eenvoudige voorbeeld wijk ik af van die regel, en stop ik de HTML opmaak in de DataBind (het is toch maar een read-only control, en op deze manier is de opmaak sneller gedaan).
DataBind – Analysing DBDataSource
In de DataBind method kunnen we de DBDataSource property analyseren, en daar dan het resultaat van weergeven. Allereerst moeten we controleren of de DBDataSource wel assigned is, wat bijvoorbeeld nog niet het geval is als de DBWebDataTableInfo net op een form is gezet. Alleen als de DBWebDataSource property een waarde heeft, kunnen we er gebruik van maken om de GetDataSourceName method aan te roepen. Dit geeft ons de naam van de DBWebDataSource zelf. Daarnaast kunnen we de GetDataSource method aanroepen, die niet de DBWebDataSource, maar juist de DataSet, DataTable of DataView teruggeeft waar de DBWebDataSource mee verbonden is.
Als we eenmaal de DataSource hebben, dan kunnen we kijken of er DataTables in zitten, en daarvan dan de kolommen (de meta-data), waarbij we van iedere kolom de ColumnName, DataType en de MaxLength waarde kunnen weergeven. Een soort analyse van de data structuur binnen de tabellen dus.
procedure DBWebDataTableInfo.DataBind;
var
DS: DataSet;
DT: DataTable;
DC: DataColumn;
begin
try
// DataBind whatever properties you require wherever you see
// See the C# source code for DBWebNavigator and DBWebLabeledTextBox
// for additional examples.
// Set value to blank if not data bound
// call to inherited triggers setting of DBDataSource.
inherited DataBind;
if Assigned(FIDataLink.DBDataSource) then
try
FMyDelegate.Text :=
FIDataLink.DBDataSource.GetDataSourceName(Page) +
'
';
if FIDataLink.DBDataSource.GetDataSource(Page) is DataSet then
begin
DS := FIDataLink.DBDataSource.GetDataSource(Page) as DataSet;
for DT in DS.Tables do
begin
FMyDelegate.Text := FMyDelegate.Text +
'' + DT.TableName + ' |
';
for DC in DT.Columns do
if DC.MaxLength > -1 then
FMyDelegate.Text := FMyDelegate.Text +
'' + DC.ColumnName + ' | ' + DC.DataType.ToString + '[' + DC.MaxLength.ToString + '] |
'
else
FMyDelegate.Text := FMyDelegate.Text +
'' + DC.ColumnName + ' | ' + DC.DataType.ToString + ' |
'
end
end
else
begin
if FIDataLink.DBDataSource.GetDataSource(Page) is DataView then
DT := (FIDataLink.DBDataSource.GetDataSource(Page) as
DataView).Table
else
if FIDataLink.DBDataSource.GetDataSource(Page) is DataTable then
DT := FIDataLink.DBDataSource.GetDataSource(Page) as DataTable
else DT := nil;
if Assigned(DT) then // DataTable
begin
FMyDelegate.Text := FMyDelegate.Text +
'' + DT.TableName + ' |
';
for DC in DT.Columns do
if DC.MaxLength > -1 then
FMyDelegate.Text := FMyDelegate.Text +
'' + DC.ColumnName + ' | ' + DC.DataType.ToString + '[' + DC.MaxLength.ToString + '] |
'
else
FMyDelegate.Text := FMyDelegate.Text +
'' + DC.ColumnName + ' | ' + DC.DataType.ToString + ' |
'
end
end;
FMyDelegate.Text := FMyDelegate.Text + '
'
except
on E: Exception do
FMyDelegate.Text := FMyDelegate.Text +
'
' + E.Message + '