Vous êtes sur la page 1sur 12

Creation and Consumption of Web Services with PowerBuilder

Web services support has been significantly enhanced in PowerBuilder.NET



Creation & Consumption of Web Services with PB



PowerBuilder 12.5 introduced a number of significant enhancements to web services
support, both for creation and consumption. In particular, the following were introduced
as new features in PowerBuilder.NET:
WCF client proxy
WCF service target
REST client proxy

Were going to look at what those new features provide and how to use them. Were also
going to look at how we can package some of that functionality so that it can be used
from PowerBuilder Classic applications as well.

Windows Communication Foundation (WCF)
First though, some background. When support for a web service client was first
introduced in PowerBuilder 9, it was done based on an open source library called
EasySOAP. There were some limitations with that implementation, primarily because the
EasySOAP library only supported SOAP 1.1, was limited to XML over HTTP transport,
and provided no support for ancillary web services standards such as WS-Security.
The situation was improved considerably when a web service client based on the .NET
Framework classes (System.Web.Services) was introduced with PowerBuilder 10.5. That
updated the support for SOAP to version 1.2, although it was still limited to XML over
HTTP for transport. In addition, support for ancillary web services standard such as WS-
Security was still lacking. Such support was available for Visual Studio, but only through
an add-in that PowerBuilder could not take advantage of.
PowerBuilder 11 provided some of the same advances in terms of web service creation
with the introduction of a .NET web service target type. One particular disadvantage of
that implementation was that it required IIS to be installed on the developers local
machine and deployed the web service to it during development.
With PowerBuilder 12.5, web services support has been updated to the Window
Communication Foundation that was introduced to .NET in version 3.0. Basing the web
services support on these new set of classes (System.ServiceModel) provides a number of
important advantages:
Support for bindings other than HTTP
Support for other message formats (e.g., JSON)
Support for WS-Security and other WS-* standards (WS-Policy, WS-Addressing,
WS-SecureConversation, WS-Trust, WS-ReliableMessaging, WS-AtomicTransaction,
WS-Coordination)
Supports self-hosting of services (no need to install IIS on development
machines)

Representative State Transfer (REST)
One of the things that SOAP-based web services have drawn fire for recently is their
complexity. When things like message security are important (e.g., WS-Security), SOAP
provides significant advantages. But many people found the complexity of SOAP too
cumbersome and have instead opted for implementing web services through REST.
REST web services are accessed through standard URIs using standard HTTP methods
(POST, GET, PUT, DELETE) and return data in Internet standard media types (typically
XML or JSON). Because REST is an architecture, not a protocol (like SOAP), there are
no official standards for it. One particular disadvantage of REST is that while there are
machine readable methods that can be used to describe REST web services such as
WSDL 2.0 or Web Application Description Language (WADL), most REST web
services rely on human readable descriptions. That makes it a bit problematic for a tool
like PowerBuilder to automatically generate client proxies for such services.

