RESTful Workshop

Labels
AJAX(112) Apple(1) Application Builder(242) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) Barcodes(3) BLOB(18) Business Rules(1) Business Rules/Logic(140) BYOD(13) Caching(2) Calendar(5) Charts(29) Cloud(14) Cloud On Time(2) Cloud On Time for Windows 7(2) Code Generator(54) Collaboration(11) command line(1) Conflict Detection(1) Content Management System(11) COT Tools for Excel(26) CRUD(1) Custom Actions(1) Data Aquarium Framework(122) Data Sheet(9) Data Sources(22) Database Lookups(50) Deployment(22) Designer(177) DotNetNuke(12) EASE(20) Email(6) Features(99) Firebird(1) Form Builder(14) Globalization and Localization(6) Hypermedia(2) Installation(4) JavaScript(20) Kiosk(1) Low Code(3) Mac(1) Many-To-Many(4) Maps(6) Master/Detail(36) Microservices(4) Mobile(63) Mode Builder(3) Model Builder(3) MySQL(10) Native Apps(5) News(15) OAuth(5) OAuth Scopes(1) OAuth2(6) Offline(14) Oracle(10) PKCE(1) PostgreSQL(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(163) Reports(48) REST(26) RESTful(21) RESTful Workshop(13) RFID tags(1) SaaS(7) Security(75) SharePoint(12) SPA(5) SQL Anywhere(3) SQL Server(26) Stored Procedure(4) Teamwork(15) Tips and Tricks(81) Tools for Excel(2) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(331) Video Tutorial(37) Web 2.0(100) Web App Generator(101) Web Application Generator(607) Web Form Builder(39) Web.Config(9) Workflow(28)
Archive
Blog
RESTful Workshop
Thursday, April 21, 2022PrintSubscribe
BLOBs with RESTful API

Touch UI provides the following view of the Categories in the backend application. The Picture field contents can be downloaded, replaced, or deleted by users. The sketch pad allows making visual annotations on the image.

Images can be downloaded, replaced, or deleted. The BLOB input of Touch UI allows making the visual annotations on the image. The BLOB data is physically stored in the database or external storage.
Images can be downloaded, replaced, or deleted. The BLOB input of Touch UI allows making the visual annotations on the image. The BLOB data is physically stored in the database or external storage.

Every row in the Categories table contains a Binary Large Object (BLOB) in the Picture column. The data type of the column is image. Modern database engines allow storing the binary data of variable length directly in the tables. Applications created with Code On Time work with the BLOBs stored in the database or external storage, such as a folder of the file system or a container in Microsoft Azure.

The JSON data presented below is the categories singleton resource returned by the backend. There are five fields and lots of hypermedia. The picture field and products field have the API links as their values. The former corresponds to the physical column Categories.Picture. The latter is the products field defined in the Categories model.

RESTful API allows fetching the BLOB values, passing BLOBs in the CRUD requests, and replacing or deleting the BLOBs in the singleton resource fields.
JSON
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152{
    "categoryId": 1,
    "categoryName": "Beverages",
    "description": "Soft drinks, coffees, teas, beers, and ales",
    "picture": "/v2/categories/1/picture",
    "products": "/v2/categories/1/products",
    "_links": {
        "self": {
            "href": "/v2/categories/1"
        },
        "up": {
            "href": "/v2/categories"
        },
        "collection": {
            "href": "/v2/categories?count=true"
        },
        "first": {
            "href": "/v2/categories?page=0&limit=10"
        },
        "products": {
            "href": "/v2/categories/1/products?count=true",
            "embeddable": true
        },
        "products-first": {
            "href": "/v2/categories/1/products?page=0&limit=10",
            "embeddable": true
        },
        "edit": {
            "href": "/v2/categories/1",
            "method": "PATCH"
        },
        "replace": {
            "href": "/v2/categories/1",
            "method": "PUT"
        },
        "replace-picture": {
            "href": "/v2/categories/1/picture",
            "method": "PUT"
        },
        "delete-picture": {
            "href": "/v2/categories/1/picture",
            "method": "DELETE"
        },
        "delete": {
            "href": "/v2/categories/1",
            "method": "DELETE"
        },
        "schema": {
            "href": "/v2/categories/1?_schema=true"
        }
    }
}

Fetching BLOBs

The BLOB can be fetched if the value of the singleton resource field is not equal to null. The link in the value identifies the resource followed by the name of the field. The original file name of the BLOB may follow the field name in the resource URL with certain configurations of the resource data controller.

Links in Values

The BLOB link in the sample data shown above consists of the /v2/categories/1 singleton resource URL and the picture field name concatenated together. An attempt to load the blob in the incognito or private browser mode will force the backend application to return the error. An authorization key or access token is required to read the resources known to the RESTful API.

RESTful API must be enabled explicitly in the application configuration. An authorization key or access token is required to access a resource.
RESTful API must be enabled explicitly in the application configuration. An authorization key or access token is required to access a resource.

Navigate to the backend application in the new browser window, sign in, and enter the same URL in the address bar. The image will be loaded successfully . Touch UI application creates an encrypted cookie to track the user identity. The BLOB request will be authenticated since the cookie is sent by the browser with each request.

RESTful API Engine identifies the user automatically when the host application is making a request. The cookie with the self-encrypted user access token provides the identity information.
RESTful API Engine identifies the user automatically when the host application is making a request. The cookie with the self-encrypted user access token provides the identity information.

Standalone single page applications and other HTTP clients will have to specify an authorization key or access token explicitly. Here is the blob fetched by Postman with the authorization key specified in the URL.

Postman shows the BLOB fetched from the resource with the authorization key specified in the "x-api-key" query parameter. RESTful API Engine requires an access token or authorization key to be specified explicitly by the external applications.
Postman shows the BLOB fetched from the resource with the authorization key specified in the "x-api-key" query parameter. RESTful API Engine requires an access token or authorization key to be specified explicitly by the external applications.

Most client applications will not be able to secure an authorization key or set up a cookie with the user identity. JavaScript client apps can establish a secure communication channel with the backend by taking advantage of the restful.js script. It is being used extensively in the standalone single page apps of the workshop.

Here is the customized SPA4 with RESTful Hypermedia and CRUD client app that fetches the images from the backend application in a secure fashion. An image appears in the Category cell when clicked by users.

The standalone single page application fetches the image of the product category on-demand when the category name is clicked. The app follows the hypermedia links to find the product category "picture" field value.
The standalone single page application fetches the image of the product category on-demand when the category name is clicked. The app follows the hypermedia links to find the product category "picture" field value.

The BLOB resource URL cannot be assigned to the src attribute of the image and must be fetched from the backend with the access token specified in the request header.

This is the modified version of the handleHypermedia function that does the image retrieval and assignment.

JavaScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758function handleHypermedia(e) {
    var row = e.target.closest('[data-index]')
    if (row && products) {
        let tableCell = e.target.closest('td');
        if (tableCell.previousSibling != null && tableCell.previousSibling.previousSibling == null) {
            // the cell in the "Category" column was clicked
            $app.restful({
                url: products.collection[row.getAttribute("data-index")]._links.self,
                hypermedia: 'categoryId >>'
            }).then(obj => {
                $app.restful({
                    url: obj.picture
                }).then(blob => {
                    // create an object URL
                    let dataUrl = URL.createObjectURL(blob);
                    // create an image element and replace the cell content with it
                    let img = document.createElement('img');
                    img.src = dataUrl;            // set the image src
                    img.title = obj.categoryName; // set the image tooltip
                    img.style.maxWidth =          // set the maximum image width
                        tableCell.getBoundingClientRect().width - 12 + 'px';
                    // clear the cell content
                    tableCell.innerHTML = '';
                    // insert image in the cell
                    tableCell.appendChild(img);
                });
            });
        }
        else
            readProduct(row.getAttribute("data-index"));
    }
    else {
        var btn = e.target.closest('[data-hypermedia]');
        if (btn && products) {
            var hypermedia = btn.getAttribute('data-hypermedia');
            switch (hypermedia) {
                case 'new':
                    newProduct();
                    break;
                case 'create':
                    postProduct();
                    break;
                case 'edit':
                    patchProduct();
                    break;
                case 'delete':
                    deleteProduct();
                    break;
                case 'cancel':
                    hideForm();
                    break;
                default:
                    refreshProductList(products._links[hypermedia]);
                    break;
            }
        }
    }
}

The original implementation orchestrates the UI flow in the app by responding to the clicks on the product list rows and buttons. The Edit Product form is displayed when users click anywhere in the product row.

The extended code figures when the click occurs in the second column of the product table and goes on to fetch and display the category image in the table cell:

  • First, it invokes the $app.restful method. The self link of the clicked product item is specified in the url parameter. The hypermedia parameter tells the method to locate and fetch the categoryId hypermedia control in the singleton product resource. The obj argument in the then chain function will be the instance of the category object just like the one shown at the top of the tutorial.
  • The obj argument has the picture property. Another call of the $app.restful method will fetch the BLOB object from the url specified in the property value. The blob argument of the then chain function is the instance of the Blob object. The $app.restful method converts the server response into a Blob instance if the content type of the response is not JSON, XML, or YAML.
  • Finally, the Object URL is created from the Blob instance and the table cell contents are replaced with the image that has its src attribute set to base-64 encoded URL. The image is fitted to the cell boundaries and has its title set to the category name to serve as a tooltip.

The products resource collection items do not have the URL of the category picture. The display of the category image takes three requests:

  1. The product item is fetched from the self hypermedia link of the selected item in the product collection.
  2. The category is fetched from the categoryId hypermedia control of the product item.
  3. The BLOB is fetched from the resource specified by the picture field of the product category.
Client application discovers the missing data by following the hypermedia links.

A shrewd developer may figure the correct category image URL and reduce this discovery sequence to a single request:

JavaScript
123456789101112131415161718// the cell in the "Category" column was clicked
Let productItem = products.collection[row.getAttribute("data-index")];
$app.restful({
    url: '/v2/categories/' + productItem.categoryId + '/picture'
}).then(blob => {
    // create an object URL
    let dataUrl = URL.createObjectURL(blob);
    // create an image element and replace the cell content with it
    let img = document.createElement('img');
    img.src = dataUrl;            // set the image src
    img.title = productItem.categoryName; // set the image tooltip
    img.style.maxWidth =          // set the maximum image width
        tableCell.getBoundingClientRect().width - 12 + 'px';
    // clear the cell content
    tableCell.innerHTML = '';
    // insert image in the cell
    tableCell.appendChild(img);
});

The item in the product collection has the categoryId, which can be used to construct the URL of the categories resource singleton field without much difficulty.

The explicit resource URLs in the client application create a tight coupling between the client and the current implementation of the backend. This may lead to broken apps when the backend evolves over time.

If efficiency is important, then consider including the additional fields in the collections and singletons by denormalizing the data models. Avoid using the explicit URLs in the client code to ensure the application longevity and maintainability.

Public Links

Making dedicated requests to fetch an image from the server may seem exotic, but unavoidable if the images cannot be exposed to the world. BLOB objects may represent documents, images, video, and sound files, which frequently require limited access.

The product category image is not something that needs to be kept private. If that is the case, then an authorization key must become a part of the image URL specified in the src attribute of the img element. The job of fetching the image from the server can be outsourced to the browser and Document Object Model.

Let’s introduce the public authorization key in the ~/app/touch-settings.json configuration file of the backend application. Note that there is also the server.rest.acl section specifying the Access Control List of the API.

JSON
123456789101112131415161718192021222324252627282930313233343536{
  "ui": {
    "roles": "Administrators"
  },
  "server": {
    "rest": {
      "enabled": true,
      "output": {
        "json": {
          "indent": true
        }
      },
      "authorization": {
        "keys": [
          {
            "key": "94b79da7-e3a9-4152-9213-886f4c810bec",
            "user": "admin"
          },
          {
            "key": "public",
            "user": "user"
          }
        ]
      },
      "acl": {
        "categories": {
          "administrators": ".",
          "users": "GET"
        },
        "products|suppliers": {
          "administrators": "."
        }
      }
    }
  }
}

The word “public” is the fitting name for the public authorization key. In this example, the user account is associated with the public key.

The authorization key can be specified as the x-api-key or api_key query parameter. There is also a more elegant method of specifying the authorization key in the URL. The key may be included in the path right after the API version like this:

http://localhost:42439/v2/public/categories/8/picture

The URL with the public key in the path will fetch the image in the incognito mode if the access control list allows it.
The URL with the public key in the path will fetch the image in the incognito mode if the access control list allows it.

The word “public” in the key name has no special meaning and can be replaced with any mnemonic. Any authorization key discoverable by the third party is the public key.

Developers must configure an access control list (ACL) to limit the API exposure to the unauthenticated users that will consume the resources with a public authorization key.

The ACL in the configuration above will allow the identities with the Users role to perform the GET requests to the categories resources. Users in the Administrators role can perform requests to the resources of categories, products, and suppliers with any HTTP verb.

The names of the server.rest.acl key properties are the controller rules (regular expressions) that will be evaluated against the names of the data controllers. If the rule of the access control list is matched with the data controller name in the resource path, then the restful API will evaluate the identity rules (the properties of the matched controller rule). The property names are the application user roles or the OAuth 2.0 scopes. If the current user is in role or has the scope in the access token, then the engine will try to match the HTTP method or the custom action identifier with the identity rule value (also a regular expression). If there is no match, then the access is denied.

This is the code that will use the public authorization key to fetch the category images when the category name is clicked in the product grid.

JavaScript
12345678910111213// the cell in the "Category" column was clicked
let productItem = products.collection[row.getAttribute("data-index")];
let img = document.createElement('img');
img.src =                             
    'http://localhost:42439/v2/public/categories/' +  
    productItem.categoryId + '/picture'; // set the image src
img.title = productItem.categoryName;    // set the image tooltip
img.style.maxWidth =                     // set the maximum image width
    tableCell.getBoundingClientRect().width - 12 + 'px';
// clear the cell content
tableCell.innerHTML = '';
// insert image in the cell
tableCell.appendChild(img);

There are no explicit resource fetching requests. A simple assignment to the src property of the img element does the job. The only concern is the hard-coded URL pointing to the backend application.

File Names in Links

The Categories table does not store the attributes of the Picture image file. The original name, type, and length of the file is not known. The BLOB processing code of the framework detects the image data automatically and sets the appropriate content type and a generic file name in the response to the request to fetch a BLOB resource. The generic content type application/octet-stream will describe a BLOB that does not contain an image.

The resource /v2/public/categories/1/picture will fetch the BLOB data even though it appears to lack the physical file name.

Application framework relies on the optional utility fields to provide the extended information about the BLOB fields of a data controller. The field names FileName, ContentType, and Length will be treated as the default utility fields. Developers can be more specific and prefix the default utility field names with the name of the corresponding BLOB field.

Alter the Categories table by adding the FileName, ContentType, and Length columns to store the extended properties of the BLOB in the Picture column. Make sure that the BLOB and the utility columns allow null values.

image8.png

Select the backend application project on the start page of the app generator, choose Refresh, and proceed to incorporate the new database schema in the project metadata. Select the Model option in the summary, choose the Categories model, and select the new columns to include them in the entity data model.

Model Builder makes it easy to perform iterative enhancement of the data models when the database schema is changed.
Model Builder makes it easy to perform iterative enhancement of the data models when the database schema is changed.

Save the Categories model and proceed to generate the app. The new fields will become a part of the user interface. Begin adding a new category and observe how the File Name, Content Type, and Length are automatically set to the properties of the selected Picture file.

Touch UI automatically detects the "utility" fields and captures the File Name, Content Type, and Length of the binary data when a file is selected or dropped on the BLOB input field.
Touch UI automatically detects the "utility" fields and captures the File Name, Content Type, and Length of the binary data when a file is selected or dropped on the BLOB input field.

Changes to the model are also reflected in the RESTful API. Here is how the new categories singleton resource may appear to the apps and developers:

JSON
1234567891011121314151617181920212223242526{
    "_links": {
        "self": {
            "href": "/v2/public/categories/197"
        },
        "up": {
            "href": "/v2/public/categories"
        },
        "collection": {
            "href": "/v2/public/categories?count=true"
        },
        "first": {
            "href": "/v2/public/categories?page=0&limit=10"
        },
        "schema": {
            "href": "/v2/public/categories/197?_schema=true"
        }
    },
    "categoryId": 197,
    "categoryName": "Baked Goods",
    "description": "Breads, cookies, pastries",
    "picture": "/v2/public/categories/MTk3L3BpY3R1cmUvag.jpeg",
    "fileName": "bread.jpeg",
    "contentType": "image/jpeg",
    "length": 13155
}

Note that that picture value now has the file name but lacks the identifier of the singleton. The resource /v2/categories/197/picture transforms to /v2/categories/MTk3L3BpY3R1cmUvag.jpeg in the BLOB value. The URL is not affected by the original file name but inherits the file extension. The extension helps users to understand the nature of data.

The BLOB value with the authorization key in the path can be used in the public-facing websites. The extension of the file name gives a hint about the data type.
The BLOB value with the authorization key in the path can be used in the public-facing websites. The extension of the file name gives a hint about the data type.

If the “FileName” utility field is a part of the data controller, then its resource will have the BLOB value URL ending with the file name composed of its base64-url-encoded BLOB field name preceded by the previous path segment. The encoded file name will have the same extension as the original file.

ETag and 304 Not Modified

The headers of the response to the BLOB resource request will include the ETag (entity tag). The value is the unique hash of the BLOB content. Web browsers capture the ETag and send it with the subsequent requests to fetch the same resource.

The response of the BLOB resource request includes the ETag, Content-Type, Content-Disposition, and Content-Length headers. ETag value can be used to avoid the unnecessary transmission of the unchanged BLOB. Type, Disposition, and Length provide the client with the BLOB metadata.
The response of the BLOB resource request includes the ETag, Content-Type, Content-Disposition, and Content-Length headers. ETag value can be used to avoid the unnecessary transmission of the unchanged BLOB. Type, Disposition, and Length provide the client with the BLOB metadata.

The RESTful API Engine will compare the optional If-None-Match header of the request with the ETag of the resource. If both values are the same, then the engine will not send the BLOB data in the response. The body will be empty and the response status code will be set to 304 Not Modified. The client application will use the previously fetched resource data.

The "304 Not Modified" response includes the ETag of the resource that has not changed since the last fetch.
The "304 Not Modified" response includes the ETag of the resource that has not changed since the last fetch.

Web browsers and views automatically use the If-None-Match and ETag headers, which helps avoid the unnecessary transmission of data.

BLOBs in CRUD Requests

Binary field values can be specified in the CRUD operations with PUT, POST, and PATCH verbs. These HTTP methods correspond to the replace, create, and edit hypermedia controls in the resources of the RESTful API Engine.

The BLOB values can be found in the collection and singleton resources. The values of the BLOB fields are the API resources links.

Multipart Form Data

The content type multipart/form-data tells the server that the binary content is specified in the request body. The HTML forms with the “file” inputs are submitted with this content type in the “formdata” format by the web browsers and views.

Here is how the request with the binary data is constructed in Postman. This request will create a new category. Individual field values are listed as key-value pairs in the form-data tab of the request body. The picture field is configured as the file. The response body shows the new resource with the BLOB link in the picture value.

This is the "create" request in the Postman with the form-data fields in the request and the new category resource in the response. The "file" value is specified in the "picture" field of the request. The properties of the file are automatically captured by the RESTful API Engine in the "fileName", "contentType", and "length" fields of the new resource.
This is the "create" request in the Postman with the form-data fields in the request and the new category resource in the response. The "file" value is specified in the "picture" field of the request. The properties of the file are automatically captured by the RESTful API Engine in the "fileName", "contentType", and "length" fields of the new resource.

The plain JavaScript code that will create a category in the browser page is presented next. It uses the Fetch API to make the POST request. Note the use of the x-api-key header set to the authorization key of the admin user.

JavaScript
12345678910111213141516171819var myHeaders = new Headers();
myHeaders.append("x-api-key", "94b79da7-e3a9-4152-9213-886f4c810bec");

var formdata = new FormData();
formdata.append("categoryName", "Baked Goods");
formdata.append("description", "Breads, cookies, pastries");
formdata.append("picture", fileInput.files[0], "bread.jpeg");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: formdata,
  redirect: 'follow'
};

fetch("http://localhost:42439/v2/categories", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

The categoryName and description request fields can be specified in the single JSON object as the value of the unnamed field in the form-data. Its content type must be set to application/json. The binary fields of the resource must be specified as the named fields of the form-data. The order of the fields in the form-data is not important.

The non-binary fields of he request are specified in the form-data field without a name in the JSON format.
The non-binary fields of he request are specified in the form-data field without a name in the JSON format.

The raw HTTP request with the multipart/form-data content type is presented in the sample below. The request will create a new category. Client applications can configure HTTP requests on their own or use the libraries available with their chosen implementation technologies.

HTTP
1234567891011121314151617181920POST /v2/categories HTTP/1.1
Host: localhost:42439
x-api-key: 94b79da7-e3a9-4152-9213-886f4c810bec
Content-Length: 408
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="picture"; filename="bread.jpeg"
Content-Type: image/jpeg

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name=""
Content-Type: application/json

{ 
   "categoryName": "Baked Goods",
   "description": "Breads, cookies, pastries"
}
----WebKitFormBoundary7MA4YWxkTrZu0gW

Method $app.restful

The standalone single page applications SPA4 and SPA5 are linking the lightweight external script with the definition of $app.restful method. The method automatically specifies the access token in the HTTP request headers to identify the user. The method refreshes the expired access token. An explicit resource URL or a hypermedia control can be specified in the url property of the method argument.

The restful.js resource of the backend application provides the standalone clients with a thin wrapper on top of the Fetch API.

The $app.restful method also detects the Blob fields specified in the body property of the argument. The request body with the Blob fields is automatically converted to an instance of the FormData object.

Let’s modify the CRUD-enabled version of SPA4 to create a category with the user-defined image in the picture field. Users will click on their avatar to activate the File Chooser prompt. The selected image file will become the picture of the new “Baked Goods” category. The name and the description of the category will be hard-coded for simplicity.

image5.png

Users can create new products in this category and observe the picture when the Category column is clicked in the product row.

image2.png

Add the following code at the bottom of the closure in the spa4.js. The code will attach the click handler to the avatar when the spa4.html page is loaded in the browser window.

JavaScript
123456789101112131415161718192021222324252627window.addEventListener('load', e => {
    // add 'click' event handler to the user avatar
    document.querySelector('#avatar').addEventListener('click', e => {
        // create a "file" input 
        var fileInput = document.createElement('input');
        fileInput.type = 'file';
        // wait for the "picture" file to be selected
        fileInput.addEventListener('change', e => {
            // create the "Baked Goods" category with the selected picture
            $app.restful({
                url: '/v2/categories',
                method: 'POST',
                body: {
                    categoryName: "Baked Goods",
                    description: "Breads, cookies, pastries",
                    picture: fileInput.files[0] // the picture image
                }
            }).then(newCategory => {
                // show the ID of the new category
                alert(newCategory.categoryId);
            }).catch(restfulException);

        });
        // display the File Chooser prompt in the browser 
        fileInput.click();
    });
});

The code dynamically creates an input of the file type and “clicks” it programmatically. The prompt to select a file is displayed. The change event will trigger on the input when a file is selected by the user. The $app.restful method executes the POST request with the categoryName, description, and picture specified in the body property of the method argument. The picture is set to the selected file.

Replacing and Deleting BLOB Values

The categories resource singleton hypermedia contains the replace-picture and delete-picture controls.

The blob image in the picture field of the resource can be replaced with the $app.restful method that has the replace-picture hypermedia object specified in its url property of the argument. The value field in the body property must be set to the new Blob or File value.

The delete-picture hypermedia control in the url property of the method argument will clear the BLOB value in the picture field.

The pair of the explicit url and method values can replace the hypermedia control in the $app.restful argument properties.

Next

Hypermedia in the resources provides instant documentation for developers and methods to transition the resource state or access the related data for the algorithms.

Learn to navigate the RESTful resources in the development tools and client apps.

Saturday, March 19, 2022PrintSubscribe
Custom Actions with Hypermedia

The REST API Level 2 and above must support HTTP methods (verbs) in resource handling. For example, the request with the GET method is expected to return the resource data while the request with the PATCH method is expected to modify some of its fields. The standard HTTP methods work well for CRUD. Unfortunately the limited number of HTTP verbs will not cover all possible scenarios of data manipulation.

RESTful API Engine allows specifying an action identifier directly in the resource URL of the POST request. The available custom actions are advertised in the resource hypermedia.

Example

Let’s modify the backend application to implement a custom action that will update the inventory when a product is sold.

The interactive users of Touch UI and the algorithmic clients of RESTful API will have the ability to sell products.

Users of the backend will navigate to the Products or Suppliers page, choose a product, and press the Sell button. The popup form will ask users to specify the number of product units to sell. The Units In Stock field value of the selected product will decrease by the number of the sold units.

Confirmation controller "SellPrompt" presents the user with an option to sell 1, 5, or 10 units of a product with a single touch. The "Other" option allows entering the arbitrary quantity of units to sell. The prompt is displayed when users press the "Sell" button in the product form.
Confirmation controller "SellPrompt" presents the user with an option to sell 1, 5, or 10 units of a product with a single touch. The "Other" option allows entering the arbitrary quantity of units to sell. The prompt is displayed when users press the "Sell" button in the product form.

Developers evolve both UI and API by making changes to the data controllers in the Model Builder and Project Designer.

The API users will post the quantity of units to sell to the action URL, which will be available in the hypermedia of the singleton resources of products.

RESTful API Engine makes the custom action "sell" available to the client apps.  The parameters of the action are specified in the "parameters" key of the payload. The new state of the resource is returned in the response.
RESTful API Engine makes the custom action "sell" available to the client apps. The parameters of the action are specified in the "parameters" key of the payload. The new state of the resource is returned in the response.

Here is how the “Sell” user interface feature is correlating with the upcoming application design changes:

  1. The new Sell button will be displayed in the form when a product is selected. The button represents the custom action.
  2. The form Quantity to Sell will be shown when the button is pressed. The form is the view of the confirmation data controller.
  3. The business rule will execute when the Quantity to Sell is confirmed. The rule will reduce the UnitsInStock value of the product by the specified quantity.

Both Touch UI and RESTful API of the backend application will make the new feature known and available to their consumers. Humans will see the Sell button and the prompt to enter the Quantity to Sell. Applications will have access to the sell hypermedia control advertising the new functionality in the products resource singletons.

Start the App Builder, select the RESTful Backend Application project, choose Design, and activate the Controllers tab in the Project Explorer. Follow the instructions to add one action, one confirmation controller, and one business rule to the application.

Action

This is the sell action in the Products / Actions / ag2 (Form) action group:

Action "sell" has the non-standard ID, which makes it available in the RESTful API hypermedia. It triggers the "Custom" command with the "Sell" argument. Actions placed in the group with the "Form" scope are rendered as buttons.
Action "sell" has the non-standard ID, which makes it available in the RESTful API hypermedia. It triggers the "Custom" command with the "Sell" argument. Actions placed in the group with the "Form" scope are rendered as buttons.

The action is rendered as the Sell button in the editForm1 view when a product is selected.

Action "sell" is presented in the "editForm1" as the "Sell" button when a product is selected or edited. The reading pane renders its buttons at the top, while the modal forms will display the buttons at the bottom.
Action "sell" is presented in the "editForm1" as the "Sell" button when a product is selected or edited. The reading pane renders its buttons at the top, while the modal forms will display the buttons at the bottom.

Instructions to configure the action:

  • Expand the Products / Actions / ag2 (Form) node, right-click, and choose the New Action option.
  • Set the Command Name to Custom, enter Sell in the Command Argument, and enter Sell in the Header Text field.
  • Set its When Last Command Name to Any. This will make the action visible regardless of what command was executed last by the state machine of Touch UI.
  • Set When Key Selected to Yes. This will make action available only when a data item is selected. The action will be included in the hypermedia of the singleton resources only.
  • Enter _controller=SellPrompt in the Confirmation property. This will force the framework to create an instance of the specified data controller, which will display its first view to the user. Note that If there is no _controller instruction, then the entire text of Confirmation is displayed in the standard OK / Cancel prompt. RESTful API engine will use the specified confirmation controller to validate the action arguments.
  • Press OK to save the new action.
  • Right-click the Products / Actions / ag2 (Form) / a100 action node and choose Rename. Type in sell and press Enter to rename the node. Actions are included in the hypermedia of the corresponding resource if their identifiers do not start with the letter “a” followed by a number.
  • Drag the action node in front of the a1 to ensure that it is rendered in the first position of its scope.
Multiple actions can trigger the same command. Scope determines the Touch UI element that will trigger the action. Actions may render as form buttons, context menu options, action bar items, etc.

Confirmation Data Controller

This is the confirmation data controller SellPrompt.

The data controller without a command can be used as the action confirmation controller. It has a virtual empty row that serves as the source of data for the fields. The first controller view is displayed with the "New" command by default.
The data controller without a command can be used as the action confirmation controller. It has a virtual empty row that serves as the source of data for the fields. The first controller view is displayed with the "New" command by default.

The form1 view of the controller is displayed when the Sell button is clicked in the product form or when the Sell option is selected in its “more” menu. Users choose the number of units to sell from the radio button list. The Enter Quantity field is displayed if the Other radio button is selected.

The "SellPrompt" controller presents a view with the fields "QuantityMenu" and "Quantity" . The first field is always visible. The second field is visible only when the "Other" radio button is selected.
The "SellPrompt" controller presents a view with the fields "QuantityMenu" and "Quantity" . The first field is always visible. The second field is visible only when the "Other" radio button is selected.

Instructions to configure the confirmation data controller:

  • Click the New Controller button on the toolbar of the Project Explorer.
  • Enter SellPrompt in the Name field and save the controller.
  • Expand the new controller node and right-click on its fields node.
  • Choose the New Field option.
    • Enter QuantityMenu in the Name field.
    • Clear the Allow null values field.
    • Select the Causes Calculate checkbox. The Calculate command will trigger when users change the field value.
    • Set its Items Style to Radio Button List.
    • Press OK to save the new field.
    • Right-click the new field and select New Value/Text Item option.
      • Enter 1 in the Value field.
      • Enter One in the Text field.
      • Save the new item.
    • Add three more static items:
      • Value =5, Text = Five
      • Value = 10, Text = Ten
      • Value = Other, Text = Other
  • Right-click the SellPrompt / Fields node and choose the New Field option.
    • Enter Quantity in the Name field.
    • Set Type to Int32.
    • Clear the Allow null values field.
    • Press OK to save the new field.
  • Right-click the SellPrompt / Views node and choose the New View option.
    • Enter form1 in the Name field.
    • Set Type to Form.
    • Enter the following tag names separated by space in the Tags field:
      • Tag modal-always will make the view to always appear as a modal popup.
      • Tag modal-fit-content will force the view to adjust its height to fit the contents.
      • Tag modal-max-xs will not let the view width to be wider than the extra-small.
      • Tag modal-auto-grow will make the view grow its content when the previously hidden fields become visible.
      • Tag material-icon-sell will display the “sell” material icon in the view header.
    • Enter Quantity to Sell in the Label field.
    • Press OK to save the new view.
  • Ctrl-click the SellPrompt / Fields / QuantityMenu and SellPrompt / Fields / Quantity field nodes.
  • Drag the selected fields onto the SellPrompt / Views / form1 view node.
  • Double-click the SellPrompt / Views / form1 / QuantityMenu data field.
    • Enter lookup-auto-advance tag name in the Tags field. The focus will shift from the tagged data field to the next input when its value is changed. The QuantityMenu data field is the only visible field in the form until its value is set to Other option. The selection of the Other option will reveal the Quantity field. The focus will automatically advance from the QuantityMenu static lookup to Quantity.
    • Enter $blank in the Header Text field. The data field will have no label when rendered.
    • Save the changes.
  • Double-click the SellPrompt / Views / form1 / Quantity field.
    • Enter $row.QuantityMenu == 'Other' in the Visible When field. This JavaScript expression is evaluated whenever the field values are changed. The variable $row represents the object with the properties set to values entered in the form inputs.
    • Save the changes.
  • Right-click the SellPrompt / Business Rules node and choose the New Business Rule option.
    • Set Type to SQL.
    • Enter Calculate in the Command Name field.
    • Set Phase to Execute.
    • Paste the text of the SQL business rule shown below into the Script field.
    • Save the new business rule.

This is the script of the SellPrompt / Business Rules / r100 rule:

SQL
1234if @QuantityMenu != 'Other'
    set @Quantity = @QuantityMenu
else
    set @Quantity = null

The business rule will copy the value of the QuantityMenu field to the Quantity if the value of the former is not equal to the Other constant. Otherwise the Quantity value is cleared. The business rule will execute during the roundtrip initiated by Touch UI when the radio button option is selected in the menu. The Quantity field value is returned from the server to the client and copied into the corresponding input control. Field values entered in the form are referenced as parameters in the anonymous SQL script of the business rule. The script is written in the programming language of the database engine. Transact-SQL is the language of Microsoft SQL Server.

Developers familiar with the client-side programming can avoid the server round trip with the business rule of the JavaScript type. The final call of the preventDefault method in the script is telling Touch UI to stop the default execution flow.

JavaScript
12345if ($row.QuantityMenu !== 'Other')
    $row.Quantity = $row.QuantityMenu;
else 
    $row.Quantity = null;
this.preventDefault();
Data controllers are the building blocks of applications created with Code On Time. The instance of a controller with a command will present a view with the data fetched after its execution. The controller without a command presents a view of an empty row in the “New” mode.

Business Rule

The SQL business rule Products / Business Rules / r100 executes on the server when the OK button is pressed in the Quantity to Sell confirmation. The execution context of the rule is the editForm1 view of the Products data controller.

Business rules of a data controller execute in the order of their declaration when their "Command", "Argument", and "View" are matched to an action. JavaScript rules execute on the client. SQL, Code, and Email rules execute on the server.
Business rules of a data controller execute in the order of their declaration when their "Command", "Argument", and "View" are matched to an action. JavaScript rules execute on the client. SQL, Code, and Email rules execute on the server.

This is the life-cycle of the sell action, SellPrompt confirmation controller, and Products / Business Rules / r100 rule:

  • The action execution flow starts with the confirmation displayed to collect the parameters in the SellPrompt.
  • User enters the quantity to sell and confirms the prompt.
  • The focus returns to the product form.
  • The framework executes the client-side business rules first and passes the action to the server-side code if the default execution flow has not been prevented on the client.
  • The server-side framework searches for the rules to execute the sell action. The definition of the business rule r100 matches the command name and argument of the action. The rule is invoked by the framework.
  • The rule manipulates the action prompt values via the parameters with the @Parameters_ prefix.
  • The server-side framework returns the result of action processing to the client.
  • If an error has been raised or a client-side script has been emitted by the server-side code, then the default user interface flow stops - the active form remains visible, the error is displayed, and the emitted JavaScript is executed.
  • Touch UI navigates the user back to the previous view if there are no errors to display or scripts to execute.

The r100 rule implementation retrieves the UnitsInStock column value from the Products table. If the value of the @Parameters_Quantity is less than the number of available units, then the quantity of available units is reduced. Otherwise the error message is returned.

SQL
123456789101112-- figure the stock in the database
declare @InStock int
select @InStock = UnitsInStock from Products where ProductID = @ProductID
-- decrease the stock if available 
if (@InStock >= @Parameters_Quantity)
    update Products
    set UnitsInStock = UnitsInStock- @Parameters_Quantity
    where ProductID = @ProductID
else
    set @Result_Error = 
        'Not enough quantity in stock to sell ' + 
        cast(@Parameters_Quantity as varchar) + ' units!'

This is the example of the error message when an attempt to sell too many units is detected. The product form remains open.

Errors raised by business rules are presented as notifications above the active view. The reading pane in the screenshot shows the "editForm1" view. The form has been active when the "Sell" action was initiated.
Errors raised by business rules are presented as notifications above the active view. The reading pane in the screenshot shows the "editForm1" view. The form has been active when the "Sell" action was initiated.

Instructions to configure the business rule:

  • Right click the Products / Business Rules node and choose the New Business Rule option.
  • Set Type to SQL.
  • Enter Custom in the Command Name field.
  • Enter Sell in the Command Argument field.
  • Set Phase to Execute.
  • Paste the text of the SQL business rule shown above into the Script field.
  • Save the new business rule.
Business rules are triggered when their command and argument are matched with the custom or built-in action. The rules can be written in JavaScript, SQL, and Code (C# or VB).

Trying it Out

Generate the customized app and try out the sell action in the user interface. The value of the Units In Stock field of the sold product must be reduced after each sale. An error will be raised whenever an attempt to sell too many units is made.

Note that the action, the prompt, and the business rule will operate on the Products page and on the Suppliers page. The former provides direct access to products. The latter requires selecting a product associated with a supplier.

The sell action is also available in the hypermedia of the RESTful API. Touch UI Frontend and RESTful API Engine are providing an interpretation of the data controllers for their respective audiences.

Custom Actions in Singletons

The audience of the RESTful API Engine is represented by the client applications and software developers. The hypermedia is their primary interface.

The hypermedia control of the sell action can be found in the resource singletons of products. Take a look at the following singleton resource sample. The href property of the control is set to /v2/products/19/sell and the method property is set to POST. The number 19 in href is the primary key of the product.

JSON
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263{
    "_links": {
        "self": {
            "href": "/v2/products/19"
        },
        "up": {
            "href": "/v2/products"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        },
        "supplierId": {
            "href": "/v2/products/19/supplier-id",
            "embeddable": true
        },
        "lookup-supplierId": {
            "href": "/v2/suppliers?count=true&fields=supplierId,companyName",
            "embeddable": true
        },
        "categoryId": {
            "href": "/v2/products/19/category-id",
            "embeddable": true
        },
        "lookup-categoryId": {
            "href": "/v2/categories?count=true&fields=categoryId,categoryName",
            "embeddable": true
        },
        "edit": {
            "href": "/v2/products/19",
            "method": "PATCH"
        },
        "replace": {
            "href": "/v2/products/19",
            "method": "PUT"
        },
        "delete": {
            "href": "/v2/products/19",
            "method": "DELETE"
        },
        "sell": {
            "href": "/v2/products/19/sell",
            "method": "POST"
        },
        "schema": {
            "href": "/v2/products/19?_schema=true"
        }
    },
    "productId": 19,
    "productName": "Teatime Chocolate Biscuits",
    "supplierId": 8,
    "supplierCompanyName": "Specialty Biscuits, Ltd.",
    "categoryId": 3,
    "categoryName": "Confections",
    "quantityPerUnit": "10 boxes x 12 pieces",
    "unitPrice": 9.2000,
    "unitsInStock": 19,
    "unitsOnOrder": 0,
    "reorderLevel": 5,
    "discontinued": false
}

Use Postman to execute an authenticated POST request without a body to the URL specified by the sell hypermedia control. The response will include the error message and the resource schema. The error is reported since the required parameters are missing. The schema explains the expected request input and response output.

JSON
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101{
    "error": {
        "errors": [
            {
                "id": "77102da4-3bfb-480c-8168-2c143f7682b4",
                "reason": "invalid_argument",
                "message": "Field 'parameters' is expected in the body."
            }
        ],
        "code": 400,
        "message": "Bad Request"
    },
    "_schema": {
        "_input": {
            "_schema": {
                "parameters": {
                    "required": true,
                    "_schema": {
                        "quantityMenu": {
                            "type": "String",
                            "required": true,
                            "label": "$blank"
                        },
                        "quantity": {
                            "type": "Int32",
                            "required": true,
                            "label": "Enter Quantity"
                        }
                    }
                },
                "*": true
            }
        },
        "productId": {
            "type": "Int32",
            "required": true,
            "key": true,
            "readOnly": true,
            "label": "Product ID"
        },
        "productName": {
            "type": "String",
            "length": 40,
            "required": true,
            "label": "Product Name"
        },
        "supplierId": {
            "type": "Int32",
            "lookup": true,
            "label": "Supplier ID"
        },
        "supplierCompanyName": {
            "type": "String",
            "length": 40,
            "readOnly": true,
            "label": "Supplier Company Name"
        },
        "categoryId": {
            "type": "Int32",
            "lookup": true,
            "label": "Category ID"
        },
        "categoryName": {
            "type": "String",
            "length": 15,
            "readOnly": true,
            "label": "Category Name"
        },
        "quantityPerUnit": {
            "type": "String",
            "length": 20,
            "label": "Quantity Per Unit"
        },
        "unitPrice": {
            "type": "Decimal",
            "default": "((0))",
            "label": "Unit Price"
        },
        "unitsInStock": {
            "type": "Int16",
            "default": "((0))",
            "label": "Units In Stock"
        },
        "unitsOnOrder": {
            "type": "Int16",
            "default": "((0))",
            "label": "Units On Order"
        },
        "reorderLevel": {
            "type": "Int16",
            "default": "((0))",
            "label": "Reorder Level"
        },
        "discontinued": {
            "type": "Boolean",
            "default": "((0))",
            "required": true,
            "label": "Discontinued"
        }
    }
}

If the action has a confirmation, then the parameters field is expected. The _schema.input._schema key makes the engine expect the required parameters field in the request body with the quantityMenu and quantity fields designated inside. These are the fields of the SellPrompt action confirmation controller. The _schema.input._schema.* key is set to true to signify that the body of the request may also include any field of the resource.

The body of the custom action request can include any number of fields of the resource described in the _schema key. For example, the body may include the productName, reorderLevel, and unitsInStock fields. The engine automatically fetchs the singleton resource and treats the field values of the request as the “new” field values when the processing of the action begins. The implementation of a custom action can interpret the “old” and “new” values of the resource fields as needed. There is no default processing.

This the valid request body according to the schema:

JSON
123456{
    "parameters": {
        "quantityMenu": "any text",
        "quantity": 1
    }
}

Post this payload to the URL of the sell action. Repeat the request a few times and observe that the quantityInStock field of the resource is reduced by 1 in each instance.

Custom action of a singleton may have the payload with custom "parameters" along with the optional resource fields. Parameters are automatically derived from the action confirmation controller. The optional resource fields in the request body are the "new" values of the singleton fields.
Custom action of a singleton may have the payload with custom "parameters" along with the optional resource fields. Parameters are automatically derived from the action confirmation controller. The optional resource fields in the request body are the "new" values of the singleton fields.

The utility field QuantityMenu has been introduced to the SellPrompt controller with the sole purpose of speeding up the input of the Quantity field. The Products / Business Rules / r100 rule is making use of the @Parameters_Quantity parameter only and ignores the QuantityMenu field.

The field parameters.quantityMenu is clearly redundant in the body of the request. How do we remove the unused quantityMenu field from the RESTful API while retaining its function in Touch UI?

The rest-api-none tag can be applied to views and data fields to make them invisible in the RESTful API.

Select the backend application project on the start page in Code On Time app builder. Choose Design, and activate the Controllers tab. Select the SellPrompt / Views / form1 / c1 / quantityMenu data field node. Add rest-api-none tag to the Tags and save changes. Generate the application.

Try the POST request to the sell action URL. The engine will let you know that the quantityMenu is not expected anymore

JSON
12345678910111213{
    "error": {
        "errors": [
            {
                "id": "0076be9b-f66a-4c31-a19e-f58ead30cded",
                "reason": "invalid_argument",
                "message": "Unexpected field 'parameters.quantityMenu' is specified in the body."
            }
        ],
        "code": 400,
        "message": "Bad Request"
    }
}

Remove the field quantityMenu from the parameters in the request body. The sell action will execute without errors. The field will also disappear from the sell resource schema.

Custom Actions in Collections

The custom action discussed above is available in the hypemedia of the singleton resources only, since its When Key Selected property is set to Yes. Actions that do not require a selection of an item will be available in the collection resources.

Follow the instructions to configure another custom action:

  • Right-click the Products / Actions / ag3 (ActionBar) - New action group and choose the New Action context menu option.
  • Set the Command Name to Custom.
  • Enter DoSomething in the Command Argument.
  • Enter Do Something in the Header Text.
  • Set When Key Selected to No.
  • Enter material-icon-archive in the Icon / Custom Style.
  • Save the action.
  • Right-click the new action node, choose Rename, and change the rule ID to doSomething.

Here is how the action is presented in Touch UI after the app has been generated. The action is visible in the action bar and in the system context menu whether or not a product is selected. Nothing happens when the action executes, since there are no business rules to handle the action.

System context menu slides out on the right side of the screen when the "More" button is pressed in Touch UI apps. All actions available to the user in the given context are listed in the menu. Action "Do Something" can be activated from the context menu or the page toolbar.
System context menu slides out on the right side of the screen when the "More" button is pressed in Touch UI apps. All actions available to the user in the given context are listed in the menu. Action "Do Something" can be activated from the context menu or the page toolbar.

The interface of apps and developers has also changed. The hypermedia of the products collection resource contains the do-something hypermedia control.

JSON
12345678910111213141516171819202122232425262728293031{
    "_links": {
        "self": {
            "href": "/v2/products"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        },
        "lookup-supplierId": {
            "href": "/v2/suppliers?count=true&fields=supplierId,companyName",
            "embeddable": true
        },
        "lookup-categoryId": {
            "href": "/v2/categories?count=true&fields=categoryId,categoryName",
            "embeddable": true
        },
        "create": {
            "href": "/v2/products",
            "method": "POST"
        },
        "do-something": {
            "href": "/v2/products/do-something",
            "method": "POST"
        },
        "schema": {
            "href": "/v2/products?_schema=true"
        }
    },
    "collection": [
    ]
}

The action is not advertised in the hypermedia of the singleton resources.

Actions with the unspecified value in the When Key Selected property are available in the hypermedia of both singletons and collections.

Try posting to the URI of the do-something hypermedia control. The response will have the collection hypermedia links and nothing else.

JSON
12345678910111213{
    "_links": {
        "up": {
            "href": "/v2/products?count=true"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        }
    }
}

If the x-restful-schema request header is set to only, then the schema will be included in the response. The framework will not attempt to execute the action.

JSON
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798{
    "_schema": {
        "_input": {
            "_schema": {
                "parameters": {
                    "_schema": {}
                },
                "collection": {
                    "array": true,
                    "_schema": {
                        "productId": {
                            "type": "Int32",
                            "required": true
                        }
                    }
                },
                "*": true
            }
        },
        "productId": {
            "type": "Int32",
            "required": true,
            "key": true,
            "readOnly": true,
            "label": "Product ID"
        },
        "productName": {
            "type": "String",
            "length": 40,
            "required": true,
            "label": "Product Name"
        },
        "supplierId": {
            "type": "Int32",
            "lookup": true,
            "label": "Supplier ID"
        },
        "supplierCompanyName": {
            "type": "String",
            "length": 40,
            "readOnly": true,
            "label": "Supplier Company Name"
        },
        "categoryId": {
            "type": "Int32",
            "lookup": true,
            "label": "Category ID"
        },
        "categoryName": {
            "type": "String",
            "length": 15,
            "readOnly": true,
            "label": "Category Name"
        },
        "quantityPerUnit": {
            "type": "String",
            "length": 20,
            "label": "Quantity Per Unit"
        },
        "unitPrice": {
            "type": "Decimal",
            "default": "((0))",
            "label": "Unit Price"
        },
        "unitsInStock": {
            "type": "Int16",
            "default": "((0))",
            "label": "Units In Stock"
        },
        "unitsOnOrder": {
            "type": "Int16",
            "default": "((0))",
            "label": "Units On Order"
        },
        "reorderLevel": {
            "type": "Int16",
            "default": "((0))",
            "label": "Reorder Level"
        },
        "discontinued": {
            "type": "Boolean",
            "default": "((0))",
            "required": true,
            "label": "Discontinued"
        }
    },
    "_links": {
        "up": {
            "href": "/v2/products?count=true&_schema=only"
        },
        "collection": {
            "href": "/v2/products?count=true&_schema=only"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5&_schema=only"
        }
    }
}

The schema of the collection custom action is similar to the schema of the singleton custom action. Optional parameters and field names of the resource may be defined in the request body. There is also the optional collection key that must be specified as an array of objects with the primary key field values. If the collection is not specified or empty, then the server-side framework will execute the matched business rules one time only. If the collection is specified, then the framework will execute the business rules once for each item.

The following SQL business rule will raise an error if the collection of primary keys is not specified. Otherwise it will increase the UnitPrice of each product in the collection by 1 cent.

SQL
123456if @ProductID is null
    set @Result_Error = 'A collection of product identifiers is expected.'
else
    update Products
      set UnitPrice = UnitPrice + 0.01
      where ProductID = @ProductID

Add the rule to the Products data controller in the Project Designer. Make sure that the Command Name is set to Custom, the Command Argument is set to DoSomething, and the Phase is set to Execute. Generate the application. Post the request without a body to the URI of the do-something action. The error message will be returned.

JSON
12345678910111213{
    "error": {
        "errors": [
            {
                "id": "3ac6f887-0953-4743-b6bc-9a09c0d84e85",
                "reason": "method_rejected",
                "message": "A collection of product identifiers is expected."
            }
        ],
        "code": 500,
        "message": "Internal Server Error"
    }
}

The same request with the following body will execute with no errors and change the UnitPrice of the products specified in the collection array.

JSON
12345678910111213{
  "collection": [
    {
      "productId": 1
    },
    {
      "productId": 17
    },
    {
      "productId": 35
    }
  ]
}

Reporting Actions

Actions may return binary data in the response. The server-side code of the application framework has the built-in processing for the Report command. It creates a dynamic RDLC report from the data controller definition and produces the output in Adobe PDF, Microsoft Word, Microsoft Excel, or TIFF format with the help of the Microsoft Report Viewer.

Touch UI fetches the output of "Report" actions and forces the browser to display the "Download" prompt. The native or installed viewers are required on the client to view the reports.
Touch UI fetches the output of "Report" actions and forces the browser to display the "Download" prompt. The native or installed viewers are required on the client to view the reports.

Developers have an option to create the custom reports in RDLC format for the specific data controller views. Custom “Code” business rules may override the default processing and return any type of binary data in the response to the Report action. The third-party tools can be engaged to produce the output.

RESTful API engine will advertise an action with the Report command name and the non-standard identifier in the resource hypermedia. Reporting actions are configured with no value in the When Key Selected property. This makes them available in the hypermedia of both collections and singletons.

Rename the Products / Actions / ag7 / a1 action node to report1 in the Project Designer.

image15.png

Generate the app and locate the report1 hypermedia control in the products collection.

JSON
12345678910111213141516171819202122232425262728293031323334{
    "_links": {
        "self": {
            "href": "/v2/products"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        },
        "lookup-supplierId": {
            "href": "/v2/suppliers?count=true&fields=supplierId,companyName",
            "embeddable": true
        },
        "lookup-categoryId": {
            "href": "/v2/categories?count=true&fields=categoryId,categoryName",
            "embeddable": true
        },
        "create": {
            "href": "/v2/products",
            "method": "POST"
        },
        "do-something": {
            "href": "/v2/products/do-something",
            "method": "POST"
        },
        "report1": {
            "href": "/v2/products/report1"
        },
        "schema": {
            "href": "/v2/products?_schema=true"
        }
    },
    "collection": [
    ]
}

Note that the HTTP method is not specified in the report1 control. Follow the link in Postman and execute an authenticated GET request. The request will fetch the four pages of the PDF document produced by the action.

All items of a collection are included in the report output by default.
All items of a collection are included in the report output by default.

Set the filter parameter of the request to unitPrice > 50 expression. Set the sort parameter to UnitPrice desc value. Both parameters will appear in the URL. Execute the request. One page report of products priced over $50 and sorted in the descending order of the Unit Price will be returned.

Parameters "filter" and "sort" specified in the URL will change the number of items and sort order in the report. If the "Report" action is executed with the POST method then the body of the request may include "parameters" and "collection" fields.
Parameters "filter" and "sort" specified in the URL will change the number of items and sort order in the report. If the "Report" action is executed with the POST method then the body of the request may include "parameters" and "collection" fields.

The same output will be produced if the request method is changed to POST. The reporting actions can be executed with either GET or POST method.

Reporting actions can have their own parameters and include only a specific set of collection items identified by their primary keys. Execute the request with the POST method to specify the parameters and collection fields in the request body.

Action Result

A custom action executed on a singleton will return the resource itself in the response. A custom action executed on a collection will return the hypermedia only. Developers may provide a custom action result for either resource type.

Returning Simple Values

Change the script of the SQL business rule responding to the Custom / DoSomething action in the Products controller as follows:

SQL
1234set @Result_ReturnValue = getdate()
set @UnitPrice = 9.99
set @UnitsInStock = 10
set @ReorderLevel = 5

The script manipulates the resource fields and the special Result_ReturnValue parameter. Execute the POST request to the URL of the do-something hypermedia control. The corresponding values will appear in the response.

JSON
12345678910111213141516171819{
    "_links": {
        "up": {
            "href": "/v2/products?count=true"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        }
    },
    "result": {
        "returnValue": "2022-04-25T18:59:00.0000000",
        "unitPrice": 9.9900000000,
        "unitsInStock": 10,
        "reorderLevel": 5
    }
}

Returning Result Set

If the resource field names and returnValue are not enough to present the action result, then consider producing a result set. The script must set the BusinessRules_EnableResultSet system parameter to 1. This will prepare the framework to expect a rowset to be in the output of the rule.

Change rule script of the do-something action like this:

SQL
12set @BusinessRules_EnableResultSet = 1
select count(*) CountOfShippers, getdate() 'Today' from shippers

The response will include the custom fields countOfShippers and today in the result key.

JSON
1234567891011121314151617{
    "_links": {
        "up": {
            "href": "/v2/products?count=true"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        }
    },
    "result": {
        "countOfShippers": 3,
        "today": "2022-04-25T19:17:29.5570000"
    }
}

Note that the SELECT statement in the rule does not have the FROM clause. Microsoft SQL Server creates a virtual rowset with a single row in this instance. If only one row is returned, then the fields will be embedded directly into the result key.


Change the SELECT statement in the script to select * from shippers. The result key will have the collection of items.

JSON
1234567891011121314151617181920212223242526272829303132{
    "_links": {
        "up": {
            "href": "/v2/products?count=true"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        }
    },
    "result": {
        "collection": [
            {
                "shipperId": 1,
                "companyName": "Speedy Express",
                "phone": "(503) 555-9831"
            },
            {
                "shipperId": 2,
                "companyName": "United Package",
                "phone": "(503) 555-3199"
            },
            {
                "shipperId": 3,
                "companyName": "Federal Shipping",
                "phone": "(503) 555-9931"
            }
        ]
    }
}

The column names in the rowset produced by the rule represent a subset of the collection resource field names, then the result is replaced with the collection key in the response body.

Enter the following as the script of the same business rule and generate the app.

SQL
123456set @BusinessRules_EnableResultSet = 1
select top 3 
    ProductId, 
    ProductName, 
    UnitPrice
from Products

The response to the do-something action will have the collection key instead of the result. The missing fields of the collection resource are included explicitly with the null values.

JSON
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657{
    "_links": {
        "up": {
            "href": "/v2/products?count=true"
        },
        "collection": {
            "href": "/v2/products?count=true"
        },
        "first": {
            "href": "/v2/products?page=0&limit=5"
        }
    },
    "collection": [
        {
            "productId": 1,
            "productName": "Chai",
            "unitPrice": 18.0500,
            "categoryId": null,
            "categoryName": null,
            "discontinued": null,
            "quantityPerUnit": null,
            "reorderLevel": null,
            "supplierCompanyName": null,
            "supplierId": null,
            "unitsInStock": null,
            "unitsOnOrder": null
        },
        {
            "productId": 2,
            "productName": "Chang",
            "unitPrice": 19.0000,
            "categoryId": null,
            "categoryName": null,
            "discontinued": null,
            "quantityPerUnit": null,
            "reorderLevel": null,
            "supplierCompanyName": null,
            "supplierId": null,
            "unitsInStock": null,
            "unitsOnOrder": null
        },
        {
            "productId": 3,
            "productName": "Aniseed Syrup",
            "unitPrice": 10.0400,
            "categoryId": null,
            "categoryName": null,
            "discontinued": null,
            "quantityPerUnit": null,
            "reorderLevel": null,
            "supplierCompanyName": null,
            "supplierId": null,
            "unitsInStock": null,
            "unitsOnOrder": null
        }
    ]
}

Invoking Custom Actions in Client Apps

The CRUD with Hypermedia tutorial explains how to build a plain JavaScript client for the RESTful API Engine of the backend application. It provides a perfect playground to demonstrate the custom actions sell and report1.

Extend the renderProductData function to add the Report button to the product list toolbar. The code snippet of the modified function shows the definition of the button with the yellow background. The button will be enabled if there is the report1 hypermedia in the product list data. The action is advertised in the hypermedia of the products collection resource.

JavaScript
12345678910111213141516171819202122232425262728293031323334function renderProductData() {
    var sb = [
        '<table border="1" cellpadding="5" cellspacing="0">',
        '<tr><th>Product</th><th>Category</th><th>Supplier</th><th>Unit Price</th><th>Units In Stock</th></tr>'];
    for (var i = 0; i < products.collection.length; i++) {
        var p = products.collection[i];
        sb.push('<tr data-index="', i, '">');
        sb.push('<td>', htmlEncode(p.productName), '</td>');
        sb.push('<td>', htmlEncode(p.categoryName), '</td>');
        sb.push('<td>', htmlEncode(p.supplierCompanyName), '</td>');
        sb.push('<td>', htmlEncode(p.unitPrice), '</td>');
        sb.push('<td>', htmlEncode(p.unitsInStock), '</td>');
        sb.push('</tr>')
    }
    sb.push('</table>');
    sb.push('<div class="toolbar">')
    var hypermedia = products._links;
    sb.push('<button data-hypermedia="new"', hypermedia.create ? '' : ' disabled="disabled"', '>New</button>');
    // *************************************************************************
    // The "Report" button with the yellow background
    sb.push('<button data-hypermedia="report" style="background-color:yellow"',
        hypermedia.report1 ? '' : ' disabled="disabled"', '>Report</button>');
    // *************************************************************************
    sb.push('<button data-hypermedia="self"', hypermedia.self ? '' : ' disabled="disabled"', '>Refresh</button>');
    sb.push('<button data-hypermedia="first"', hypermedia.first ? '' : ' disabled="disabled"', '>First</button>');
    sb.push('<button data-hypermedia="prev"', hypermedia.prev ? '' : ' disabled="disabled"', '>Prev</button>');
    sb.push('<button data-hypermedia="next"', hypermedia.next ? '' : ' disabled="disabled"', '>Next</button>');
    sb.push('<button data-hypermedia="last"', hypermedia.last ? '' : ' disabled="disabled"', '>Last</button>');
    var hypermediaArgs = urlArgs(products._links.self);
    sb.push('Page: ', parseInt(hypermediaArgs.page) + 1, ' of ', Math.ceil(products.count / hypermediaArgs.limit),
        ' (', + products.count + ' items)');
    sb.push('</div>');
    document.querySelector('#product-list').innerHTML = sb.join('');
}

The Report button appears right next to the New button in the pager toolbar. A browser prompt to save the PDF file of the report will appear when the button is clicked.

The system browser prompt is displayed when the "Report" button is clicked. Client apps can process the JSON, XML, and YAML responses from the server. The application engine can also stream out the binary data, such as PDF reports or images.
The system browser prompt is displayed when the "Report" button is clicked. Client apps can process the JSON, XML, and YAML responses from the server. The application engine can also stream out the binary data, such as PDF reports or images.

Change the readProduct function to add the Sell button to the Edit Product form. The button will appear next to the Update button if there is the sell hypermedia link in the obj instance of the fetched product.

JavaScript
12345678910111213141516171819function readProduct(selectedIndex) {
    $app.restful({
        url: products.collection[selectedIndex]._links.self
    }).then(obj => {
        showForm({
            title: 'Edit Product',
            from: obj,
            buttons: [
                { text: 'Update', hypermedia: 'edit' },
                { text: 'Sell', hypermedia: 'sell' }, // the "Sell" button definition
                { text: 'Delete', hypermedia: 'delete' }
            ]
        }).then(form => {
            form.fields.forEach(f => {
                form.elem.querySelector('[data-field="' + f.name + '"]').value = obj[f.name];
            });
        });
    })
}

The browser confirmation will appear when the button is clicked.

A click on the "Sell" button will execute a sale of one product unit.  If the sale has failed, then the product form will remain visible and an alert with the error message will be displayed.
A click on the "Sell" button will execute a sale of one product unit. If the sale has failed, then the product form will remain visible and an alert with the error message will be displayed.

Finally, modify the handleHypermedia function to include the implementation of the sell and report actions.

JavaScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061function handleHypermedia(e) {
    var row = e.target.closest('[data-index]')
    if (row && products)
        readProduct(row.getAttribute("data-index"));
    else {
        var btn = e.target.closest('[data-hypermedia]');
        if (btn && products) {
            var hypermedia = btn.getAttribute('data-hypermedia');
            switch (hypermedia) {
                case 'sell':
                    // invoke custom action "sell"
                    if (confirm('Sell?'))
                        $app.restful({
                            url: showForm.sell,
                            body: {
                                "parameters": {
                                    "quantity": 1
                                }
                            }
                        })
                            .then(() => {
                                hideForm();
                                refreshProductList(products._links.self);
                            })
                            .catch(restfulException);
                    break;
                case 'report':
                    // invoke action "report1"
                    $app.restful({
                        url: products._links.report1,
                    }).then(blob => {
                        let dataUrl = URL.createObjectURL(blob);
                        let a = document.createElement('a');
                        a.setAttribute('download', blob.name);
                        a.setAttribute('href', dataUrl);
                        a.click();
                        URL.revokeObjectURL(dataUrl);
                    });
                    break;
                case 'new':
                    newProduct();
                    break;
                case 'create':
                    postProduct();
                    break;
                case 'edit':
                    patchProduct();
                    break;
                case 'delete':
                    deleteProduct();
                    break;
                case 'cancel':
                    hideForm();
                    break;
                default:
                    refreshProductList(products._links[hypermedia]);
                    break;
            }
        }
    }
}

The function will respond to the click on the Sell button by invoking the $app.restful method with the url argument set to the hypermedia control of the sell action. The control is the property of the showForm function used to display the selected product item. The body argument specifies the custom action parameters with the quantity of 1. The implementation will “sell” a single product unit only. The form is closed if the product was sold successfully and the current page of the product list is refreshed.

The function will respond to the click on the Report button by invoking the $app.restful method with the url argument set to the report1 hypermedia control found in the product list data. If the response content type is not JSON, XML, or Yaml, then the $app.restful method will convert the response data into a Blob object. The then chain function expects the binary content and creates an Object URL from the blob in the argument. The URL is assigned to a dynamically created link element. The link is clicked programmatically to cause the Save File prompt of the browser to appear.

Next

Once in a while the client apps will need to submit the binary data to the backend application or have it fetched and presented to users. Multiple fields of the Binary Large Object type may be a part of a resource. Learn how to specify the BLOBs as parameters of the $app.restful method and in the raw HTTP requests.

Tuesday, March 15, 2022PrintSubscribe
RESTful Workshop

This workshop is designed to empower both individual developers and teams to create amazing enterprise-quality backends for mobile and web applications. Zero experience and no code is required to provide your data with the REST API conforming to the Level 3 of Richardson Maturity Model with the built-in OAuth 2.0 and Open ID Connect.

  • This workshop will help you understand what the RESTful API is, what are the benefits of the RESTful Hypermedia, and how to build the RESTful applications with Code On Time.
  • You will use Code On Time application builder to rapidly produce an inventory management system with a sophisticated user interface that runs on mobile and desktop devices. No coding experience is required.
  • You will enable the RESTful API Engine and build three single-page applications (SPA) embedded directly into the inventory manager. These apps will feature a custom user interface blending in the backend. The apps will invoke either the hypermedia or the direct resource URLs to interact with the API. JavaScript and HTML experience required.
  • You will enable OAuth 2.0 Authentication in the backend and build two standalone SPAs that can be hosted anywhere. Both apps will rely on “Authorization code flow with PKCE” to authenticate the users. The apps will use the OpenID Connect (OIDC) to get the user email and picture from JWT. The apps will make use of the hypermedia or direct resource URLs to get data from the backend. JavaScript and HTML experience required.

Lesson 1: RESTful Backend Application

The workshop begins with the implementation of an inventory management system and includes the step-by-step instructions. Your own application will look similar to the one shown in the picture below. It will have an advanced user interface that allows users to manage categories, suppliers, and products.

Reading Pane split view in the application created with Code On Time.
Reading Pane split view in the application created with Code On Time.

Start the workshop with the RESTful Backend Application segment.

You may be compelled to finish right there when you see the end result of your work. It will be that good! Please do not stop and keep going.

At the end of the segment the application configuration will be changed with a few keystrokes to enable the built-in RESTful API Engine. You will learn to use the API development tools to browse the hypermedia links of the RESTful API.

Hypermedia makes your API easy to learn and consume. Welcome to the RESTful World!

An example of the RESTful resource data with the embedded hypermedia.
An example of the RESTful resource data with the embedded hypermedia.

RESTful Hypermedia of the backend application is easy to discover with the help of API development tools.

Keep reading to learn more about building the embedded and standalone client apps for the RESTful backend.

Lesson 2: Embedded SPA1 with RESTful Hypermedia

The first client of the RESTful API is the Single Page Application embedded directly in the backend and protected by its security system. Users must sign in to access the page. The page will blend into the inventory manager user interface and allow paging of the products by invoking the hypermedia controls of the API.

Embedded SPA1 with RESTful Hypermedia
Embedded SPA1 with RESTful Hypermedia

Start building your first Embedded SPA1 with RESTful Hypermedia.

Lesson 3: Embedded SPA2 with REST Level 2

This tutorial will explain how to alter the SPA1 to invoke the resource URLs of the RESTful API directly. This embedded app will look just like its cousin that makes use of the hypermedia.

The implementation of the product paging will become a little more complex and require a few extra lines of code since the paging logic will have to be duplicated in the client app itself.

In contrast, the hypermedia-enabled client is simpler since it relies on the hypermedia controls (API links) embedded directly in the data received from the server. The hypermedia makes it very simple for client app developers to alter the interface and respond to the user actions.

Discover the benefits of hypermedia in the Embedded SPA2 with REST Level 2 segment by learning how to live without it!

Lesson 4: Embedded SPA3 (Custom UI) with RESTful Hypermedia

Both SPA1 and SPA2 are simple pages hosted by the backend application (the inventory manager). The apps blend seamlessly in the user interface of the host and invoke its RESTful API Engine through hypermedia or resource URLs.

Our next embedded Single Page Application features a completely custom user interface while being hosted by the backend. In fact, there will be no user interface of the host application whatsoever. The page will remain protected by the security system of the host.

You will learn how to disable the user interface of the host (shhh - very easy) while still taking advantage of the built-in RESTful API Engine with hypermedia.

Embedded SPA3 (Custom UI) with RESTful Hypermedia
Embedded SPA3 (Custom UI) with RESTful Hypermedia

Learn how to create custom landing pages, dashboards, and single page apps embedded in the API host while following the instructions in the Embedded SPA3 (Custom UI) with RESTful Hypermedia segment.

Lesson 5: Standalone SPA4 with RESTful Hypermedia and OAuth 2.0

The purpose of this tutorial is to create a standalone Single Page Application that uses the Authorization Code Flow with PKCE to authenticate the end users. This app will allow paging the inventory products by taking advantage of the RESTful Hypermedia similar to the embedded SPA1 and SPA3. This client app can be hosted on any web server of your choice.

You will begin by changing the configuration of the RESTful Backend Application to limit the user interface availability to the Administrators only and to enable OAuth 2.0.

You will learn how to register a client app for OAuth 2.0 in the backend.

You will create the login and logout capabilities and learn how to obtain the email address and picture of the authenticated user from JSON Web Token (JWT).

This is how the app looks when users first load it up in the browser.

The user interface of anonymous user in the standalone SPA4 with RESTful Hypermedia and OAuth 2.0
The user interface of anonymous user in the standalone SPA4 with RESTful Hypermedia and OAuth 2.0

Login button will redirect to the backend application and require users to sign in and authorize the account access.

Account Access consent form presented to the user during the OAuth 2.0 Authorization Code flow with PKCE in the application created with Code On Time.
Account Access consent form presented to the user during the OAuth 2.0 Authorization Code flow with PKCE in the application created with Code On Time.

If the account access is allowed, then the backend will redirect back to the client app SPA4 with the authorization code in the URL. The client app will exchange the authorization code for an access token and use the token when making requests to the backend.

The user interface of authenticated user in the standalone SPA4 with RESTful Hypermedia and OAuth 2.0
The user interface of authenticated user in the standalone SPA4 with RESTful Hypermedia and OAuth 2.0

The tutorial shows a typical hybrid app based on HTML and JavaScript. API calls are illustrated with the HTTP requests. Native app developers will get a clear understanding of how to implement the user authentication with OAuth 2.0 authorization flows built into the backend of applications created with Code on Time.

You will become ready to build client apps with your favorite user interface framework and access data with hypermedia or traditional REST.

Start the Standalone SPA4 with RESTful Hypermedia and OAuth 2.0 segment.

Lesson 6: Standalone SPA5 with REST Level 2 and OAuth 2.0

OAuth 2.0 Authorization is a set of REST APIs embedded into the backend of applications created with Code On Time. These APIs are implemented on top of the same RESTful API Engine, which speaks to its strength and flexibility. There are no 3rd party dependencies or runtime fees!

The user interface of authenticated user in the standalone SPA5 with REST Level 2 and OAuth 2.0
The user interface of authenticated user in the standalone SPA5 with REST Level 2 and OAuth 2.0

Learn to sign the users in and out in Standalone SPA5 with REST Level 2 and OAuth 2.0 without relying on the hypermedia.

This client app is the look-alike of the SPA4. It works with the REST resources directly. The HTTP methods and URLs are embedded in the code. If you have fallen in love with the hypermedia, then you may find it as a disadvantage. If you are not convinced and prefer the traditional REST, then use this client app as the foundation for your next hybrid or native application.

Lesson 7: CRUD with Hypermedia

Typical CRUD operations are performed on REST resources with POST, GET, PATCH, PUT, and DELETE methods of HTTP protocol. The uniform hypermedia controls create, edit, replace, and delete are embedded in the resource data.

The reader will modify the SPA4 app to allow creating, editing, and deleting the products.

Single page app SPA4 with RESTful Hypermedia and CRUD has the "New" button in the toolbar. The "New Product" form is displayed when the button is pressed. A click on any row in the product grid will open the "Edit Product" form allowing users to change or delete the product.
Single page app SPA4 with RESTful Hypermedia and CRUD has the "New" button in the toolbar. The "New Product" form is displayed when the button is pressed. A click on any row in the product grid will open the "Edit Product" form allowing users to change or delete the product.

Users can make changes to the product or delete it from the list.
Users can make changes to the product or delete it from the list.

Learn to Create, Read, Update, and Delete data with confidence.

Lesson 8: Custom Actions with Hypermedia

The REST API Level 2 and above must support HTTP methods (verbs) in resource handling. For example, the request with the GET method is expected to return the resource data while the request with the PATCH method is expected to modify some of its fields. The number of HTTP verbs is limited and will not cover all possible scenarios of data manipulation.

Custom action identifiers can be specified directly in the resource URLs requested with the POST method. The custom action hypermedia controls are embedded in the data by the RESTful API Engine.

Developers evolve the application API by making changes to the data controllers and their models. Both Touch UI and RESTful API are reflecting the configuration and capabilities of the application data controllers.

Confirmation controller "SellPrompt" presents the user with an option to sell 1, 5, or 10 units of a product with a single touch. Option "Other" allows entering the arbitrary quantity of units to sell. The prompt is displayed when users press the "Sell" button in the product form.
Confirmation controller "SellPrompt" presents the user with an option to sell 1, 5, or 10 units of a product with a single touch. Option "Other" allows entering the arbitrary quantity of units to sell. The prompt is displayed when users press the "Sell" button in the product form.

RESTful API Engine makes the custom action "sell" available to the client apps.  The parameters of the action are specified in the "parameters" key of the payload. The new state of the resource is returned in the response.
RESTful API Engine makes the custom action "sell" available to the client apps. The parameters of the action are specified in the "parameters" key of the payload. The new state of the resource is returned in the response.

Enhance the RESTful API of applications with custom actions to go beyond CRUD.

Lesson 9: BLOBs

Values of Binary Large Objects (BLOBs) are represented as resource URLs in the data. User identity must be specified in the Authorization header to get or change a BLOB. Hypermedia links to replace or delete BLOB values are included. Developers submit binary data as fields in the body property of the $app.restful method argument or as the named values in the multipart/form-data of HTTP requests.

Work with the BLOB resources like a pro.

Lesson 10: Resource Navigation with Hypermedia

Hypermedia helps developers and client applications to discover the available actions and related data of a resource. The RESTful API Engine evaluates the user identity, assesses the HTTP method, and consults the Access Control List (ACL) to produce the resource data. The engine performs the same rigorous checks when embedding the relevant hypermedia links directly in the output.

Resources may return singleton entities or collections of entities. The latter may be filtered with conditional expressions and query parameters with optional sorting.

Master the navigation and filtering of the hypermedia-enabled resources.

Lesson 11: No More Under-Fetching or Over-Fetching

Modern client applications require complex data to be fetched from the server. The RESTful resources provide the singletons and collections of uniform entities. The resources are easy to fetch. The hypermedia embedded in the resource entities allows the subsequent fetching of the related data.

Client application developers will love the hypermedia but hate the work required to arrange the multiple fetch requests. Also the resources may be too rich for one's taste when only a couple of fields are needed but dozens of entity properties are always included in a response. Developers had to invent the query language GraphQL to fight both the under-fetching and over-fetching of data typical for the Web APIs!

RESTful API Engine supports the fields and _embed parameter to allow fetching a complex data of a specific shape in a single request while still taking advantage of HTTP caching, which is not possible with GraphQL.

Get the data without Under-Fetching and Over-Fetching with hypermedia.

Lesson 12: JWT, OAuth 2.0 Scopes, Roles, and ACL

JSON Web Tokens (JWT) provide the industry-standard for representing claims securely between two parties. The built-in scopes make it possible for the client apps to continuously refresh the access tokens and to request specific identity claims with the explicit user permission. Custom scopes provide granular access to the RESTful resources. Built-in Access Control List (ACL) associates specific HTTP verbs and custom actions with the scopes. ACL will instruct the RESTful engine to allow specific HTTP verbs and resource action only. The embedded hypermedia is also restricted according to the ACL rules.

Combine the scopes, roles, and ACL for granular resource access.

Lesson 13: Input and Output Content Types

Built-in RESTful API Engine handles multiple input and output content types. Developers use the Content-Type and Accept headers to request and receive data in JSON, Yaml, XML, and a few other formats.

It is simple to incorporate a secure export to CSV (comma-separated values) in the RESTful apps. The standard header "Accept" directs the RESTful API Engine to produce data in the desired format.
It is simple to incorporate a secure export to CSV (comma-separated values) in the RESTful apps. The standard header "Accept" directs the RESTful API Engine to produce data in the desired format.

Discover the data languages of the RESTful API Engine.

Lesson 14: ETag and HTTP Caching

RESTful API Engine responses are provided with the ETag header. Browsers automatically submit ETag with the subsequent requests to fetch the same resource. The engine responds with the HTTP status 304 and empty body if the ETag of the resource has not changed. Apps can also submit ETag with CRUD requests for conflict detection.

ETag allows preventing the physical transfer of the unchanged resources but still requires some processing on the server. The backend application framework and the RESTful engine can be instructed to cache the resource response in the server memory and in the persistent storage. The engine will include the HTTP client caching instructions in the responses, which will allow browsers to completely eliminate the need to contact the server for a period of time.

Build high performance applications by taking advantage of the HTTP protocol.

RESTful Apps with Code On Time

Startups, IT departments, hobbyists, and students will find Code On Time to be a powerful and easy-to-use tool that allows instant production of high quality mobile and web applications with an amazing Touch UI interface.

Built-in RESTful API Engine makes it possible to extend the applications with the embedded pages that blend in or have a completely custom look. Applications can participate in complex orchestrations through cloud functions and scripts calling into the API resources thanks to the built-in support for API keys.

Built-in OAuth 2.0 Authentication turns an application created with Code On Time into the backend of your next big thing! Do not hire hordes of backend developers and pay no runtime fees.

Get Code On Time application builder now!