Inleiding
Webparts zijn een nieuw soort besturingselementen in webapplicaties, geïntroduceerd door Microsoft in ASP.Net sinds versie 2.0. Webparts zijn vooral bekend vanwege de toepassing binnen Sharepoint en MOSS. In deze twee platformen zijn webparts één van de mogelijkheden om eenvoudig toegang te krijgen tot legacy systemen.
Echter ook in maatwerk ASP.Net applicaties zijn webparts “fun”. In een moderne webtoepassing is het meer en meer gebruikelijk dat gebruikers een eigen indeling kunnen maken van hun “eigen pagina”. Kijk naar sites als hyves.nl en de verschillende elementen lijken verdacht veel op webparts.
In dit artikel gaan we in op een aantal basisaspecten van webparts en behandelen we naast de opzet ervan een manier om webparts te genereren op basis van een domeinmodel. De webparts zijn ontwikkeld in C#. Dat is voor mij niet mijn dagelijkse programmeertaal (dat zijn Vulcan.Net en VB.Net). Reden om hiervoor te kiezen is het feit dat binnen Sharepoint installatie van webparts die niet geschreven zijn in C#, lastig is. De webparts zijn op deze wijze in een handomdraai geschikt te maken voor Sharepoint.
Basispagina
Om webparts mogelijk te maken in een webpagina is het van belang een aantal extra besturingselementen op te nemen in een standaard pagina. In de onderstaande afbeelding een voorbeeld van een pagina met een aantal besturingselementen in ontwerpmodus.

Fig. 1
Opvallend is dat de pagina een aantal zones heeft waarin een webparts geplaatst zijn. Deze zones bieden de ontwikkelaar de mogelijkheid om de gebruiker van de toepassing een standaard indeling te geven. Door verschillende templates aan te bieden kun je gebruikers op eenvoudige wijze behulpzaam zijn bij het werken met zones.
Standaard zijn een webpart-zone en een webpart-manager de minimum vereiste voor een pagina. De webpart-manager zorgt als een soort hub voor al het gedrag van de webpart-zones en de webpart-besturingselementen. Op een pagina met webparts moet één webpart-manager voorkomen.
De webpart-zone is een container dat gedrag toevoegt aan de webparts die binnen deze zone voorkomen. Zo kun je een zone maken waarop men een aantal ‘samenvatting’-webparts kan plaatsen. In onderstaande code een voorbeeld van een webpart-manager en een webpart-zone:
<asp:WebPartManager id="WebPartManagerDRG"
runat="server">
</asp:WebPartManager>
<asp:WebPartZone id="WebPartZoneLinks"
runat="server"
EmptyZonetext="Voeg hier een webpart toe"
Height="100px"
Font-Names="Verdana"
Padding="1"
ShowTitleIcons="true"
AllowLayoutChange="true"
EditVerb-Visible="true"
EditVerb-Enabled="true"
WebPartVerbRenderMode="TitleBar"
LayoutOrientation="Vertical" >
<EditVerb text="Bewerken"
Description="Pas de instellingen aan" />
<DeleteVerb text="Verwijder"
Description="Verwijder het element" />
<MinimizeVerb text="Minimaliseren"
Description="Verklein element"/>
<ConnectVerb text="Verbinden"
Description="Verbind element" />
<RestoreVerb text="Maximaal"
Description="Maximaliseer element" />
</asp:WebPartZone>
Op basis van de eigenschappen is een zone-part op maat te maken met eigen opmaak en meldingen van de diverse opdrachten en gebeurtenissen. Aardig is dat ook de woorden en toelichtingen bij de verschillende opdrachten aangepast kunnen worden, zodat deze specifiek gemaakt kunnen worden voor de eigen toepassing.
In de voorbeeldtoepassing is de pagina default2.aspx te vinden met daarin een compleet uitgewerkte pagina met drie zones.
Naast de zones is het wenselijk dat een gebruiker van de toepassing de pagina kan gebruiken (bekijken) maar ook kan ontwerpen en beheren. Met de optie ontwerpen kun je webparts van de ene zone naar de andere verplaatsen om zo een logische indeling te krijgen.
Bij bewerken komt er in het menu van een webpart de optie ‘bewerken’ te voorschijn. Hiermee kan een aantal instellingen van het webpart veranderd worden. In figuur 2 zie je een voorbeeld van de pagina.

Fig. 2
Als laatste is er de catalogus-modus. Hiermee is het mogelijk om webparts vanuit een lijst van beschikbare webparts toe te voegen aan één van de zones.
Voor al deze verschillende pagina-functionaliteiten zijn zones aanwezig. In onderstaande code is één voorbeeld uitgewerkt; de andere zones zijn opgenomen in de voorbeeldapplicatie.
<asp:CatalogZone
runat="server" id="CatalogZoneDRG"
Height ="200px" Width="400px"
BackColor="#F7F6F3"
BorderColor="#CCCCCC" BorderWidth="1px"
Font-Names="Verdana" Padding="6">
<ZoneTemplate>
<asp:DeclarativeCatalogPart
runat="server" id="catalogDrg">
<WebPartsTemplate>
<DRGwebpart:DRGMedewerker
id="DRGMdw" runat="server" title="Mdw" />
<DRGwebpart:DRGProject
id="DRGProj" runat="server" title="Project"/>
<DRGwebpart:DRGOrganisatie
id="DRGOrg" runat="server" title="Org." />
<DRGwebpart:DRGPrs
id="DRGPrs" runat="server" title="Prs" />
</WebPartsTemplate>
</asp:DeclarativeCatalogPart>
</ZoneTemplate>
</asp:CatalogZone>
In het voorbeeld is te zien hoe van de catalogus verschillende eigenschappen in te stellen zijn voor de eigen toepassing. Daarnaast is in deze zone de lijst van beschikbare webparts opgenomen.
Instellen van de verschillende scherm-modi is mogelijk door de webpart-manager aan te passen. In de voorbeeldtoepassing wordt dit eenvoudig opgelost door het scherm te openen met een bepaalde querystring-.
public partial class Default2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
switch (Request.QueryString["instellen"])
{
case "DesignDisplayMode":
{
WebPartManagerDRG.DisplayMode =
WebPartManager.DesignDisplayMode;
break;
};
case "BrowseDisplayMode":
{
WebPartManagerDRG.DisplayMode =
WebPartManager.BrowseDisplayMode;
break;
};
case "EditDisplayMode":
{
WebPartManagerDRG.DisplayMode =
WebPartManager.EditDisplayMode;
break;
};
case "CatalogDisplayMode":
{
WebPartManagerDRG.DisplayMode =
WebPartManager.CatalogDisplayMode;
break;
};
case "ConnectDisplayMode":
{
WebPartManagerDRG.DisplayMode =
WebPartManager.ConnectDisplayMode;
break;
};
};
};
}
}
Basis-webpart
De voorbeeldtoepassing is zodanig opgezet dat de specifieke webparts zo weinig mogelijk gedrag bevatten, nl. door het instellen van een aantal eigenschappen en het definiëren van de besturingselementen die in het webpart getoond moeten worden. In het eerste codevoorbeeld is de definitie van de class en het instellen van een aantal eigenschappen opgenomen:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Specialized;
using System.Collections;
namespace DRG.Webparts.Controls
{
public class DRGOrganisatie : DRGAbstractWebpart
{
public DRGOrganisatie()
{
this.insertcommand = "INSERT INTO [Organisatie]
( [bezoekadres],[bezoekplaats],
[bezoekpostcode],[email],
[organisatie_naam],[organisatie_soort],
[postadres],[postplaats],[postpostcode],
[telefoon],[website] )
VALUES (
'#bezoekadres#','#bezoekplaats#',
'#bezoekpostcode#','#email#',
'#organisatie_naam#','#organisatie_soort#',
'#postadres#','#postplaats#','#postpostcode#',
'#telefoon#','#website#' )";
this.updatecommand = "UPDATE [Organisatie] SET
[bezoekadres] = '#bezoekadres#',
[bezoekplaats] = '#bezoekplaats#',
[bezoekpostcode] = '#bezoekpostcode#',
[email] = '#email#',
[organisatie_naam] = '#organisatie_naam#',
[organisatie_soort] = '#organisatie_soort#',
[postadres] = '#postadres#',
[postplaats] = '#postplaats#',
[postpostcode] = '#postpostcode#',
[telefoon] = '#telefoon#',
[website] = '#website#'
WHERE [organisatie_id] = #organisatie_id# ";
this.deletecommand = "DELETE FROM [Organisatie]
WHERE [organisatie_id] = #organisatie_id# ";
this.selectcommand = "SELECT
[bezoekadres], [bezoekplaats],
[bezoekpostcode], [email],
[organisatie_naam], [organisatie_soort],
[postadres], [postplaats], [postpostcode],
[telefoon], [website]
FROM [Organisatie]
WHERE [organisatie_id] = #organisatie_id# ";
}
}
Het voorbeeld laat zien dat het webpart overerft van DRGAbstractWebpart. In deze abstracte klasse is de generieke functionaliteit opgenomen, waarover in de volgende paragraaf meer. In de constructor wordt gedefinieerd wat de verschillende SQL-statements zijn die bij dit specifieke webpart horen. In een volgende versie van het webpart schreeuwt dit vanzelf om een XML file dat deze instellingen opslaat en inleest in het webpart.
In de CreateChildControls-methode van het webpart worden de verschillende besturingselementen gedefinieerd voor dit webpart. In onderstaande sourcecode een voorbeeld (in de applicatie zijn een viertal webparts in detail uitgewerkt):
protected override void CreateChildControls()
{
NameValueCollection para = new NameValueCollection();
DRGHelper objHelper;
objHelper = new DRGHelper();
this.creerStandaardControls();
DropDownList organisatie_id =
ControlFactoryHelper.CreateDropDownList(
this.Controls, "organisatie_id", "", 400);
objHelper.Sql2ListControl(
"SELECT ORGANISATIE.organisatie_id as valuecolumn
, ORGANISATIE.organisatie_naam as displaycolumn
FROM ORGANISATIE UNION
SELECT 0, '--Maak uw keuze--'
FROM WebDefault ORDER BY 2",
organisatie_id,
"valuecolumn", "displaycolumn", para);
ControlFactoryHelper.CreateTextBox
(this.Controls, "bezoekplaats", "",500);
ControlFactoryHelper.CreateTextBox
(this.Controls, "postadres", "",500);
ControlFactoryHelper.CreateTextBox
(this.Controls, "postpostcode", "",100);
…
this.toevoegenControlProperty(new
DRGControlProperty("organisatie_id",
"Organisatie", false, false, "Supply"));
this.toevoegenControlProperty(new
DRGControlProperty("organisatie_naam",
"Organisatie naam", false, false, "Modify"));
this.toevoegenControlProperty(new
DRGControlProperty("bezoekadres",
"Bezoekadres", true, false, "Modify"));
ChildControlsCreated = true;
}
Eerst worden de verschillende besturingselementen aangemaakt; hiervoor is een helper klasse beschikbaar, waarover later meer. Daarna worden voor deze controls elementen van het type DRGControlProperty toegevoegd aan een arraylist binnen de abstracte klasse. Deze helper klasse wordt in de generieke klassen gebruik om gedrag te activeren wanneer gewenst.
Generieke en helper-classes
De eerste helper-klasse is een factory voor besturingselementen welke ervoor zorgt dat een besturingselement op de juiste wijze wordt gecreëerd. Als voorbeeld is de methode opgenomen die een multline-edit aanmaakt:
static public TextBox CreateMultiLineTextBox(
System.Web.UI.ControlCollection controls,
String ID, String tooltip, Int16 width)
{
TextBox control = new TextBox();
control.ID = ID;
control.ToolTip = tooltip;
control.Width = width;
control.Height = (width / 3);
control.TextMode = TextBoxMode.MultiLine;
controls.Add(control);
return control;
}
De code is rechttoe rechtaan: er wordt een besturingselement van het juiste type aangemaakt en vervolgens wordt een aantal eigenschappen ingesteld en wordt deze toegevoegd aan de controls-collection van het webpart. Als returnwaarde wordt de aangemaakt control gebruikt. Dit maakt het mogelijk om specifiek gedrag aan een bepaald control toe te voegen. In het voorbeeld van het basis besturingselement zie je hoe dit wordt gedaan bij een keuzelijst. Hierbij maakt de ControlFactory een keuzelijst aan en vervolgens zorgt de specifieke code ervoor dat het juiste SELECT-statement wordt gekoppeld aan de juiste keuzelijst.
Een andere routine die in de ControlFactory-klasse is opgenomen, handelt af dat een dataset wordt omgezet naar de waardes in één of meer besturingselementen. In het codevoorbeeld hieronder is een deel van de routine opgenomen; in de voorbeeldtoepassing is de complete code te vinden.
static public void Dataset2Controls(DataSet ds,
System.Web.UI.ControlCollection controls)
{
String varType;
if (ds.Tables.Count == 1)
{
DataTable objTable = ds.Tables[0];
if (ds.Tables[0].Rows.Count > 0)
{
DataRow objRow = objTable.Rows[0];
foreach (Control webcontrol in controls)
{
varType =
webcontrol.GetType().ToString().ToUpper();
switch (varType)
{
case "SYSTEM.WEB.UI.WEBCONTROLS.TEXTBOX":
{
TextBox objTB = (TextBox)webcontrol;
if (ColumnNameExists(
objTable.Columns, objTB.ID))
{
objTB.Text = objRow[objTB.ID].ToString();
};
break;
};
case "SYSTEM.WEB.UI.WEBCONTROLS.DROPDOWNLIST":
{
DropDownList objTB =
(DropDownList)webcontrol;
if (ColumnNameExists(
objTable.Columns, objTB.ID))
{
objTB.Text = objRow[objTB.ID].ToString();
};
break;
};
…
};
};
};
};
}
Via de switch-case wordt de waarde van een rij in een datatable gekoppeld aan een waarde van besturingselement. De foreach loop zorgt ervoor dat alle besturingselementen gecontroleerd worden. Het kan natuurlijk zijn, dan een besturingselement niet is gekoppeld aan een waarde uit de database. In dat geval zal de waarde niet gekoppeld worden, omdat de ColumnNameExists functie niet afgaat.
In de onderstaande code is een voorbeeld van een methode te zien die in een andere helper-klasse geïmplementeerd wordt.
public void Sql2ListControl(string sql,
ListControl control, string valueField,
string displayField, NameValueCollection colPara)
{
DataSet objDS;
sql = this.verwerkParameters(sql, colPara);
objDS = this.Statement2DataSet(sql);
if (objDS.Tables.Count > 0)
{
control.DataSource = objDS;
control.DataValueField = valueField;
control.DataTextField = displayField;
control.DataBind();
};
}
De code laat zien dat een SQL-statement wordt omgezet naar een dataset en dat de datatable afkomstig vanuit de aangemaakte dataset gebruikt wordt als vulling van een keuzelijst besturingselement.
Reden om dit soort functionaliteit te plaatsen in een abstracte klasse is dat de toestand van een eigenschap kan veranderen namelijk via de eigenschap errorMelding.
Naast het gebruik van een helper-klasse is het werken met overerving in het geval van webparts erg handig. Onder een helper-klasse versta ik trouwens een klasse met alleen maar static methods zodat die niet geïnstantieerd kan worden tot een object. Eigenlijk kan een helper-klasse gezien worden als een half object. Een object bestaat normaal uit status en gedrag; een statische klasse bestaat enkel uit gedrag (dat de status van andere objecten kan aanpassen).
Is wel de combinatie van status en gedrag gewenst, dan is het gebruik van overerving vaak een middel om hergebruik te introduceren. In figuur 3 hieronder is te zien hoe het overerving en statische klassen gecombineerd gebruikt kunnen worden.

Fig. 3
In de abstracte klasse kan generieke code geplaatst worden welke de status van het webpart of de status van de besturingselementen binnen het webpart aanpassen. Ook kun je in een abstracte klasse events plaatsen die op generieke wijze reageren op acties van de gebruikers. Als voorbeeld de code hieronder:
protected void verwerk_supply(
Object sender, EventArgs e)
{
NameValueCollection para = new NameValueCollection();
String sql = "";
DRGHelper objHelper = new DRGHelper();
DataSet ds;
para = ControlFactoryHelper.Controls2Collection(
this.Controls);
if (this.soortMutatie.Text == "Muteren")
{
sql = this.selectcommand;
sql = objHelper.ProcessStatement(sql, para);
ds = objHelper.Statement2DataSet(sql);
ControlFactoryHelper.Dataset2Controls(
ds, this.Controls);
};
}
In onze webparts komt een aantal standaard besturingselementen voor (bijvoorbeeld soortMutatie) waarmee je instelt welke bewerking op de gegevens uitgevoerd wordt. In het voorbeeld is te zien hoe op basis van de helper-klasse de controls worden omgezet naar een naam-waarde collection. Deze wordt gebruikt om een SQL-statement te parametriseren met waarden zoals ingevuld in de besturingselementen. Vervolgens wordt het SQL(SELECT)-statement omgezet naar een dataset. De waarden uit deze dataset worden gekoppeld aan de eigenschappen van de controls. Hiermee is een round trip van controls naar database en weer terug naar de controls gerealiseerd.
In de onderstaande afbeelding is een en ander te zien. Na het klikken op de knop kiezen gaat bovenstaande source code af. Omdat alleen de waarde van de eerste keuzelijst relevant is voor het SELECT-statement wordt op correcte wijze het juiste besturingselement gebruikt voor het instantieren van het SELECT-statement. Omdat de namen van de besturingselementen overeenkomen met de kolomnamen in de resultset van het SELECT-statement, worden vervolgens de juiste besturingselementen gevuld met waarden uit de resultset.

Fig. 4
Genereren van webparts
Uit dit artikel blijkt dat een webpart bestaat uit een groot generiek deel en een klein specifiek deel. Het specifieke deel wordt gebruikt om collecties van eigenschappen te instantiëren met specifieke waarden. Echter we kunnen nog een stap verder gaan. Het specifieke deel kan vanuit een CASE tool gegenereerd worden, hierdoor kun je op basis van datamodellen zeer snel één of meerdere webparts genereren. Omdat de webparts in gedrag generiek zijn werken alle specifieke webparts op een zelfde wijze.
De CASE tool DLA-Work in Process bevat de mogelijkheid om een domein- of datamodel om te zetten naar source code in een webpart. In figuur 5 is te zien hoe in een repository wordt opgegeven waarin specifieke eigenschappen aangepast worden.

Fig. 5
De CASE tool maakt het voor een gebruiker mogelijk om snel en eenvoudig door het domeinmodel te navigeren, maar nog belangrijker is het dat de tool structuur biedt en validaties uitvoert zodat een inconsistent objectmodel vrijwel niet mogelijk is.
Is het domeinmodel gereed, dan kan er eerst een database-structuur aangemaakt worden voor SQL-Server of MS-Access. Daarna kan het webpart worden gegenereerd. Dit wordt gedaan op basis van een template zodat eigen gedrag later toegevoegd kan worden aan het webpart. In de afbeelding hieronder is te zien hoe een genereerscherm van een webpart er uitziet.

Fig. 6
Samenvatting
In dit artikel is ingegaan op de basismogelijkheden van webparts. Webparts zijn niet alleen interessant binnen Sharepoint maar zeker ook binnen ASP.Net webtoepassingen. Bij het gebruik van webparts is het gebruik van helper-klasses en overerving een belangrijk hulpmiddel voor het implementeren van hergebruik en het zorgen dat specifieke sourcecode minimaal is en alleen gebruikt wordt voor het aanroepen van generieke functies binnen de generieke modulen. Door het gebruik van een CASE tool is het verder mogelijk om het specifieke deel van de code uit een repository te genereren. Hierdoor wordt het maken van webparts bijzonder eenvoudig. Een freeware versie van de CASE tool DLA Work in Process is te vinden op de website www.dla-os.nl. Bij het artikel hoort een voorbeeldtoepassing dat gebruikt kan worden in Visual Studio 2008. In volgende artikelen over webparts zal ik ingaan op het koppelen van webparts aan elkaar en het gebruik van datagrids en andere besturingselementen voor Master-Detail webparts.