Class wwJsonServiceClient

This class makes it easy to call JSON REST services with Visual FoxPro. The class manages JSON serialization and HTTP calls in a simple single method interface.

You can use the class directly using CallMethod() method to call a service directly, or - better - create a subclass and build a dedicated service class for multiple calls.

For a more detailed look please check out the following blog post:

You can use this class to:

  • Directly call JSON REST services
  • Subclass it and implement service methods that map a service

Directly call a JSON REST Service

The direct approach just uses the CallService() method directly.

A simple HTTP GET data retrieval could look like this:

*** Load library and dependencies
DO wwJsonServiceClient

loProxy = CREATEOBJECT("wwJsonServiceClient")

lcUrl = "https://albumviewer.west-wind.com/api/artists"
loArtists = loProxy.CallService(lcUrl)

IF (loProxy.lError OR ISNULL(loArtists))
   ? loProxy.cErrorMsg
   RETURN
ENDIF   

*** Service returns an array which is turned into a FoxPro collection
? loArtists.Count  && Collection 
FOREACH(loArtist in loArtists)
   ? loArtist.ArtistName
   ? loArtist.Description
   ? loArtist.ImageUrl
   ? "---"
ENDFOR

Sending Data to the Server

You can also pass data to the service. JSON REST endpoints that expect data typically expect a single JSON parameter - which can be an object with many sub properties - to represent input values. To call the service with an object parameter for example, simply provide a FoxPro object that maps the structure of the JSON expected by the service. wwJsonServiceClient automatically serializes the FoxPro object/value into JSON for you.

To send data to the server, simply pass the lvData parameter in CallService() with a FoxPro value or object and specify the HTTP verb to be used (typically POST or PUT):

loProxy = CREATEOBJECT("wwJsonServiceClient")

lcUrl = "http://albumviewer.west-wind.com/api/album"
lvData = loAlbum && FoxPro object with Album Data
lcVerb = "PUT"   && HTTP Verb

*** Update the Album with the loAlbum data we're sending
loAlbum = loProxy.CallService(lcUrl,lvData,lcVerb)

IF (loProxy.lError)
   ? loProxy.cErrorMsg
   RETURN
ENDIF   

*** Use the updated result album
? loAlbum.Title
? loAlbum.Description

Customizing the HTTP Request

The Service client class has an .oHttp property that handles the HTTP request and is loaded on initialization. You can access this existing instance, or create a new one (using .CreatewwHttp(loNewHttp)) to customize the behavior of the HTTP request:

loProxy = CREATEOBJECT("wwJsonServiceClient")
loProxy.oHttp.AddHeader("x-custom","Custom Value")
loProxy.oHttp.nTimeout = 10

You can also override the CreatewwHttp() method in a subclass to provide re-usable common behavior for the oHttp instance.

Customizing the JSON Serializer

Likewise you can also use the .oSerializer property to customize serialization behavior.

loProxy = CREATEOBJECT("wwJsonServiceClient")
loProxy.oSerializer.PropertyNameOverrides = "lastName,firstName,lastAccess"

You can override the CreateSerializer() method in a subclass to provide re-usable common behavior for the oSerializer property.

Creating a Service Proxy Class

If you are calling a service that has many methods it's recommended that you create a separate class for all the service calls, similar to a service 'business object' that isolates all logic related to the service in a class. This does several things:

  • Single logical location for all Service related code
  • Consistent interface for calling service methods
  • Easy reuse for service calls throughout application

To use, subclass wwJsonServiceClient and implement your service methods that then using CallService() to call JSON service endpoints. The idea is that each service method you implement completely abstracts the service logic so the calling code simply calls a method with a parameter and a result value - all the service logic is handled internally to the service wrapper class.

Here's what a class looks like calling a couple of service methods:

DEFINE CLASS CustomerServiceClient as wwJsonServiceClient

cServiceBaseUrl = "http://localhost:23332/localizationService.ashx?method="

