Table of Contents
Master Detail data in RAD Server
The TEMSDataSetResource is a very powerful component that enables rapid development of full document REST API’s for TDataSet using RAD Server. Using TEMSDataSetResource, along with traditional master detail relationship configurations, it is possible to expose, and automatically document data APIs via REST with no code at all.
In this article, I will cover sharing master detail data with no code, but also how to roll your own REST endpoint to cover more advanced detail with detail embedded calls.
In my previous article, I updated advise on getting started with Swagger UI, using the new WebFiles feature of RAD Server (from 10.3.2) as a way to view your documentation as you build your backend services API. This article will build upon the sample application created in that post.
Master Details Data Basics
The basics of Master Detail in RAD Studio / Delphi / C++Builder have not change since Delphi 1.
- You have two datasets, master and detail
- A TDataSource is connected to the master
- The detail uses the TDataSource to identify where to read the current record from
- The detail sets up the master field(s)
This setup ensures that as the master is navigated, the detail is filtered to the current records matching the key from the master.
(Need to see more… watch from 2:20 in the video)
Master Detail Data via REST
To expose the data as a REST API’s requires three to four steps.
- Connecting a TEMSDataSetResource – this converts the dataset into a REST consumable resource.
- Definition of the endpoint to call to access the resource. – This is done via the ResourceSuffix Attribute. The below example shows three end points used in the video above that belong to the Resource exams.
123456789101112131415161718192021[ResourceName(<span style="color: #0000ff;">'exams'</span>)]TExamResource1 = class(TDataModule)[ResourceSuffix(<span style="color: #0000ff;">'./'</span>)][ResourceSuffix(<span style="color: #0000ff;">'List'</span>, <span style="color: #0000ff;">'./'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Get'</span>, <span style="color: #0000ff;">'./{EXAM_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Put'</span>, <span style="color: #0000ff;">'./{EXAM_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Delete'</span>, <span style="color: #0000ff;">'./{EXAM_ID}'</span>)]dsrEXAM: TEMSDataSetResource;[ResourceSuffix(<span style="color: #0000ff;">'./{EXAM_ID}/questions'</span>)][ResourceSuffix(<span style="color: #0000ff;">'List'</span>,<span style="color: #0000ff;"> './'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Get'</span>, <span style="color: #0000ff;">'./{QUESTION_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Put'</span>, <span style="color: #0000ff;">'./{QUESTION_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Delete'</span>, <span style="color: #0000ff;">'./{QUESTION_ID}'</span>)]dsrQuestions: TEMSDataSetResource;[ResourceSuffix(<span style="color: #0000ff;">'./{EXAM_ID}/questions/{QUESTION_ID}/answers'</span>)][ResourceSuffix(<span style="color: #0000ff;">'List'</span>, <span style="color: #0000ff;">'./'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Get'</span>, <span style="color: #0000ff;">'./{ANSWER_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Put'</span>, <span style="color: #0000ff;">'./{ANSWER_ID}'</span>)][ResourceSuffix(<span style="color: #0000ff;">'Delete'</span>, <span style="color: #0000ff;">'./{ANSWER_ID}'</span>)]dsrAnswers: TEMSDataSetResource;
The first ResourceSuffix shows the endpoint to call. Named parameters are then showing what is appended to that call to make specific item requests. e.g. Assuming there is an exam 1 and question 2 – The following end points become available.
/exams/ List of exams /exams/1/ Exam 1 /exams/1/questions/ List of questions for Exam 1 /exams/1/questions/2/ Question ID 2 (part of Exam 1) /exams/1/questions/2/answers/ Answers for Question 2 - Once the end points are defined, link the key fields to the endpoint to enable the parameters to apply at runtime.
- Optionally, define the documentation – rapidly done via attributes. (covered previously). – Adding attributes for each parameter listed in the ResourceSuffix. Anything in the base ResourceSuffix should be mark as required.
Rolling your own custom REST end point with detailed detail
While the components are great, there may be a time you want to embed detail inside the original API call. e.g. getting a customer with their active orders (rather than making two api calls.
For the example at hand, this is true of the exam Questions. Each question has 2 to 4 answers. As one exam has 90 questions, this is 90 API calls that can be removed by including the data in the initial call.
To do this, you can write a custom Get method. This follows the similar practices to before, with the same attributes being used.
[hint] To rapidly start this, it might be simplest to create a new Resource from File > New > Other, and then choosing RAD Server package from under RAD Server menu. Follow the wizard and choose the Sample End Points to create. You can then modify those.
Defining the REST API end point
1 2 3 4 5 6 7 8 9 10 11 12 |
[ResourceSuffix(<span style="color: #0000ff;">'./{EXAM_ID}/questionsfull/'</span>)] [EndPointRequestSummary(<span style="color: #0000ff;">'Exams'</span>, <span style="color: #0000ff;">'Exam Questions and Answers'</span>, <span style="color: #0000ff;">'Retrieves list of Questions for an exam, with the multiple-choice answers.'</span>, <span style="color: #0000ff;">'application/json'</span>, <span style="color: #0000ff;">''</span>)] [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Path, <span style="color: #0000ff;">'EXAM_ID'</span>, <span style="color: #0000ff;">'EXAM_ID from the /exams/ end point'</span>, true, TAPIDoc.TPrimitiveType.spInteger, TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spInteger, <span style="color: #0000ff;">''</span>, <span style="color: #0000ff;">''</span>)] [EndPointResponseDetails(<span style="color: #0000ff;">200</span>, <span style="color: #0000ff;">'Ok'</span>, TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, <span style="color: #0000ff;">''</span>, <span style="color: #0000ff;">''</span>)] <span style="color:#000080;"><strong>procedure</strong></span> Get(<span style="color:#000080;"><strong>const</strong></span> AContext: TEndpointContext; <span style="color:#000080;"><strong>const</strong></span> ARequest: TEndpointRequest; <span style="color:#000080;"><strong>const</strong></span> AResponse: TEndpointResponse); |
The above example shows the end point /exams/1/questionsfull/. The resource name (‘exams’) from the datamodule is again used as the root, with the ResourceSuffix defining the end point.
Building the REST data packet manually
To export the data, the following steps need to be taken
- Get the Parameter being passed in
- Set the dataset parameter and open the dataset (trapping any errors from bad parameters)
- Build the response JSON
Get the Parameter being passed in
1 |
LItem := ARequest.Params.Values['EXAM_ID']; |
Getting the parameter is as easy as asking for it by Value. This returns a string value.
Set the query parameter and open the query.
1 2 3 4 5 6 7 8 9 10 |
qryQuestions.Close; <strong><span style="color:#000080;">try</span></strong> qryQuestions.ParamByName(<span style="color:#0000ff;">'EXAM_ID'</span>).AsString := LItem; qryQuestions.Open(); qryAnswers.Open(); <strong><span style="color:#000080;">except</span></strong> <span style="color:#339966;">// This raises the default not found error</span> AContext.Response.RaiseNotFound(<span style="color:#0000ff;">''</span>,<span style="color:#0000ff;">'Invalid EXAM_ID'</span>); Exit; <strong><span style="color:#000080;">end</span></strong>; |
It is important to trap for a bad parameter. The end user can push in anything, so if you are expecting an Integer, you could get a string!
Build the response JSON
Now, it’s time to build the output. This is simple thanks to the JSONWriter that is part of the AResponse.Body. e,g,
1 2 3 4 5 6 |
AResponse.Body.JSONWriter.WriteStartArray; AResponse.Body.JSONWriter.WriteStartObject; AResponse.Body.JSONWriter.WritePropertyName(<span style="color:#0000ff;">'foo')</span>; AResponse.Body.JSONWriter.WriteValue(<span style="color:#0000ff;">'bar'</span>); AResponse.Body.JSONWriter.WriteEndObject; AResponse.Body.JSONWriter.WriteEndArray; |
In the video, I loop the fields from the dataset to build the output, before looping the current detail and adding it in as a named array called answers, while ignoring the master field from the detail (as its explicit already) Watch now (from 16:15)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
AResponse.Body.JSONWriter.WriteStartArray; <span style="color:#339966;">// Sample code</span> qryQuestions.First; <span style="color:#000080;"><strong>while</strong> <strong>not</strong></span> qryQuestions.Eof <span style="color:#000080;"><strong>do</strong> <strong>begin</strong></span> <span style="color:#339966;"> // Build Question</span> AResponse.Body.JSONWriter.WriteStartObject; ConvertRecordToJSONObject(qryQuestions); <span style="color:#339966;"> // Answers for the question</span> AResponse.Body.JSONWriter. WritePropertyName(<span style="color:#0000ff;">'ANSWERS'</span>); AResponse.Body.JSONWriter.WriteStartArray; qryAnswers.First; <span style="color:#000080;"><strong>while</strong></span> <span style="color:#000080;"><strong>not</strong></span> qryAnswers.Eof <span style="color:#000080;"><strong>do</strong> <strong>begin</strong></span> AResponse.Body.JSONWriter.WriteStartObject; ConvertRecordToJSONObject(qryAnswers, <span style="color:#0000ff;">'QUESTION_ID'</span>); AResponse.Body.JSONWriter.WriteEndObject; qryAnswers.Next; <span style="color:#000080;"><strong>end</strong></span>; AResponse.Body.JSONWriter.WriteEndArray; AResponse.Body.JSONWriter.WriteEndObject; qryQuestions.Next; <span style="color:#000080;"><strong>end</strong></span>; AResponse.Body.JSONWriter.WriteEndArray; |
The above code uses an inline procedure (declared after var, and before begin in the Get procedure. This loops the data set and put the field name and value into the current JSON object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<strong><span style="color:#000080;">procedure</span></strong> TExamResource1.Get( <strong><span style="color:#000080;">const</span></strong> AContext: TEndpointContext; <strong><span style="color:#000080;">const</span></strong> ARequest: TEndpointRequest; <strong><span style="color:#000080;">const</span></strong> AResponse: TEndpointResponse); <span style="color:#000080;"><strong>var</strong></span> LItem: string; <span style="color:#000080;"><strong>procedure</strong></span> ConvertRecordToJSONObject( DataSet : TDataSet; MasterField : string = <span style="color:#0000ff;">''</span>); <span style="color:#000080;"><strong>var</strong></span> FieldIdx : Integer; <span style="color:#000080;"><strong>begin</strong></span> <span style="color:#000080;"><strong>for</strong></span> FieldIdx := 0 <span style="color:#000080;"><strong>to</strong></span> DataSet.FieldCount -1 <span style="color:#000080;"><strong>do</strong></span> <span style="color:#000080;"><strong>begin</strong></span> <span style="color:#339966;"> // Skip master field</span> <span style="color:#000080;"><strong>if</strong></span> (MasterField > <span style="color:#0000ff;">''</span>) <span style="color:#000080;"><strong>and</strong></span> (Uppercase(MasterField) = Uppercase(DataSet.Fields[FieldIdx]. FieldName)) <span style="color:#000080;"><strong>then</strong></span> Continue; <span style="color:#339966;"> // Add name value pairs</span> AResponse.Body.JSONWriter. WritePropertyName(DataSet.Fields[FieldIdx]. FieldName); AResponse.Body.JSONWriter. WriteValue(DataSet.Fields[FieldIdx]. AsString); <span style="color:#000080;"><strong>end</strong></span>; <span style="color:#000080;"><strong>end</strong></span>; <span style="color:#000080;"><strong>begin</strong></span> <main method code from above> <span style="color:#000080;"><strong>end;</strong></span> |
The resulting output, then covers master detail in the same response.
The post Master Detail data in RAD Server using TEMSDataSetResource appeared first on Stephen Ball’s Technical Blog.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition