ASP.NET met Delphi for .NET en een klein beetje C# |
ASP.NET
De Delphi for .NET preview command-line compiler is te gebruiken als scripting taal binnen ASP.NET.
We kunnen we zowel web forms (.aspx toepassingen) als web services (.asmx toepassingen) mee bouwen.
Allereerst wil ik aandacht besteden aan de visuele web forms, en later in dit artikel aan de web services.
Maar voor we zover zijn moeten we eerst Delphi for .NET en ASP.NET zo configureren dat ze met elkaar kunnen werken.
Ik ga ervanuit dat je een machine hebt met daarop het .NET Framework, en de laatste update van de Delphi for .NET preview command-line compiler.
Als dat het geval is, moet je de volgende dingen nog doen:
web.config
Op machines met Windows 2000 Professional en SP3, met het .NET Framework en SP2, gebruik ik Microsoft's Internet Information Server (IIS) als web server.
Default krijg ik hierdoor een directory C:\InetPub met daarin een (virtual) directory genaamd Scripts.
In deze virtuele directory moeten we een bestand genaamd web.config neerzetten met de volgende inhoud:
<configuration> <system.web> <compilation debug="true"> <assemblies> <add assembly="DelphiProvider" /> </assemblies> <compilers> <compiler language="Delphi" extension=".pas" type="Borland.Delphi.DelphiCodeProvider,DelphiProvider" /> </compilers> </compilation> </system.web> </configuration>
DelphiProvider.dll
Zoals je kunt lezen in de web.config geven we hierbij aan dat we een nieuwe taal genaamd "Delphi" willen gebruiken, en dat hiervoor gebruik gemaakt moet worden van de DelphiProvider die te vinden is in de Borland.Delphi.DelphiCodeProvider.
We moeten nog wel even zorgen dat ASP.NET deze DelphiProvider kan vinden, en moeten dan ook de DelphiProvider.dll uit de Delphi for .NET Preview\Aspx\Bin directory kopiëren naar een bin directory die we als subdirectory van de scripts directory moeten aanmaken.
Vaak moet je de web server even herstarten om te zorgen dat de inhoud van dit web.config bestand ook daadwerkelijk wordt gebruikt.
aspx\framework
Als laatste stap moet je de vier bestanden uit de Delphi for .NET Preview\Aspx\Framework directory naar de C:\WinNT\Microsoft.NET\Framework\v1.0.3705 directory kopiëren.
Dit betreft dccasp.exe (exact hetzelfde als dccil.exe overigens), dccil.cfg, rescvt.dll en rlink32.dll.
Na het kopiëren moet je ook nog de rescvt.dll registreren als volgt:
regasm rescvt.dll /registerVolgens de readme.txt zou dit laatste allemaal automatisch al moeten gebeuren, inclusief het kopiëren van de bestanden. Maar kennelijk is dat alleen maar de bedoeling voor de uiteindelijke versie, want het kopiëren gebeurt echt niet automatisch, en het registeren ook niet (bovendien praat de readme.txt nog over drie in plaats van vier bestanden, dus die tekst is so-wie-so aan een nadere bestudering en eventuele verbetering toe).
ASP.NET Web Forms
Delphi for .NET gebruiken binnen ASP.NET betekent concreet dat we bestaande Delphi code kunnen gebruiken als scripting code binnen ASP.NET web forms.
Een klein voorbeeld betreft het converteren van temperatuur waarden, van Celsius naar Fahrenheit, wat door de volgende ASP.NET pagina (in Celsius.aspx) gedaan kan worden:
<html>
<head>
<title>Delphi for .NET en ASP.NET</title>
<script language="Delphi" runat="server">
procedure Celsius2FahrenheitClick(Sender: System.Object; E: EventArgs);
begin
edtFahrenheit.Text := Convert.ToString(9 / 5 *
Convert.ToDouble(edtCelsius.Text) + 32)
end;
procedure Fahrenheit2CelsiusClick(Sender: System.Object; E: EventArgs);
begin
edtCelsius.Text := Convert.ToString(5 / 9 *
(Convert.ToDouble(edtFahrenheit.Text) - 32))
end;
</script>
</head>
<body bgcolor="ffffcc">
<font face="verdana" size="2">
<form runat="server">
<br><b>Celsius:</b> <asp:textbox id="edtCelsius" runat="server"/>
<br><b>Fahrenheit:</b> <asp:textbox id="edtFahrenheit" runat="server"/>
<p>
<asp:button text="Celsius naar Fahrenheit"
OnClick="Celsius2FahrenheitClick" runat="server"/>
<asp:button text="Fahrenheit naar Celsius"
OnClick="Fahrenheit2CelsiusClick" runat="server"/>
</form>
</body>
</html>
De twee OnClick event handlers van de buttons bevatten hierbij gewoon Delphi code! Wie dit voorbeeld in praktijk wil zien werken, kan naar http://www.eBob42.com/cgi-bin/Celsius.aspx gaan waar het op het web uit te proberen is.
Code Behind
Nadeel van de code zoals we die in de eerste listing zagen is dat de HTML en de Delphi code in hetzelfde bestand staat (in mijn geval Celsius.aspx).
En het zou veel "netter" (en makkelijker te onderhouden) zijn als de code en de presentatie gescheiden is.
Dat kan met ASP.NET, en wordt ook wel Code Behind genoemd.
Als ik bovenstaand voorbeeld neem, zou dit in Code Behind vorm leiden tot twee nieuwe bestanden: Celsius2.aspx en Celsius2.pas.
Het Celsius2.aspx bestand zou er als volgt uit komen te zien (zonder één regel Delphi code):
<%@ Page language="Delphi" src="Celsius2.pas" Inherits="Celsius2.WebForm42" %> <html> <head> <title>Delphi for .NET en ASP.NET</title> </head> <body bgcolor="ffffcc"> <font face="verdana" size="2"> <form runat="server"> <br><b>Celsius:</b> <asp:textbox id="edtCelsius" runat="server"/> <br><b>Fahrenheit:</b> <asp:textbox id="edtFahrenheit" runat="server"/> <p> <asp:button id="btnCelsius2Fahrenheit" text="Celsius naar Fahrenheit" OnClick="Celsius2FahrenheitClick" runat="server"/> <asp:button id="btnFahrenheit2Celsius"text="Fahrenheit naar Celsius" OnClick="Fahrenheit2CelsiusClick" runat="server"/> </form> </body> </html>En de bijbehorende code - los van de user interface opmaak codes - zit dan in Celsius2.pas:
unit Celsius2; interface uses System.Web.UI, System.Web.UI.WebControls; type WebForm42 = class(System.Web.UI.Page) edtCelsius: TextBox; edtFahrenheit: TextBox; btnCelsius2Fahrenheit: Button; btnFahrenheit2Celsius: Button; protected procedure Celsius2FahrenheitClick(Sender: System.Object; E: EventArgs); procedure Fahrenheit2CelsiusClick(Sender: System.Object; E: EventArgs); end; implementation procedure WebForm42.Celsius2FahrenheitClick(Sender: System.Object; E: EventArgs); begin edtFahrenheit.Text := Convert.ToString(9 / 5 * Convert.ToDouble(edtCelsius.Text) + 32) end; procedure WebForm42.Fahrenheit2CelsiusClick(Sender: System.Object; E: EventArgs); begin edtCelsius.Text := Convert.ToString(5 / 9 * (Convert.ToDouble(edtFahrenheit.Text) - 32)) end; end.Helaas gaat dit nog niet goed met de Delphi for .NET preview command-line compiler. We krijgen een foutmelding die betrekking heeft of het koppelen van de OnClick event handlers aan de buttons (de Delphi code die achter de schermen gegenereerd wordt is niet correct). Als we C# als taal hadden gebruikt dan werkt het voorbeeld wel. Vervang hiertoe de eerste regel van Celsius2.aspx door:
<%@ Page language="C#" src="Celsius2.cs" Inherits="Celsius2.WebForm42" %>En gebruik de volgende C# code in Celsius2.cs om dit te demonstreren:
using System; using System.Web.UI; using System.Web.UI.WebControls; namespace Celsius2 { public class WebForm42: System.Web.UI.Page { public TextBox edtCelsius; public TextBox edtFahrenheit; public Button btnCelsius2Fahrenheit; public Button btnFahrenheit2Celsius; public void Celsius2FahrenheitClick(System.Object Sender, EventArgs E) { edtFahrenheit.Text = Convert.ToString(9.0 / 5.0 * Convert.ToDouble(edtCelsius.Text) + 32); } public void Fahrenheit2CelsiusClick(System.Object Sender, EventArgs E) { edtCelsius.Text = Convert.ToString(5.0 / 9.0 * (Convert.ToDouble(edtFahrenheit.Text) - 32)); } } }Dit probleem zou een show-stopper kunnen zijn voor ASP.NET en Code Behind met de preview van de Delphi for .NET command-line compiler (tot er weer een volgende update uitkomt), ware het niet dat er nog een manier is om de event handlers niet het Celsius2.aspx bestand aan te geven, maar dynamisch te koppelen.
Code Behind met AutoEventWireup
Het dynamisch koppelen van event handlers (achter de schermen dus) kunnen we doen in de Page_Load event handler, die wordt aangeroepen als de we AutoEventWireup optie in de eerste regel van het Celsius2.aspx bestand toevoegen (op sommige systemen blijkt dit al bij default op true te staan, en kun je dit achterwegen laten).
De aangepaste Celsius2.aspx komt er dan als volgt uit te zien:
<%@ Page language="Delphi" src="Celsius2.pas" AutoEventWireup="true" Inherits="Celsius2.WebForm42" %> <html> <head> <title>Delphi for .NET en ASP.NET</title> </head> <body bgcolor="ffffcc"> <font face="verdana" size="2"> <form runat="server"> <br><b>Celsius:</b> <asp:textbox id="edtCelsius" runat="server"/> <br><b>Fahrenheit:</b> <asp:textbox id="edtFahrenheit" runat="server"/> <p> <asp:button id="btnCelsius2Fahrenheit" text="Celsius naar Fahrenheit" runat="server"/> <asp:button id="btnFahrenheit2Celsius" text="Fahrenheit naar Celsius" runat="server"/> </form> </body> </html>Merk op dat het nu verplicht is om de buttons een ID mee te geven, om zodoende de koppeling tussen een asp:button in het Celsius2.aspx bestand en de Delphi Button in het Celsius2.pas bestand tot stand te kunnen brengen. De AutoEventWireup zorgt ervoor dat de Page_Load event handler wordt aangeroepen, en daarbinnen kunnen we dan gebruik maken van de btnCelsius2Fahrenheit en btnFahrenheit2Celsius en bij allebei deze buttons een event handler aan hun OnClick toe te voegen via de Add_Click methode.
unit Celsius2; interface uses System.Web.UI, System.Web.UI.WebControls; type WebForm42 = class(System.Web.UI.Page) edtCelsius: TextBox; edtFahrenheit: TextBox; btnCelsius2Fahrenheit: Button; btnFahrenheit2Celsius: Button; protected procedure Celsius2FahrenheitClick(Sender: System.Object; E: EventArgs); procedure Fahrenheit2CelsiusClick(Sender: System.Object; E: EventArgs); public procedure Page_Load(Sender: System.Object; E: EventArgs); end; implementation procedure WebForm42.Page_Load(Sender: System.Object; E: EventArgs); begin btnCelsius2Fahrenheit.Add_Click(Self.Celsius2FahrenheitClick); btnFahrenheit2Celsius.Add_Click(Self.Fahrenheit2CelsiusClick) end; procedure WebForm42.Celsius2FahrenheitClick(Sender: System.Object; E: EventArgs); begin edtFahrenheit.Text := Convert.ToString(9 / 5 * Convert.ToDouble(edtCelsius.Text) + 32) end; procedure WebForm42.Fahrenheit2CelsiusClick(Sender: System.Object; E: EventArgs); begin edtCelsius.Text := Convert.ToString(5 / 9 * (Convert.ToDouble(edtFahrenheit.Text) - 32)) end; end.Deze combinatie - een .aspx bestand zonder event handlers maar met AutoEventWireup, en een .pas bestand waarin de Page_Load event handler wordt gebruikt om alle andere event handlers neer te zetten - werkt in ieder geval wel, en kan dus gebruikt worden om ook met de preview van de Delphi for .NET command-line compiler aan ASP.NET met Code Behind te doen.
Andere problemen...
Helaas zijn er nog wel andere ASP.NET web form zaken die niet werken met de Delphi for .NET preview command-line compiler, zoals het gebruik van het globals.asax bestand.
Hierin kun je normaal gesproken een afgeleide HttpApplication class in opnemen, met code voor custom event handlers zoals Application_Start, Session_Start, Application_Error, Session_End, en Application_End.
Helaas wordt voor de inhoud van globals.asax een Delphi unit gegenereerd met als interne naam "ASP" (in plaats van Globals ofzo), en aangezien iedere ASP.NET page form ook al in een unit komt met de interne naam ASP krijgen we hierdoor een circular unit reference (de ASP unit van de page form zal in de uses clause moeten verwijzen naar de ASP unit van de nieuwe HttpApplication).
Een oplossing voor dit probleem zou het gebruik van namespaces kunnen zijn (een Delphi.PageForm.ASP unit tegen een Delphi.Globals.ASP unit bijvoorbeeld), of het überhaubt genereren van een unit met een andere (interne) naam dan ASP voor de globals.asax.
Eenzelfde probleem treedt op bij het maken van custom controls voor ASP.NET in externe source files.
Deze zijn alleen te gebruiken als we de custom control opnemen in een assembly (en deze in de bin subdirectory van de virtual directory te plaatsen), en - nog - niet als source code omdat ook hiervoor een unit met de interne naam ASP wordt gegenereerd.
Deze problemen zijn bekend bij Borland, en er wordt hard aan gewerkt.
De Delphi for .NET preview command-line compiler is echter nog maar een preview, en vandaar dat nog niet alles helemaal is geïmplementeerd.
Zonder globals.asax en custom controls kunnen we echter al heel wat vind ik.
ASP.NET Web Services
Behalve web forms, kun je met ASP.NET ook web services bouwen, en dat is het laatste onderwerp van dit artikel.
De configuratie van de Delphi for .NET preview command-line compiler is hetzelfde als voor ASP.NET web forms, dus als die al werken hoef je verder niks meer te veranderen om ook web services te kunnen maken (zie anders het begin van dit artikel voor instructies om de Delphi for .NET preview command-line compiler te configureren zodat het door ASP.NET gebruikt kan worden).
Web Service
Waar een web form nog een visueel aspect heeft, daar is een web service eigenlijk alleen maar een "motor" met als doel een bepaalde taak uit te voeren (vaak met een aantal interfaces naar de buitenwereld).
De daadwerkelijke communicatie tussen de web service (ook wel een SOAP object genoemd) gaat via SOAP enveloppen (XML) over HTTP, FTP of SMPT (in praktijk wordt vrijwel uitsluitend van HTTP gebruik gemaakt).
Web services maken een onderdeel uit van de foundation van .NET via de System.Web.Services assembly, en we kunnen dan ook in iedere native .NET ontwikkelomgeving een .NET web service bouwen - zoals we nu in zowel Delphi for .NET als C# zullen doen.
Een nieuwe web service class moet worden afgeleid van de WebService class (uit bovengenoemde assembly).
En daarnaast moeten we van iedere methode uit deze nieuwe class aangeven dat het een "webmethod" is, door het attribuut [WebMethod] ervoor te vermelden.
De Delphi for .NET source code (in eBob42.asmx) van een eenvoudife Euro conversie web services is als volgt:
<%@ WebService Language="Delphi" Class="eBob42.Euro42" %>
unit eBob42;
interface
uses
System.Web.Services;
type
Euro42 = class(WebService)
public
[WebMethod]
function About: String;
[WebMethod]
function GuldenToEuro(Gulden: double): double;
[WebMethod]
function EuroToGulden(Euro: double): double;
end;
implementation
function Euro42.About: String;
begin
Result := 'Dr.Bob''s Euro42 Web Service in Delphi for .NET (preview)'
end;
const
GuldenPerEuro = 2.20371;
function Euro42.GuldenToEuro(Gulden: double): double;
begin
Result := Gulden / GuldenPerEuro
end;
function Euro42.EuroToGulden(Euro: double): double;
begin
Result := Euro * GuldenPerEuro
end;
end.
De voorvader class WebService bevat alle functionaliteiten van een web service al in zich, dus hoeven we niet meer te doen dan onze eigen methoden te definiëren en te implementeren.
Eenvoudiger kan haast niet.
ASP.NET Web Services Testen
Om de web service te testen kun je deze in Internet Explorer bekijken (merk op dat we meteen een waarschuwing krijgen dat we de default namespace tempuri.org gebruiken in plaats van een eigen namespace).
Op je eigen .NET machine kan dat (default) met http://localhost/scripts/eBob42.asmx, maar ik heb deze web service ook op mijn eBob42.com web server neergezet, zodat je hem ook kunt bekijken en uitproberen als http://www.eBob42.com/cgi-bin/eBob42.asmx.
Merk op dat de pagina ook al instructies bevat - voor C# en VB.NET ontwikkelaars - om de namespace aan te passen.
Om met Delphi for .NET deze default namespace aan te passen, moeten we ook speciaal attribute in de source code opnemen vlak voor de definitie van de Euro42 web service.
Let daarbij op dat het woord Namespace zelf case-sensitive is, dus als je per ongeluk NameSpace schrijft (zoals ik de eerste keer deed) dan krijg je een hele verwarrende compiler error (die mij weer enige tijd heeft bezig gehouden tot ik door had dat ik Namespace moest schrijven).
De gewijzigde class definitie van Euro42 is als volgt:
type [WebServiceAttribute(Namespace='http://www.eBob42.com', Description='Dr.Bob''s Euro42 Web Service written in Delphi for .NET (preview)')] Euro42 = class(WebService) public [WebMethod] ... end;Na deze wijziging verdwijnt meteen de waarschuwing, en zien we zelfs de Description property terug:
WSDL voor Delphi for .NET Web Services
In het screenshot zag je wellicht al de link naar de Service Description van deze web service.
De URL hiervoor is http://www.eBob42.com/cgi-bin/eBob42.asmx?WSDL (of http://localhost/scripts/eBob42.asmx?WSDL op je lokale machine).
Het resultaat van deze link is de WSDL (Web Service Description Language) formele beschrijving van de web service, en deze WSDL kan gebruikt worden door web service importer tools, zoals de Delphi WSDL Importer of de wsdl tool die in de .NET Framework SDK zit.
Voor we echter de Euro42 web service gaan importeren en gebruiken (in C#), wil ik eerst even laten zien dat ASP.NET al een hoop mogelijkheden biedt om de web service uit te proberen en te testen.
Delphi for .NET Web Services Testen
De drie methoden die we in de screenshot zien zijn alle drie voorzien van een link.
Als je op die link klikt kom je in een pagina met meer informatie over de SOAP request en response die gebruikt (zullen) worden voor de betreffende methode, maar ook een "Invoke" knop om de betreffende methode te testen.
Inclusief de mogelijkheid om daarbij vast de parameters in te vullen.
Als we bijvoorbeeld naar de GuldenToEuro link gaan, dan krijgen we een pagina waar informatie over de methode staat, maar waar we ook de waarde van Euro kunnen invullen en op de Invoke knop kunnen drukken om de aanroep te testen.
Het resultaat komt in een pop-up window, en bevat de SOAP envelop met daarin het aantal guldens (220.371) dat we krijgen als we 100 euro naar guldens converteren.
Delphi for .NET Web Services Importeren
Na het eventueel lokaal uittesten van de web service wordt het tijd om deze echt te importeren en vanuit een andere omgeving te gebruiken.
Ik zal de wsdl tool van de .NET Framework SDK gebruiken om de Delphi for .NET web service te importeren (tot een C# source code file), en hier vervolgens met de C# command-line compiler een assembly van maken, en assembly in een C# toepassing gebruiken, en tot slot de import assembly ook in een Delphi for .NET toepassing gebruiken zodat de cirkel weer rond is.
WSDL
De wsdl tool kan met een hoop opties worden aangeroepen, waaronder /language voor de taal die hij zal genereren.
Default is dit C#, en het alternatief is VB.NET, maar er zal uiteraard geen Delphi for .NET gegenereerd kunnen worden.
Met de /n optie kunnen we aangeven dat er een expliciete namespace gebruikt moet worden voor de gegenereerde impot unit.
Dit is (nog) nodig om deze met Delphi for .NET te kunnen gebruiken.
De aanroep om de Euro42 web service (vanaf het web) te importeren is als volgt:
wsdl.exe /language:cs /n:Euro42 http://www.eBob42.com/cgi-bin/eBob42.asmx?WSDLHet resultaat is een file Euro42.cs met daarbinnen de namespace "Euro42" die de class Euro42 bevat met daarbinnen de definities voor About, GuldenToEuro en EuroToGulden. Wat er nog meer in staat is code om de web service te kunnen benaderen, argumenten door te geven, resultaat te ontvangen, etc. gebruikmakend van een proxy class. Voor we deze Euro42 class in onze web service clients kunnen gebruiken, moeten we eerst de source file compileren met de C# command-line compiler, en daarbij aangeven dat er een library gemaakt moet worden (de /out: optie is overgens optioneel):
csc.exe /t:library /out:Euro42.dll Euro42.csZodra we de Euro42.dll assembly hebben, kunnen we .NET toepassingen schrijven die de Euro42 class uit deze assembly gebruiken om met de web service te communiceren (en die te gebruiken). Een kleine C# toepassing die dit doet kun je hieronder zien in UseEuro42.cs:
using System; namespace Euro42 { class UseEuro42 { static void Main(string[] args) { Euro42 euro = new Euro42(); Console.WriteLine(euro.About()); double gulden = euro.EuroToGulden(100); Console.WriteLine("100 euro = " + gulden.ToString() + " guldens."); Console.WriteLine("100 gulden = " + euro.GuldenToEuro(100).ToString() + " euros."); } } }Om deze toepassing te kunnen compileren moeten we via de /r: optie meegeven dat de Euro42 namespace als reference gebruikt moet worden (anders krijg je foutmeldingen dat de class Euro42 niet gevonden kan worden):
csc.exe /r:Euro42.dll UseEuro42.csEn uiteindelijk kun je UseEuro42.exe uitvoeren die het volgende resultaat zal opleveren:
Dr.Bob's Euro42 Web Service written in Delphi for .NET (preview) 100 euro = 220.371 guldens. 100 gulden = 45.3780216090139 euros.Ondanks het feit dat we geen Delphi for .NET code kunnen genereren bij het importeren van de web service, kunnen we uiteraard wel de Euro42.dll import assembly gebruiken in een Delphi for .NET toepassing (ook al was de assembly oorsponkelijk in C# geschreven, .NET is taal-neutraal en alles kan iedereen gebruiken, dus Delphi for .NET kan ook gecompileerde C# assemblies gewoon gebruiken).
program UseEuro42; {$APPTYPE CONSOLE} uses Euro42; var E: Euro42.Euro42; begin E := Euro42.Euro42.Create; writeln(E.About); writeln('100 euro = ', E.EuroToGulden(100), ' guldens.'); writeln('100 gulden = ', E.GuldenToEuro(100), ' euros.') end.Merk op dat ik Euro42 in de uses clause heb staan, terwijl er helemaal geen unit Euro42 is. Dit is echter een verwijzing naar een assembly, en we zullen straks de command-line compiler moeten vertellen waar hij die kan vinden (zodat er meta informatie uit gehaald kan worden om het progamma te compileren).
dccil -LUEuro42 UseEuro42.dprDit levert echter een foutmelding op - niet van de compiler, maar van iets dat door de compiler gebruikt wordt kennelijk, waardoor het geen fatale fout is. De foutmelding is: Error: Identifier redeclared '.Euro42', maar het resultaat is toch een executable die we gewoon kunnen draaien met goed resultaat.
Meer Informatie...
In dit artikel heb ik laten zien hoe we een machine met het .NET Framework (en liefst ook de SDK erop) en de Delphi for .NET preview command-line compiler (met de Update 3) zo kunnen configureren dat we Delphi als taal binnen ASP.NET web forms en web services kunnen gebruiken.
Er zijn nog enkele problemen met de Delphi for .NET preview command-line compiler, zoals het gebruik van de global.asax, maar ik heb in ieder geval Code Behind aan de praat kunnen krijgen.
Ook voor het importeren van web services moeten we soms nog wat speciale toeren uithalen, maar uiteindelijk werkt het meeste wel goed.
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .
Wie op de hoogte wil blijven van de laatste ontwikkelingen zou zeker eens moeten overwegen om mijn Delphi for .NET clinic bij te wonen.