Client Web Services

Nullable Support / Nillable Support

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 as with minOccurs="0" and maxOccurs="1" definition. This will be used with simple and complex types:

<xs:element minOccurs="0" name="MySimpleString" type="xsi:string" />

<xs:element minOccurs="0" name="MyComplexType" type="ns:SomeComplexType" />

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

<MySimpleString></MySimpleString>

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 work around is not simple and it 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 be 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.

For example:

Here is a 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.

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

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

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 enahnaced 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 it's 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 disabled for backwards compatibility and testing.

Nillable 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 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.

Better Naming of Data Types and other Web Service Components

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.

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 can be disabled for compatibility purposes.

This is a Studio only change

Better Handling of Abstract Class Types

A few web-services employs 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 which 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 sub-class. 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 <choice> type. There is no easy way to handle this with Structs but the parser now does a better of catching the abstract part of this and turning it into a an XML node, which the developer can then manually process.

This is a Studio only change.

Better Handling of Arrays when used Directly in Parameter Definitions

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.

Better Handling and Support for Bare Requests and Bare Responses

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 a RPC style.

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 straight forward 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 <body> 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 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 is 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 or 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 this will ever need this option. This can be useful for debugging and it can be useful in better understanding how web-services work.

Other Changes

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 BigInit. 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<SimpleType>

      Boolean bNull

      <SimpleType> 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 2014 - 18.0