Creating a WCF Client Proxy in PowerBuilder.NET
Now that weve covered the background, lets see how we can use these new features.
Well start with the WCF Client Proxy. For this example, were going to create a client
for the Amazon AWSECommerceService web service.[1] Note that you will need an
account with Amazon Web Services to call the service, so you should go to their website
and create one before trying these samples on your own.[2]
The first thing we will do is create a WCF Client Proxy Target from the New dialog (see
Figure 1). The first step in the wizard then prompts us to give our new project a name and
indicate a library we want to store it in. The second step is to provide the wizard with the
URL for the WSDL file used to describe the service (referenced below). In the third step
of the wizard we provide a namespace for the generated proxy classes and indicate the
assembly where the .NET generated code will be stored as well as the PowerBuilder
library where the PowerBuilder generated client will be stored. After this, we will be
shown the one service described by the WSDL and the data classes that PowerBuilder
will end up generating. While there are additional optional steps in the wizard, the Finish
button is enabled at this point and we can simply use it to close out of the wizard.
Next, generate the actual proxy and supporting .NET assembly by running the proxy
project you just created. What you should see as a result is a proxy object in the
PowerBuilder library with the methods supported by the proxy (see Figure 2).
What you will also see is an assembly that was automatically added to the References for
the target that provides the supporting .NET classes for the proxy (see Figure 3). Note
that unlike the web service client in PowerBuilder Classic, the data classes created by the
WCF client are stored in the supporting .NET assembly, not in the PowerBuilder library.
For this particular sample, Ive added the WCF proxy to an existing WPF target, and will
be adding a window to that WPF target that will use the WCF proxy. The first thing Ill
do after creating the new window is add a reference to the supporting assembly to the
Usings portion of the window to make it easier to use the classes it contains (see Figure
4). Because the generated data classes contain .NET array classes that support the
IEnumerator interface but do not provide access through an indexer, Ive added a
reference in Usings to the Systems.Collection classes as well so I can use them in the new
window to loop through those arrays.
Listing 1 provides the code I used to call the web service to search Amazon and then
populate a listbox with the data returned by the service. Note that Ive dummied up the
accessKeyId and secretKey values; you will need to provide the ones that Amazon
assigned to you when you registered to use their web services before the sample will
work.
Some portions of the sample bear some explanation. As indicated earlier, one of the
advantages of moving to WCF as the basis for the web service client was support for
ancillary standards such as WS-Security. The Amazon Web Services do in fact require
that you provide your accessKey as an extra header item, that you sign the request using
your secretKey, and that you use transport security to send the message. Those
requirements are easily met using the WCF proxy.
After weve created the client proxy, we enable the transport security Amazon requires as
follows:

_client.wcfConnectionObject.BasicHttpBinding.Security.SecurityMode =
PBWCF.BasicHttpSecurityMode.TRANSPORT!

To add your accessKey as an additional header item, do the following:

_client.wcfConnectionObject.SoapMessageHeader.AddMessageHeaderItem(&
AWSAccessKeyId,&
http://security.amazonaws.com/doc/2007-01-01/,&
_accessKeyId,&
PBWCF.WCFHMAC.NONE!,&
)

And finally, to sign the message with your secretKey, do the following:

_client.wcfConnectionObject.SoapMessageHeader.AddMessageHeaderItem(&
Signature,&
http://security.amazonaws.com/doc/2007-01-01/,&
_secretKey,&
PBWCF.WCFHMAC.HMACSHA256!,&
)

Also note that the proxy is self-contained. Unlike the web service clients that we would
generate in PowerBuilder Classic, we dont have to import any PBNI extensions into our
library or use any system classes (e.g., SoapConnection). Everything we need to call the
web service was generated by the proxy and can be used directly.

Creating a WCF Service Target in PowerBuilder.NET
Now lets see how to create a WCF Service. The first thing well do is select the WCF
Service target type from the New dialog (see Figure 5).
The first step of the wizard will ask you if you want to create a new WCF Service, or if
you want to migrate over an existing PowerBuilder Classic .NET Web Service target.
Well select the new client option. The second step will ask you for the project name, the
library name, and the target name. The third step will allow you to modify the library
search path to include other libraries. In the fourth step, youll assign a name to the .NET
assembly that the target will eventually create.
In the fifth step of the wizard, youll choose the type of object that will initially be
created with the target and will give it a name. You can specify (None) if you want to
skip this step. Were going to select what probably makes the most sense, Custom
Nonvisual, and give it a name (see Figure 6).
Note that the Finish button has been enabled since the second step of the wizard. Were
going to keep going for a while because theres one more option we want to customize.
In the sixth step, we can include additional resources or resource folders. The seventh
step allows you to add references to Win32 dynamic link libraries. The eighth step is the
one were looking for though. Here we get to choose whether the application will be
deployed to our local IIS server or generated as a console self-hosted application. We
want the second option (see Figure 7).
Having selected that option, there is a ninth step in the wizard that allows us to specify
the name of the executable that will run the self-hosted application as well as the base
address and the service URL. At this point, we can hit the Finish button to complete the
project.
Now we want to open the nonvisual object that was automatically created and code a
method on it (see Listing 2). For this sample, Im connecting to the Xtreme Sample
Database 2008 database that appears to have been installed on my system as part of
Visual Studio 2008. You might want to use the sample database provided with the SQL
Anywhere 12 install as part of PowerBuilder 12.5. Note that, like a .NET Web Service
application in PowerBuilder Classic, I can use the global SQLCA transaction object in
my custom nonvisual. I dont have to create a local copy of a transaction object like I
needed to do for some of the earlier EAServer-based methods of deploying web services.
Also note that Im connecting and disconnecting from the database within the method. In
a real application, you would typically use an ADO.NET connection to the database
with connection pooling enabled, so that the connect and disconnect would simple
acquire and return a connection from the connection pool rather than making and
breaking an actual connection to the database.
Ive created a d_grid DataWindow that retrieves the list of employees from the sample
database. What I then do is transfer that data to an array of structure using the
Object.Data property. To facilitate that, Ive created a DW2STRUCT utility that reads a
DataWindow and creates a structure that matches the result set definition. The sample
code for this article, as well as a number of other PowerBuilder code examples are
available at:
https://docs.google.com/#folders/0B7FWlsMaOo12NWIyODU4ZjktMWYwZS00MT-
I3LThlYmEtOTRkMGYwNjJkZjVk. There is a PB11 version of that utility available on
Sybases CodeXchange, and a 12.5 version that Ive updated to support
PowerBuilder.NET that I will make available shortly. What the function is returning is an
array of that structure. Note that one of the recent enhancements in PowerBuilder, at least
in PowerBuilder.NET, that made this possible is the ability to directly return arrays from
methods.
At this point we need to open the deployment project that was initially created because
we need to update it so that it knows to expose the method we just created (see Figure 8).
Note that we can also give the web service method a more conventional CamelCase
method name when we generate the service. One other change Ill make to the project is
to add a reference to the executable that launches the web service in a console to the Run
tab (see Figure 9).
As a result, when we select the Run option for this target, the web service hosting
environment will be launched (see Figure 10) and referencing the WSDL location in a
browser will return the WSDL from the running service (see Figure 11).

