Fun met webparts in ASP.Net Deel 1: Basis Webparts door Bert Dingemans, e-mail : info@dla-architect.nl www : http:// 1
Inhoudsopgave FUN MET WEBPARTS IN ASP.NET... 1 DEEL 1: BASIS WEBPARTS... 1 INHOUDSOPGAVE... 2 INLEIDING... 3 BASIS PAGINA... 3 BASIS WEBPART... 6 GENERIEKE EN HELPER CLASSES... 8 GENEREREN VAN WEBPARTS... 12 SAMENVATTING... 13 OVER DE AUTEUR... 13 2
Inleiding Webparts zijn een nieuw sort besturingselementen in webapplicaties geintroduceerd door Microsoft in ASP.Net. 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 van webparts een opzet om webparts te genereren op basis van een domeinmodel. De webparts zijn ontwikkeld in C#. Dat is voor mij niet mijn dagelijkse programmeertaal (Vulcan.Net en VB.Net) Reden om hiervoor te kiezen is het feit dat Sharepoint eenvoudiger installatie van webparts niet geschreven in C# lastig is. De webparts zijn op deze wijze in een handomdraai geschikt te maken voor sharepoint. Basis pagina 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. Opvallend is dat de pagina een aantal zones heeft waarin een aantal 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 is een webpart zone en een webpartmanager de minumum vereiste voor een standaard pagina. De webpartmanager zorg als een soort hub voor al het gedrag van de webpart zones en de webpart besturingselementen. Op een pagina met webparts moet één webpartmanager voorkomen. 3
De webpartzone 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 webpartmanager en een webpartzone <asp:webpartmanager id="webpartmanagerdrg" runat="server"> </asp:webpartmanager> <table> <tr> <td> <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 zonepart 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 kunnen een aantal instellingen van de webpart veranderd worden. In onderstaande afbeelding een voorbeeld van de pagina. 4
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 voorbeeld applicatie <asp:catalogzone id="catalogzonedrg" Height ="200px" Width="400px" runat="server" BackColor="#F7F6F3" BorderColor="#CCCCCC" BorderWidth="1px" Font-Names="Verdana" Padding="6" > /> <ZoneTemplate> <asp:declarativecatalogpart runat="server" id="catalogdrg" > <WebPartsTemplate> <DRGwebpart:DRGMedewerker id="drgmedewerker" runat="server" title="medewerker" /> <DRGwebpart:DRGProject id="drgproject" runat="server" title="project" /> <DRGwebpart:DRGOrganisatie id="drgorganisatie" runat="server" title="organisatie" /> <DRGwebpart:DRGContactpersoon id="drgcontactpersoon" runat="server" title="contactpersoon" /> </WebPartsTemplate> </asp:declarativecatalogpart> </ZoneTemplate> </asp:catalogzone> 5
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 webpartmanager aan te passen. In de voorbeeldtoepassing wordt dit eenvoudig opgelost door het scherm te openen met een bepaalde querystring waarde voor instellen. 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; case "BrowseDisplayMode": WebPartManagerDRG.DisplayMode = WebPartManager.BrowseDisplayMode; case "EditDisplayMode": WebPartManagerDRG.DisplayMode = WebPartManager.EditDisplayMode; case "CatalogDisplayMode": WebPartManagerDRG.DisplayMode = WebPartManager.CatalogDisplayMode; case "ConnectDisplayMode": WebPartManagerDRG.DisplayMode = WebPartManager.ConnectDisplayMode; Basis webpart De voorbeeld toepassing is zodanig opgezet dat de specifieke webparts zo weinig mogelijk gedrag bevatten, namelijk het instellen van een aantal eigenschappen en het definieren van de besturingselementen die in de 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; 6
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#' [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 de 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 behorend bij dit specifieke webpart. In een volgende versie van het webpart schreeuwt dit vanzelf om een XML file welke deze instellingen opslaat en inleest in de webpart. In de createcontrols methode van de 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); 7
ControlFactoryHelper.CreateTextBox(this.Controls, "bezoekplaats", "",500); DropDownList organisatie_soort = ControlFactoryHelper.CreateDropDownList(this.Controls, "organisatie_soort", "", 400); objhelper.sql2listcontrol("select searchcode as valuecolumn, [description] as displaycolumn, 2 FROM WEBCODELIST WHERE [section] = 'organisatie_soort' UNION SELECT '0', '--Maak uw keuze--', 1 FROM WEBDEFAULT ORDER BY 3, 2", organisatie_soort, "valuecolumn", "displaycolumn", para); 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")); this.toevoegencontrolproperty(new DRGControlProperty("bezoekpostcode", "Bezoekpostcode", true, false, "Modify")); this.toevoegencontrolproperty(new DRGControlProperty("bezoekplaats", "Bezoekplaats", true, false, "Modify"));... ChildControlsCreated = true; Eerst worden de verschillende besturingselementen aangemaakt, hiervoor is een helper klasse beschikbaar, waarover later meer. Daarna worden voor de ze 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 multlineedit 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 worden een aantal eigenschappen ingesteld en vervolgens wordt deze toegevoegd aan de controlscollection van de 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 vervolgens zorgt de specifieke code ervoor dat het juiste select statement wordt gekoppeld aan de juiste keuzelijst. 8
Een andere routine welke in de ControlFactory klasse is opgenomen is het zorgdragen 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(); case "SYSTEM.WEB.UI.WEBCONTROLS.DROPDOWNLIST": DropDownList objtb = (DropDownList)webcontrol; if (ColumnNameExists(objTable.Columns, objtb.id)) objtb.text = objrow[objtb.id].tostring(); 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, dan 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); 9
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 van 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 het niet geinstantieerd wordt 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 de afbeelding hieronder is te zien hoe het overerving en statische klassen gecombineerd gebruikt kunnen worden. In de abstracte klasse kan generieke code geplaatst worden welke de status van de webpart of de status van de besturingselementen binnen de webpart aanpassen. Ook kun je in een abstracte klasse events plaatsen die op generieke wijze reageren op acties van de gebruikers. Bijvoorbeeld de code hieronder: protected void verwerk_supply(object sender, EventArgs e) 10
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 komen 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 selectstatement worden vervolgens de juiste besturingselementen gevuld met waarden uit de resultset. 11
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 instantieren 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 onderstaande afbeelding is te zien hoe in een repository wordt opgegeven welke specifieke eigenschappen aangepast worden. De CASE tool maakt het voor een gebruiker mogelijk om snel en eenvoudig door het domein model te navigeren, 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 de webpart worden gegenereerd. Dit wordt gedaan op basis van een template zodat eigen gedrag later toegevoegd kan worden aan de webpart. In de afbeelding hieronder is te zien hoe een genereerscherm van een webpart er uitziet. 12
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. 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. Over de auteur Bert is een software architect en is werkzaam bij de Realisatiegroep, een consultancy bureau gericht op ICT en (jeugd)zorg. Bert heeft een voorliefde voor Model Driven Development en het genereren van software. Zo heeft hij CASE tools ontwikkeld in Visual Objects als DLA-Architect en DLA Work in Process. Er zijn freeware versies van deze tools beschikbaar op de dla-os website. Bert heeft een weblog op. 13