Skip to content

Client Web Services 18.0

Overview

One of the issues we are seeing more of is the mechanism that is used for passing null values in web services. For example, you might have a String that should only be passed when you really want to pass the string. This is usually represented in the schema with minOccurs="0" and maxOccurs="1" definitions. This will be used with simple and complex types.

In the above examples, minOccurs="1" is the default and is implied. This says it is legal to pass 0 or 1 instances of this element.

In the past, with requests, we always passed a single element, which is by definition a valid thing to do. For example, if there was no string to pass, we passed a node with an empty value.

While this is usually technically valid, there really is no way to distinguish between a null and an empty string/value. Most of the time this works, and the web service just figures this out. However, lately, more web services are considering this to be an error and not processing the request. With responses, this is less of a problem because we can control this. We've always been able to handle an empty node when minOccurs="0" and nothing is passed back to us. In this case, we’d see that the node is missing and assign it an empty value ("" or 0) on our side. This works fine unless you actually need to distinguish between Null and "", which is rare (and not even something we support in our language).

With requests, this is becoming more and more of an issue. This problem occurs with simple types and with structs. The workaround is not simple and usually involves custom modifying the request XML. This has now been addressed.

As an aside, we explored various ways to auto-detect null values in our structs and datatypes and to not pass them when minOccurs="0". The problem is we have no reliable way of knowing if a value is meant to be null or an empty value for a type (e.g., "" for a string, 0 for a number, or False for a boolean). The exception has always been dates where we can assume that a 0 date is indeed a null as it is not a valid date - we've always had a way to handle this.

The solution is to provide a way to explicitly mark data as being null. With complex types (structs), this is done by adding an extra bNull member. With simple types, this is done by defining a "simple type" struct that contains the simple value and the bNull member. The web service then needs to recognize that these bNull members have a special meaning and to properly convert them to and from XML. The Studio's WSDL parser has been altered to create these types of nullable datatypes (when needed), and the cClientWebService class has been changed to support this.

Example of Native Simple Type

String sMyValue
Move "John" to sMyValue // this sets the value
// or to mark it null
Move "" to sMyValue // if null, make it blank. We don't really know if this is null or blank.

Example of Nullable Struct

Here is what this would look like using the new nullable struct generated by the web service parser:

Struct tNString
    Boolean bNull
    String Value
End_Struct

tNString sMyValue
Move "John" to sMyValue.Value // this sets the value
// or to mark it null
Move True to sMyValue.bNull // if null, you mark this element as null

Example of Regular Struct

The same idea would apply with structs. Consider a regular struct that does not support nulls.

Struct tWSLocalization
    String LanguageCode
    String LocaleCode
End_Struct

tWSLocalization MyVar
Move "EN" to MyVar.LanguageCode
Move "XX" to MyVar.LocaleCode
// or to mark it null
Move "" to MyVar.LanguageCode // leaving it blank implies it is null - but is it really?
Move "" to MyVar.LocaleCode

Example of Nullable Struct Generated by Web Service Parser

Compare this to a nullable struct generated by the Web Service parser:

Struct tWSLocalization
    Boolean bNull
    tNString LanguageCode
    tNString LocaleCode
End_Struct

tWSLocalization MyVar
Move "EN" to MyVar.LanguageCode
Move "XX" to MyVar.LocaleCode
// or to mark it null
Move True to MyVar.bNull // if null, set the element to true

The Studio's client web service generator will now create these null types if they are needed and will build web-service clients that will handle this. The cClientWebService class has been enhanced to know how to deal with these new nullable types and create appropriate requests and responses. The interface for all of this is private.

When working with simple types, this means you are using a struct instead of a simple type. This requires a little more code to use, but its usage is clear and straightforward. Most of the extra work is handled by the Studio and the cClientWebService class. The Studio will not use nullable datatypes if they are not needed.

This nullable support, which is now enabled by default, can be disabled for backwards compatibility and testing.

Nillable XML Types

XML types can also be defined as nillable, which is yet another way of handling null values. In this case, the schema marks a type or element with a nillable="true" attribute, usually setting minOccurs="1". This means that when the value is null, the element must be sent but should be sent with a special nill attribute (nill="true"). This is also supported as part of these changes. If a DF nullable type is set to null (i.e., you set the bNull member to true), it will pass a nillable node with a nill="true" attribute. It will handle a nill="true" response by setting the bNull element to true.

The nillable support can also be enabled or disabled for backwards compatibility and testing.

From a developer's point of view, all of this is built in. If your web service uses nulls and you create the class with the new null type support, you now have a way of marking a type as null.

Both of these changes required Studio and cClientWebService class changes.

Improved Naming Conventions

Complex web services often ended up with some strange looking names, with odd numbers and strange suffixes in their names. This was done to make sure everything generated by the web service class generator had unique names. The entire process for creating these names has been changed, and the naming is dramatically improved. You will only see strangely "decorated" names when they are actually required to avoid duplicate names.

Example of Improved Naming

An example of this improvement would be:

Enum_List
    Define C_tWSChoice_74_Locale
    Define C_tWSChoice_74_ZipCode
End_Enum_List

Struct tWSChoice_74
    Integer eChoice
    String Locale
    String ZipCode
End_Struct

Struct tWSFinderCode
    tWSChoice_74 Choice_74
    String Description
End_Struct

Now generates the following:

Enum_List
    Define C_tWSFinderCode_Choice_Locale
    Define C_tWSFinderCode_Choice_ZipCode
End_Enum_List

Struct tWSFinderCode_Choice
    Integer eChoice
    String Locale
    String ZipCode
End_Struct

Struct tWSFinderCode
    tWSFinderCode_Choice Choice
    String Description
End_Struct

This makes the code easier to read and easier to work with. If your service contains names with strange suffixes or odd embedded numbers, you may wish to try regenerating these services with the new naming system. This change is optional and can be disabled for compatibility purposes. This is a Studio-only change.

Abstract Data Types

A few web services employ a schema definition where a datatype is defined as being abstract. When a schema data type is abstract, it means that it cannot be used directly as a data type. Instead, the data is passed with an attribute that specifies the data type. That data type should be an extension of the abstract type. This is much like defining a class and then passing a subclass. It is not easy to represent this in a struct. In this case, each array element could be one of a variety of different structs. Usually, this sort of thing is handled with a type. There is no easy way to handle this with structs, but the parser now does a better job of catching the abstract part of this and turning it into an XML node, which the developer can then manually process.

This is a Studio-only change.

Improved Array Handling

When a web service definition had array parameters defined directly in their parameter definition, the handling of these arrays was not quite right. Often, this would be treated as a bare request or response, which means the entire function call would be treated as a single struct parameter. In other cases, it would just generate wrong code. This is now better supported.

This required Studio and cClientWebService class changes.

Document Type Services

Most web services are document type services. In theory, this means that the entire request or the entire response can be treated as a single piece of data - a document. This was meant to be different than RPC type services (remote procedure call) where the data must be defined like a normal computer language function call with separate in and out parameters.

The catch is that document style web services can also be used to model RPC style services. In fact, modeling a document type service in an RPC style is the most common way to use services. RPC style better models the web service's use as a function call. Restated, most modern SOAP services are document type and not RPC type (these are formally defined SOAP types). However, that document can be and usually is treated in an RPC style.

Example of Document Style SOAP Service

For example, a web service that adds two numbers and returns the sum might be defined on the server as follows:

// Adds two numbers and returns the result.
Function AddNumber Real Number1 Real Number2 returns Real

This is normally modeled on the client in a very straightforward manner:

// Adds two numbers and returns the result.
Function wsAddNumber Real llnumber1 Real llnumber2 returns Real

Even though this is a document style SOAP service, we model it as a function call that closely matches the way it is defined on the server.

This could also be modeled in a pure document style where the entire request and/or the entire response is treated as a single piece of data. When that happens, this is referred to as being a bare request and/or bare response.

Struct tWSAddNumber
    Real number1
    Real number2
End_Struct

Struct tWSAddNumberResponse
    Real AddNumberResult
End_Struct

// Adds two numbers and returns the result - bare style
Function wsAddNumber tWSAddNumber llAddNumber returns tWSAddNumberResponse

With either method, the XML data sent and received is identical. However, the mechanism you use to work with the data changes. In the above example, the regular non-bare style is much easier to work with.

As an aside, the term "bare" is used because the entire XML node inside of the section is treated as a single piece of "bare" data. Regular style goes in one more level and pulls the parameters out of the document for you.

In some cases, a web service cannot be properly represented as a procedure or function call with multiple parameters. When that happens, the service must be called using the bare style. Bare style is therefore the most flexible because any web service can be treated as bare. However, it is often not the best way to represent the service.

Using a service in a bare or regular style requires changes in how the client web class is generated. It is the job of the Studio's client web service generator to detect if a service's request and its response is best handled bare or regular. The Studio has always done this, and it does this for each method in a web service. In each method, it marks a request as bare by setting pbRequestBare to True. It marks a response as bare by setting pbResponseBare to True.

The rules the Studio uses to determine the best format are complex. There is nothing in a schema or WSDL that flags a service as being bare. In prior versions, we thought there were flags, and we were wrong. A change was made in version 17.0 where the parser looks at the structure of the schema to determine if it can be used for a regular request or response. If it can, it uses the regular mechanism; if it cannot, it generates it bare style. This has been refined in 18.0, and the Studio now does a better job of detecting this. This means that more web services will be generated regular style and are therefore easier to use.

All web services can be represented bare style, but not all web services can be represented regular style. In some cases, it is impossible to represent a service regular style, and the Studio will detect this. In other cases, it is possible to represent the service regular, but it is not the best choice as the service was actually designed to be bare. While using it regular style will work, it now makes the service appear more complicated. When this happens, the Studio's analysis of the service did not arrive at the best choice. In those cases, you will want to work with a service bare style even though it could be used regular style. To handle this, we now have an option in the client web service generator where you can force the web service's request and/or its response to be bare. If you think a service would be better handled bare, you can try generating the service forcing the request or response to be bare.

It is unlikely you will ever need this option. This can be useful for debugging and can be useful in better understanding how web services work.

Struct Definitions

The definition of the tValueTree and tValueTreeEx structs have been moved out of cClientWebService.pkg and into their own packages tValueTree.pkg and tValueTreeEx.pkg. This was done so these types could be used in applications that do not use client web services.

A package named tSimpleNullTypes.pkg creates null types for all of the simple types. These are the simple types used by web services such as String, Number, Date, Integer, and BigInt. This package is used when simple null types are needed in a client web service. All of these types are defined using the same format:

Struct tN
    Boolean bNull
    Value
End_Struct

Examples of these types are tNString, tNNumber, tNDate, tNInteger, tNBigInt, etc. While created for client web services, these can be used for other purposes.

See Also

What's New in DataFlex 18.0