Creating a REST Client in PowerBuilder.NET
What well look at next is creating a REST Client in PowerBuilder.NET. For this sample,
were going to use some of the services from Flickr.com.[3] Once again, as with the
Amazon web services, youll need to create an account with Flickr and obtain and apiKey
before the samples will work for you. What were going to use specifically is the photo
search method, which takes two arguments: our apiKey and the text we want to use to
search for photos.
Once again, we open the New dialog and this time select REST Client Proxy (see Figure
12).
The first step of the wizard will have us give the project a name and pick a library to store
the project in. The second step is where things get interesting. It requests a service
method URL and a service method type (GET, PUT, POST or DELETE). With regard to
the service method type, think of the four basic methods of CRUD operations (create,
read, update and delete). By convention, the four service method types correspond to
those four CRUD operations:
GET = Read
PUT = Create
POST = Update
DELETE = Delete

Since were doing a read operation, well use GET for our service method type. For the
service method URL, we need to ensure that we include argument names in {} brackets
in the URL at the location where we want to provide arguments at runtime. If we neglect
this step, the proxy will end up creating a method with no arguments that is fixed for the
one call we used to create the proxy.

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={apikey}&text
={text}&format=rest

The third step is interesting as well. Remember earlier I mentioned that few REST
services actually provide a machine-readable method of describing themselves. To
compensate for that, PowerBuilder gives you three different methods of defining the data
structure that will be returned by the service:
Skip the step and use primitive types
Provide a schema or sample data that PowerBuilder will parse to determine the
data structure
Provide an existing .NET assembly that contains the data types to expect

Unfortunately, PowerBuilder doesnt provide an explicit option to handle a WSDL 2.0 or
WADL URL to describe the result data set for those methods that do provide one. In any
event, were going to use the second option and provide some sample data from calling
the method. The way we get that data is to simply use the URL referenced above with
appropriate arguments. This particular method will return XML to our browser, which we
can then cut and paste into the appropriate location on the wizard (see Figure 13). In this
step in the wizard, we also provide the name of the assembly where PowerBuilder will
generate the .NET classes it will use to handle the response from the service.
In the fourth step of the wizard, well tell PowerBuilder what format to expect the data in,
since REST services can provide it in a number of different formats. Since this particular
method returns XML, well choose that option. We also need to indicate which of the
classes that PowerBuilder generated from the sample data we consider to be the overall
response data type. For this example, PowerBuilder found the following data types in the
sample data:
restclient.rsp: The overall response type
restclient.rspPhotos: The array of photos matching our query with the overall
response type
restclient.rspPhotosPhoto: An individual photo within the above array

