Developing client applications using RESTful master-detail data with TRESTResponseDataSetAdapter

This is part 3 in my series of developing an REST server and client application and will focus around using the TRESTResponseDataSetAdapter.

In my last two posts, we have created a REST server with a fully documented API using YAML , and exposed 3 datasets with master detail relationships over REST using zero lines of code.  If you have not read and watch the videos. I would suggest starting there. – It’s now time to consume the API into a cross platform Delphi Client.

Steps to making the client

The video and supporting blog post take you through the following.

  1. Setting up components to connect to the REST API. (RAD Style)
  2. Converting the JSON into a master detail datasets (based on the current item in the JSON data)
  3. Enabling the data in the UI with LiveBindings and zero code.
  4. Tricks for reducing API calls.

Connecting to a REST resource in a client

To connect rapidly to a REST resource, there are a set of components available in Delphi / C++Builder that work across VCL (Windows) and FMX (multi-platform) applications. The units all appear as part of the REST name space, enabling common native code to be written to run over Windows, macOS, Linux, iOS and Android.

I have previously covered how to use the TRESTClient to connect to MailChimp and also Azure Translation Services, so I wont go over the basis of these components here, but I will quickly expand on the REST Debugger and how useful it is to get started.

The RESTDebugger is an application (in RADStudio /bin directory) that can be used to rapidly connect to a REST end point. You can define user security, end points, resources, parameters etc all in the GUI. Using this, you can then “copy components” to paste into your application the components required, all pre-configured. (See how to add The REST Debugger into the IDE Tools menu)

Once running you can then test the end point and use the REST Debugger to check its working before copying the configuration into a set of components that can be used inside your application.

Watch – Using the Rest Debugger

Converting JSON into a TDataSet on the client side

Using the TRESTResponse component, it is possible to take the JSON array data and convert it into a TDataSet using the TRESTResponseDataSetAdapter. 

The TRESTResponseDataSetAdapter takes the JSON content from the TRESTRequest, and converts it into a TDataSet, based on the FieldDefs defined. If none are defined, it creates a set of String fields.

If you execute the TRESTRequest (right click on the component) inside the RAD Studio IDE, you can then auto-populate the field defs in the TDataSet based on the returned data. If you need Strings larger than 255 char, then you would want to update the StringFieldSize property (or the specific field defs for a certain string).

Working with Detail in JSON Data Array using TRESTResponseDataSetAdapter

The nice thing with a single TRESTRequest, is that it can be used to feed into multiple TRESTResponseDataSetAdapters. This way, a JSON array with embedded array data can be passed with a single API call.

Example of http://localhost:8080/exams/1/questionsfull/ in the sample REST server

The resource  /exams/{exam_id}/questionsfull/ in the REST Server provides the QUESTIONS with the ANSWERS embedded as an array of possible answers.

By repeating the example above for the second endpoint it is possible to get the QUESTIONS exported as an a set of data, but leaves us with the answers showing as a string field that contains the JSON. (not very end user friendly)

Using an additional TRESTResponseDataSetAdapter it is possible to set the RootElement property to convert a specific JSON array field into a TDataSet (in our example the ANSWERS).

Example – Getting the RootElemenent

The RESTDebugger can again be used to help configure the components here (or just to work out the syntax for the root element). You can add the components manually by adding a new TRESTResponseDataSetAdapter and TDataSet, or by using the copy method from before and then deleting the duplicate TRESTClient and TRESTRequest. The JSON root element is based on the index in the JSON content, and then the name of the property.  e.g. [0].ANSWERS returns the first JSON array items ANSWERS field (which is the array we want) and converts to the TDataSet.

Using TRESTRequestDataSetAdapators RootElement at RunTime

While the TDataSets will populate automatically based on the Content provided from the TRESTResponse and the RootElement, there is still work todo to make the selected RootElement match the current record in the Questions TDataSet.

This is easily managed with the TDataSet on AfterScroll event. (ignore Loading for now, we will come back to this custom method and its use later).

procedure TdataExams.table_QUESTIONSAfterScroll(DataSet: TDataSet);
begin
  if (DataSet.FieldByName('QUESTION_ID').AsString 
    = '') or Loading then Exit;

  rdsaANSWERS.RootElement :=
    '['+Pred(DataSet.RecNo).ToString+'].ANSWERS';
end;

While this will enable the Master Detail data to load into the datasets as it scrolls, there is still the need to ensure the questions match the current exam requested.

Using the same event on the Exams dataset, it is possible to update the Parameter for the Questions API (based on the current selected EXAM) and fetch the data using the TRESTRequest.Execute method.

procedure TdataExams.tblEXAMSAfterScroll(DataSet: TDataSet);
begin
  if  Loading or  (DataSet.FieldByName('EXAM_ID').AsString = '') then
    Exit;

  RESTRequestQUESTIONSFULL.Params.
  ParameterByName('EXAM_ID').Value := 
    DataSet.FieldByName('EXAM_ID').AsString;
  BeginLoad;
  try
    // reset the RootElement before execute
    rdsaANSWERS.RootElement := '[0].ANSWERS';
    RESTRequestQUESTIONSFULL.Execute;
  finally
    EndLoad(rdsaQUESTIONS.Dataset);
  end;
end;

Loading, BeginLoad and EndLoad

To keep the code for the data all within the datamodule, a method for LoadExams as added. This includes the following methods below. FLoad : Integer is a  variable of the datamodule, and used to keep track of when the data is being loaded. If loading is happening, then the check to loading prevents multiple API calls from happening as the dataset scrolls during loading.

procedure TdataExams.BeginLoad;
begin
  Inc(FLoad);
end;

procedure TdataExams.EndLoad(DataSet: TDataSet);
begin
  Dec(FLoad);
  // Now loading is done, update the data
  if Assigned(DataSet) and
     Assigned(DataSet.AfterScroll) then
    DataSet.AfterScroll(DataSet);
end;

function TdataExams.Loading: Boolean;
begin
  Result := FLoad > 0;
end;

procedure TdataExams.LoadExams;
begin
  BeginLoad;
  try
    RESTRequestEXAMS.Execute;
  finally
    EndLoad(rdsaEXAMS.Dataset);
  end;
end;

Visualising the data in the UI

If you are unsure how to show the data in the UI, then I would suggest watching the video above or looking at these series of LiveBindings posts I have done previously. For the example here, 3 grids is fine to start, one each for exams, questions, and answer.

In short, you need to make sure you have added the data module to the uses. (File > Use Unit.. and then select the unit from the list) You can then right click on the form, choose Bind Visually and link the DataSet to a Grid control. There is nothing else to do in the client application, other than to call the datamodule LoadExams method.

Until next time… happy coding.

Leave a Reply

Your email address will not be published. Required fields are marked *