FUNCTION GetResourceSets()
    LOCAL loResult
    loResult = this.CallService(this.cServiceBaseUrl + "GetResourceSets")

    IF (this.lError)
       RETURN .NULL.
    ENDIF
    
    RETURN loResult
ENDFUNC

FUNCTION UpdateResource(loResource)
    LOCAL loResult
    
    *** Service returns update resource
    loResult = this.CallService(this.cServiceBaseUrl + "UpdateResource",loResource,"PUT")
    IF (this.lError)
       RETURN NULL
    ENDIF
    
    RETURN loResult
ENDFUNC

ENDDEFINE

To use this you would then just use:

loProxy = CREATEOBJECT("CustomerServiceClient")

loResources = loProxy.GetResources()
if ISNULL(loResources)
   ? loProxy.cErrorMsg
   RETURN
ENDIF   

? loResources.Count
? loResources.Item(0).Locale
? loResources.Item(0).Text


loResource = GetMyResourceToUpdateFromSomewhere()
loResource = loProxy.UpdateResource(loResource)

if !llResult
   ? loProxy.cErrorMsg
   RETURN
ENDIF   

? loResource.ResourceId
? loResource.Locale
? loResource.Text

This latter approach is preferrable as it treats service access like a business object where all logic created around the service is created in one place. It allows you to reuse the service calls and isolate your error handling and future maintenance all in one place.

Custom
  wwJsonServiceClient

Class Members

MemberDescription

AddHeader

Adds an HTTP header to the current .oHttp instance.

This method is a shortcut for .oHttp.AddHeader() to make the functionality easier to discover.

o.AddHeader(lcHeader, lcValue)

CallService

o.CallService(lcUrl,lvData,lcVerb)

CreateSerializer

Allows for overriding of the wwJsonSerializer instance that is loaded on Init() by this class in subclasses to provide custom serializer functionality. .oSerializer is always available, but you can customize this method to specify the create a customized instance of a serializer that can be used instead.

o.CreateSerializer(loSerializer)

CreatewwHttp

o.CreatewwHttp(loHttp)

cErrorMsg

An error message if an error occurs and .lError is .T.

cRequestData

If lSaveRequestData is enabled, this property will contain any request data that is sent to the server after CallService() has completed.

cResponseData

If lSaveRequestData is enabled, this property will contain any response data returned from the HTTP request after CallService() has completed.

cServiceUrl

lError

Flag that is set if either the HTTP request or JSON Serialization failed. If .T. the .cErrorMsg is also set.

lNoUtf8Decoding

By default all JSON responses are decoded from UTF-8. If the response content is not UTF-8 encoded set this value to .T.

JSON output is almost always returned in UTF-8 format, so you should never have to set this flag. It's here for those rare exceptions.

lSaveRequestData

If enabled saves request and response data - if any - in the respective cRequestData and cResponseData properties.

oHttp

oSerializer

Example


************************************************************************
DEFINE CLASS CustomerServiceClient as wwJsonServiceClient
*********************************************************

************************************************************************
*  GetCustomers
***************
FUNCTION Customers(loParms)
LOCAL loCustomers

loCustomers = THIS.CallService(this.cServiceBaseUrl + "customers.csvc", loParms)

IF THIS.lError
   RETURN null
ENDIF   

RETURN loCustomers
ENDFUNC
*   GetCustomers

************************************************************************
*  UpdateCustomer
*****************
FUNCTION UpdateCustomer(loCustomer)

loCustomer = THIS.CallService(this.cServiceBaseUrl + "customer.csvc",loCustomer,"PUT")

IF THIS.lError
   RETURN null
ENDIF 

RETURN loCustomer
ENDFUNC
*   UpdateCustomer

ENDDEFINE

Requirements

Assembly: wwJsonSerializer.prg • wwHttp.prg (dependency)

See also:

Class wwJsonServiceClient

© West Wind Technologies, 1996-2023 • Updated: 05/19/23
Comment or report problem with topic