The class names are taken from the tag names used in the XML response. Select the
restclient.rsp class as the response data type.
The fifth step of the wizard allows us to specify the namespace for the proxy, give the
proxy a name, and indicate whicch library it will be stored in. While the wizard has
additional steps, weve provided enough for it to get started and can hit the Finish button
so the proxy project is generated.
After that, deploy the project so that the actual proxy is generated. Similar to the WCF
Client Proxy mentioned earlier, PowerBuilder will create an assembly and automatically
reference the one that contains the supporting classes (including the data type classes
well work with) as well as a proxy object in our PowerBuilder library. Also, as with the
WCF Client Proxy sample earlier, Ive added this proxy to an existing WPF application,
and will now create a window that uses that proxy. Listing 3 contains the code for this
particular sample. One thing it references is a DataWindow called d_grid that Ive
created to display the results that contains two columns: title (a string of 256 characters)
and URL (a string of 1024 characters). Only the title shows on the visible surface of the
DataWindow; the URL is used internally later in the code sample to display the picture.
Once again, note that like the WCF Client proxy, the REST Client proxy is self-
contained. Everything we need to use to call the service was generated by the proxy
object. We just instantiate the proxy class and call methods on it.
Some parts of the sample code bear explanation. The REST service we call limits the
amount of hits returned in order to keep the size of the response small. It operates in a
paging fashion where we can call it subsequent to the first call to retrieve additional
pages of responses. The service returns two important pieces of information in this
regard in the message header: photos.total the total number of photos that matched our
search and photos.perpage the maximum number of responses returned in single
request. That means that the number of photos that was returned to our initial call is the
smaller of those two values, which becomes important as we attempt to loop through the
data and display it.
Once we know how many results we have, we loop through them and populate the
DataWindow reference above with the title and the URL for the photo. Flickr stores its
images in a URL schema as follows:

http://farm{farm number}.static.flickr.com/{server name}/{photo id}_{photo secret}.jpg

All of the information we need to construct that URL was returned by the service; we just
need to assemble it together to get the actual URL.
Note that the service doesnt return actual photos to us; it just returns the data we need to
construct a URL to reference the image. How do we display the photos now that we have
the URL though? Since this is a WPF application, we can take advantage of the
System.Windows.Controls.Image class from the.NET Framework. Its Source property
takes a System.Windows.Media.ImageSource class as an argument, and the
System.Windows.Media.Imaging.BitmapImage class (which is a descendant of
ImageSource) accepts a URI as a constructor. As a result, we have Listing 4 that we add
to the RowFocusChanging event of our DataWindow. It takes the URL from the row that
just got focus, converts it to a URI, passes that to the BitmapImage class, and then
assigns the result of that to the Image class, which then displays the photo in the window.

Using a PowerBuilder.NET WCF Client in a PowerBuilder Classic Application
All this is great for our PowerBuilder.NET applications, but what if we want to use some
of this new functionality in our PowerBuilder Classic applications? Do we need to wait
for the next major release to make that possible? Well, fortunately, the answer is no.
Because PowerBuilder.NET can generate .NET assemblies, and because PowerBuilder
Classic targets can access .NET assemblies either through conditional code blocks (.NET
targets) or COM Callable Wrappers (Win32 targets), we can create assemblies in
PowerBuilder.NET that take advantage of these new features and then reference them in
our PowerBuilder Classic applications.
Because the use of .NET assemblies is fairly straightforward in PowerBuilder Classic
.NET targets, for this example Im going to focus on using a WCF Client generated in
PowerBuilder.NET in a PowerBuilder Classic Win32 target. The technique involves five
steps:
1. Create a WFC client proxy
2. Wrap that in a .NET assembly target
3. Expose some methods of the WCF client proxy
4. Expose the COM Callable Wrapper (CCW) of that .NET assembly
5. Reference that CCW from the PowerBuilder Classic application

