|Delphi Clinic||C++Builder Gate||Training & Consultancy||Delphi Notes Weblog||Dr.Bob's Webshop|
Delphi Prism and WCF
In this article, I'll demonstrate we can use Delphi Prism to create WCF (Windows Communication Foundation) applications, a logical follow-up from the SOAP articles in the previous months.
Windows Communication Foundation with Delphi Prism
Windows Communication Foundation (WCF) is the next generation of ASP.NET Web Services (and .NET Remoting combined), and although it was possible with previous versions of Delphi to produce a WCF application, it was not very easy.Delphi Prism has full support for WCF, which is what I'll demonstrate in this article.
Where ASP.NET Web Services offer the ability to send SOAP messages over HTTP, and .NET Remoting offered a way to send SOAP or binary messages over HTTP or TCP, the Windows Communication Foundation (WCF for short) extends this even further. This time, however, I will not focus on the extended capabitlies, but rather show how we can create a simple WCF server using Delphi Prism, and how we can deploy this SCF service using IIS, and then import and consume it in a Delphi Prism client again.Nothing fancy, just to get started and let you get a little feeling of how WCF works.In future articles, I'll continue covering WCF topics, so stay tuned for me (and don't hesitate to send me feedback if you have questions or comments).
Pawel Glowacki has writen an article that explains how to create a WCF service in Delphi Prism by starting with a console application, building everything from scratch. However, it may be good to see the built-in support of Delphi Prism in action, too. Start Delphi Prism, and do File | New Project.In the WCF category, select the WCF Service Library, and give it a sensible name (like MyWcfServiceLibrary) and location.
If you click on OK, you will get a new solution with one project that contains two main source files: Host.pas and Service1.pas. The Host.pas defines the WCF service host and the way it communicates with the outside world.The Service1.pas defines an example service that we make available. Let's first example the service, and add some semi-useful methods to it, and then come back to the host and see how it can communicate, before we actually write a client and use the WCF library.
The file Service1.pas contains the definition of the IService1 public interface, which is decorated with the ServiecContract attribute. The IService1 interface defines a number of methods, each decorated with the OperationContract attribute.One of these methods has an argument of type DataContract1, which in turn is a public class defined with the DataContract attribute. The complete definition of the interface and classes is as follows:
type [ServiceContract()] IService1 = public interface [OperationContract] method MyOperation1(myValue: String): String; [OperationContract] method MyOperation2(dataContractValue: DataContract1): String; end; Service1 = public class(IService1) private [OperationContract] method MyOperation1(myValue: String): String; [OperationContract] method MyOperation2(dataContractValue: DataContract1): String; end; [DataContract] DataContract1= public class public [DataMember] property FirstName: String; [DataMember] property LastName: String; end;
Especially the WCF attributes ServiceContract, OperationContract and DataContract may need some clarification here. There are even more contract types, including the FaultContract and MessageContract that we'll cover later. The attribute ServiceContract is used to specify that the interface (or class) is used as the WCF "contract" of the service.It means that this interface (or class) will be visible to WCF clients.
However, specifying that the interface (or class) is used as the contract does not imply that all members are part of the contract - these have to be explicitly marked using the OperationContract attribute.If a method is not decorated with the OperationContract attribute, then this method is not part of the WCF contract and hence not visible to WCF clients.Unfortunately, the generated example code is not entirely correct (see QC#79736), since you may have noticed the [OperationContract] attributes for the MyOperation1 and MyOperation2 methods in the Service1 class.This is not correct, since [OperationContract] attributes are only allowed on methods if the class (or interface) to which they belong has been decorated with the [ServiceContract] attribute.An easy workaround would seem to add the [ServiceContract] attribute to the Service1 class, but alas, there can be only one [ServiceContract] in a given hierarchy, and since IService1 already contains the [ServiceContract] attribute, we cannot add it to the Service1 class.The only way to fix the example code is to remove the [OperationContract] attributes from the MyOperation1 and MyOperation2 methods in the Service1 class.Note that the compiler never mentions these issues - but you will encounter them when you (try to) deploy the WCF server.
Note that only methods can be part of the WCF contract - not data, properties or events.A DataContract is a WCF contract to add meta data to the service - complex types that can be shared and used by the WCF service and clients, so we re not limited to simple types like strings, integers and floats.The individual fields that make up the data contract should be marked with the DataMember attribute.
While the generated WCF example is nice, the implementation is doing nothing more than Hello World, so let's create a new WCF contract, with some methods that's are a bit more useful. The definition of my WCF service contract is reusing my own Num2Word example that we implemented using ASP.NET Web Services a few months ago.Apart from the Num2Word method, I also want to have a method that will transform an amount of money - in main currency and cents - to a string.Like 40.10 should become forty Euro and ten cents.For this, I need a special data structure that will hold the money amount in Euro as well as the fraction (cents), plus the string values of the currency name and cents.This is put in a DataContract called MoneyContract, and I'm using the MoneyContract in the special method Money2Word, to translate an amount of money to a string of words that can be used in financial or legal contracts. The definition of the WCF service contract IMoneyService and the data contract is as follows (note that only the interface is decorated with the [ServiceContract) attribute, and the methods inside this interface with the [OperationContract] attribute, as it should.
type [ServiceContract(&Namespace := 'http://www.eBob42.org')] IMoneyService = public interface [OperationContract] method Num2Word(value: Integer): String; [OperationContract] method Money2Word(Money: MoneyContract; const &And: String): String; end; MoneyService = public class(IMoneyService) public method Num2Word(value: Int32): String; method Money2Word(Money: MoneyContract; const &And: String): String; end; [DataContract] MoneyContract = public class [DataMember] property Amount: Integer; [DataMember] property AmountCurrency: String; [DataMember] property Cents: Integer; [DataMember] property CentsCurrency: String; end;
The implementation of the Num2Word method is similar to what we've seen in the ASP.NET Web Service example a few months ago, so I won't repeat that code.The implementation for the Money2Word method is actually using the Num2Word method, as well as the AmountCurrency, CentsCurrency and "&And" string to compose the combined string with the money information.This implementation is as follows:
method MoneyService.Money2Word(Money: MoneyContract; const &And: String): String; begin Result := Num2Word(Money.Amount) + #32 + Money.AmountCurrency + &And + // &And should also include any leading spaces, when needed #32 + Num2Word(Money.Cents) + #32 + Money.CentsCurrency end;
Like the comment says, the "&And" string should contain leading spaces when needed.This is done, so we can pass the string "," (a comma) or the string "en" as value to &And to compose a correct result (there should not be a space before the comma, so the &And needs to include it when needed).
The file Host.pas contains the self-hosting implementation of a MyServiceHost assembly class, with class methods StartService and StopService. The StartService method creates an instance of the ServiceHost class, from the System.ServiceModel namespace, and registers the service at a specific address. This technique can be used by hosting the resulting assembly in a WCF hosting application, which can be anything, from Console, WinForms to whatever other target you can produce. However, we can also host the WCF service in IIS, which I personally find a convenient way to deploy the WCF server.When taking this route, we do not need the implementation in the Host.pas unit, so we can actually remove it from the project (or keep it if you ever want to self-host the WCF service elsewhere).
Hosting in IIS
In order to host the WCF service in IIS, we need three files. One of these files is already produced by our MyWcfServiceLibrary project, and should be the MyWcfServiceLibrary.dll assembly.The second file is a .svc file, which stands for service file, with only one line as follows:
<%@ ServiceHost Language="C#" Service="MyWcfServiceLibrary.MoneyService" %>
The third file that we need is a web.config file, to define the service and especially the endpoints that we want to expose.For our MoneyService, this web.config during development can be as follows:
<?xml version="1.0"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="returnFaults" > <serviceDebug includeExceptionDetailInFaults="true" /> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration="returnFaults" name="MyWcfServiceLibrary.MoneyService"> <endpoint address="http://vista/Deployment/MoneyService.svc" binding="basicHttpBinding" contract="MyWcfServiceLibrary.IMoneyService"> </endpoint> </service> </services> </system.serviceModel> <system.web> <customErrors mode="Off"/> </system.web> </configuration>
Note that I said "during development", because you may want to remove the <serviceDebug> tag as well as the <customErrors> tag when it's time for final deployment.The current contents of the <serviceDebug> tag helps to show the exception details if something goes wrong, and the <customErrors> tag specifies that we see the detailed error, including source code when appropriate.
More interesting (and important) is the <serviceMetadata> tag in the <serviceBehaviors> section, where I've specified that the httpGetEnabled property is set to True.The result of this property is that we expose (so others can get) the meta data using HTTP GET, which includes a browser (as we'll see shortly).
The actual contents of the WCF service and the endpoint are specified in the <services> section.This part resembles the example configuration file which is given in the generated Service1.pas file.Unfortunately, due to a slight typo in the example generated by Delphi Prism, the example won't work. (see again QC#79736)Inside the <service> tag, we must use the "name" property, where the example generated by Delphi Prism is using a "type" property.Unfortunately, when using the generated example, the result will be an error message that no endpoints for the service can be found, which can take you hours to solve.
Armed with the MyWcfServiceLibrary.dll, MoneyService.svc and web.config file, we now only have to create a virtual directory in IIS, give it scripting rights, and place both the MoneyService.svc and web.config in that virtual directory, and the MyWcfServiceLibrary.dll in a "bin" subdirectory of that virtual directory. After that, you can start a browser and view the URL, starting with the name of your server (which is usually localhost), followed by the name of the virtual directory, and finally the .svc file.In my case, being deployed on my www.bobswart.net server in a virtual directory called Deployment, the URL is http://www.bobswart.net/Deployment/MoneyService.svc, which shows the following in a browser:
Where ASP.NET web services offer the ability to be tested in a browser window, the WCF service shows information how to create a client in order to test the service. The examples are using C# and VB (no Delphi Prism), and include the use of the svcutil.exe utility that will generate an import unit for the WSDL of the WCF service.The WSDL URL that is shown is http://vista/Deployment/MoneyService.svc?wsdl, which is interesting since it's using the name of my machine instead of the domain.A better URL for the WSDL is http://www.bobswart.net/Deployment/MoneyService.svc?wsdl which also works for people outside of my local network.If we take a look at that WSDL, it doesn't look like a regular SOAP WSDL.In fact, it starts with the tempuri.org namespace, followed by a reference to another WSDL - for the SOAP import - and then the information for the WCF service.The URL of the embedded WSDL (with the name of the machine replaced by my domain name again) is http://www.bobswart.net/Deployment/MoneyService.svc?wsdl=wsdl0, and if we view that WSDL, we actually see a "normal" SOAP WSDL, but without the portType - so although it defines what the SOAP interface is, it doesn't tell you where to call to. Let's now use Delphi Prism to consume the WCF service and write a WCF client for it.
Delphi Prism WCF Client
Start a new Delphi Prism project, for example a Windows Forms Application. Inside that new project in the Solution Explorer, right-click on the References node and select "Add Web Reference".In the dialog that follows, enter the URL for the WCF service - with or without the ?wsdl attached to it, which should result in the Add Web Reference dialog finding the MoneyService service:
If you click on Add Reference, a new node called "net.bobswart.www" will be created in your project tree, with a MoneyService.disco, and two .wsdl plus two .xsd files (the .xsd files are used to represent the DataContract class).
Below the Reference.map node, you see the Reference.pas file, which contains the definition for the imported MoneyService. We can now write a few lines of code to use the MoneyService, by creating an instance of the MoneyService proxy class, creating a MoneyContract, and then calling the Money2Word method, resulting in a full string with the money details.
method MainForm.button1_Click(sender: System.Object; e: System.EventArgs); var m: WindowsApplication1.net.bobswart.www.MoneyService; Amount: WindowsApplication1.net.bobswart.www.MoneyContract; begin m := new WindowsApplication1.net.bobswart.www.MoneyService(); Amount := new WindowsApplication1.net.bobswart.www.MoneyContract(); Amount.Amount := 1234567; Amount.AmountCurrency := 'Euro'; Amount.Cents := 42; Amount.CentsCurrency := 'cent'; Amount.AmountSpecified := True; Amount.CentsSpecified := True; MessageBox.Show(m.Money2Word(Amount, ' en')); end;
If we run the WinForms client, and click on the button, the result is the following:
Which is the correct answer (in case you cannot read Dutch). So with only a few simple steps, we were able to consume a WCF service in Delphi Prism.And since it's hosted on my development server, you are free to play along and try to consume this WCF service for yourself.
In this article, I've introduced Windows Communication Foundation using Delphi Prism, by writing a WCF Service Library, hosting it in IIS and consuming it in a Delphi Prism client again. These were the simple steps, but next time we'll focus on the more complex options and capabilities of WCF.
Pawel Glowacki - Simple Delphi Prism WCF service and Delphi 2009 client
QC Report #79736 - WCF Service Library sample errors