We already saw how to create a WFC Client proxy for the Amazon web services earlier
in this article. Im going to use that same proxy, but this time instead of adding it to a
WPF target Ill be adding it to a .NET assembly target. In the nonvisual object in that
.NET assembly target Ill add a method called of_productsearch and populate it with
almost the exact same code we used in our WPF sample (see Listing 1). The primary
difference is that the keyword it will search from will be changed to an argument, and
instead of populating a listbox Ill populate an array of type String and return that from
the method. Once Ive done that, I can update the project object in the target so that it
knows to expose that new method in the generated assembly (see Figure 14).
The way were going to expose this assembly so that our PowerBuilder Classic Win32
application can see it is through a COM Callable Wrapper (CCW). For this sample, what
were going to do is add the assembly were generating here to the Global Assembly
Cache (GAC) using GACUTIL from the Windows SDK. Well also use the REGASM
utility from the .NET Framework to generate the COM entries the PowerBuilder Classic
Win32 application will use and add them to the registry.
In order to add an assembly to the GAC, the assembly must be signed with a strong
name. Therefore, we will also update the project in our .NET assembly target so that it
signs our assembly with a strong name when it generates it (see Figure 15).
One problem well encounter when we attempt to add our assembly to the GAC is that it
references the assembly that was created in support of the WCF Client. Not only must our
assembly be signed, but so must every assembly it references and PowerBuilder doesnt
provide us with an option to sign that assembly during creation. Fortunately, there is still
a way we can sign it. The Windows SDK provides a utility called ILDASM that can be
used to disassemble an assembly back into an Intermediate Language (IL) file. Another
utility called ILASM from the Windows SDK can be used to recompile IL back into an
assembly, and that utility has command line options that allow us to sign the assembly
with a strong name during that recompilation. All we need to do is use the strong name
file that PowerBuilder generated when we signed our assembly and feed it to the ILASM
utility so that it will sign the proxy assembly with a strong name as well. In our sample,
where our WCF proxy assembly is called amazonservice.dll and the strong name file that
PowerBuilder created for our wrapper class is called amazonwrapper.snk, we can strong
name sign the WCP proxy assembly as follows:

C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\x64\ildasm.exe /all
/out=amazonservice.il amazonservice.dll
ilasm /dll /key=amazonwrapper.snk amazonservice.il

Now we can add both our wrapper assembly and the WCF proxy assembly to the GAC
using GACUTIL:

C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\NETFX 4.0 Tools\x64\gacutil.exe
/i amazonservice.dll

We use REGASM to create the registry entries our PowerBuilder Classic Win32 target
will use:

regasm amazonwrapper.dll /regfile:amazonwrapper.reg

Ive taken the option here to create a registry entry file that we will run separately to add
the entries to the target computer.
Once the assemblies have been loaded into the GAC and the registry entries created, our
PowerBuilder Classic Win32 application can access them easily through OLE
Automation (see Listing 5).

Conclusion
What weve seen in this article is how web services support has been significantly
enhanced in PowerBuilder.NET through the support for WFC Client proxies, WCF
Service targets and REST Client proxies. Weve also seen how we can use
PowerBuilder.NETs .NET Assembly target capability to make such functionality
(particularly WCF and REST clients) available to PowerBuilder Classic targets.

References
1.
http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl
2. http://aws.amazon.com/
3. http://www.flickr.com/services/api/
About the Author
Bruce Armstrong is a development lead with Integrated
Data Services (www.get-integrated.com). A charter member of TeamSybase, he has been
using PowerBuilder since
version 1.0.B. He was a contributing author to SYS-CONs
PowerBuilder 4.0 Secrets of the Masters and the editor
of SAMs PowerBuilder 9: Advanced Client/Server
Development.

brucea@sys-con.com

(Listings 15 are on the following page.)

Listing 1: Using the WCF Client

string _accessKeyId = XXXXXXXXXXXXXXXXXXXXXXX
string _secretKey = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
string _responseGroup[]
ItemSearch _itemsearch
ItemSearchRequest _itemsearchrequest
ItemSearchRequest _isr[]
ItemSearchResponse _itemsearchresponse
awsecommerceservice_AWSECommerceServicePortTypeClient _client
IEnumerator _itemsenum
IEnumerator _itemenum
Item_item
Items_items
// create a WCF Amazon ECS client
_client = create awsecommerceservice_AWSECommerceServicePortTypeClient
// Configure the client
_client.wcfConnectionObject.EndpointAddress.URL =
https://webservices.amazon.com/onca/soap?Service=AWSECommerceService
_client.wcfConnectionObject.BasicHttpBinding.Security.SecurityMode =
PBWCF.BasicHttpSecurityMode.TRANSPORT!
// prepare an ItemSearch request
_itemsearchrequest = create ItemSearchRequest
_responseGroup[1] = Small
_itemsearchrequest.SearchIndex = Books
_itemsearchrequest.Title = sle_1.Text
_itemsearchrequest.ResponseGroup = _responseGroup
_itemsearch = create ItemSearch
_isr[1] = _itemsearchrequest
_itemSearch.Request = _isr
_itemSearch.AWSAccessKeyId = _accessKeyId;
_client.wcfConnectionObject.SoapMessageHeader.AddMessageHeaderItem(&
AWSAccessKeyId,&
http://security.amazonaws.com/doc/2007-01-01/,&
_accessKeyId,&
PBWCF.WCFHMAC.NONE!,&
)
_client.wcfConnectionObject.SoapMessageHeader.AddMessageHeaderItem(&
Signature,&
http://security.amazonaws.com/doc/2007-01-01/,&
_secretKey,&
PBWCF.WCFHMAC.HMACSHA256!,&
)
SetPointer ( HourGlass! )
Try
// issue the ItemSearch request
_itemsearchresponse = _client.ItemSearch(_itemsearch);
lb_1.Reset()
// write out the results
_itemsenum = _itemsearchresponse.Items.GetEnumerator()
do while _itemsenum.MoveNext()
_items = _itemsenum.Current
_itemenum = _items.Item.GetEnumerator()
do while _itemenum.MoveNext()
_item = _itemenum.Current
lb_1.AddItem(_item.ItemAttributes.Title)
loop
loop
catch ( System.Exception e )
MessageBox ( Exception, e.ToString() )
end try

Listing 2: Sample WCF Service code

DataStore lds
integer li_rc
long ll_rows
employees emp[]
SQLCA.DBMS = ODBC
SQLCA.AutoCommit = False
SQLCA.DBParm = ConnectString=DSN=Xtreme Sample Database
2008,DelimitIdentifier=Yes
connect ;
lds = create DataStore
lds.DataObject = d_grid
li_rc = lds.SetTransObject ( sqlca )
ll_rows = lds.Retrieve()
emp = lds.Object.Data
disconnect ;
destroy lds
return emp

Listing 3: REST Client sample code

int i
int count
int row
string url
restclient.rspPhotosPhoto photo
TeamSybase.n_restproxy lnv_proxy
lnv_proxy = create TeamSybase.n_restproxy
restclient.rsp resp
resp = lnv_proxy.GetMessage ( XXXXXXXXXXXXXXXXXXXXXXXX,
sle_search.Text )
if resp.photos.total > resp.photos.perpage then
count = resp.photos.perpage
else
count = resp.photos.total
end if
for i = 1 to count
photo = resp.photos.photo[i]
url = http://farm
url += photo.farm.ToString()
url += .static.flickr.com/
url += photo.server.ToString()
url += /
url += photo.id.ToString()
url += _ + photo.secret + .jpg
row = dw_hits.InsertRow ( 0 )
dw_hits.Object.title[row] = photo.title
dw_hits.Object.url[row] = url
next

Listing 4: REST Client sample code used to display images

string url
System.Windows.Media.Imaging.BitmapImage bi
System.Uri uri
Try
url = this.Object.url[newrow]
uri = Create System.Uri(url)
bi = Create System.Windows.Media.Imaging.BitmapImage(uri)
image_preview.Source = bi
catch (System.Exception e)
//ignore the error
end try

Listing 5: PowerBuilder Classic Win32 sample calling WCF Client exposed through
CCW
integer li_rc, li_index, li_count
oleobject loo
string products[]
loo = Create oleobject
SetPointer ( HourGlass! )
li_rc = loo.ConnectToNewObject ( amazonwrapper.n_amazonwrapper )
products = loo.ProductSearch ( PowerBuilder )
li_rc = loo.DisconnectObject()
li_count = UpperBound ( products )
FOR li_index = 1 TO li_count
lb_1.AddItem ( products[li_index] )
NEXT

Vous aimerez peut-être aussi