RESTful Adapter Service
Documentation about an example of a RESTful api service that consumes a source adapter.
1. Purpose of the document
This documentation contains an overview of the Visual Studio solution and descriptions of all REST service calls. Additional to all calls an example request and response as json objects are explained and displayed.
2. Visual Studio Solution for REST Service
Content of the solution:

The VS solution consists of three projects and one configuration:
TivityPluginService: WebAPI project, the actual plugin REST service
Tivity.Plugnis.Dummy: a example adapter serves inside the WebAPI as fallback adapter
Tivity.Plugins.RestService.Test: UnitTests for the REST Service project
docker-compose: the docker configuration, it is possible to start the service
in the container out of the service.
After you build the solution, the service starts and the homepage appears in the browser from the plugin service. The start page is an openAPI documentation like swagger.

3. Plugin Service Project
This is a project created as a VS WebAPI project based on .NET core 2.1 and serves as the API interface for an adapter instance. This project is intended as example code for a REST Service which the TIVITY platform can communicate with. Of course, this service can also be used productively and a specially developed adapter connect and wrap. This adapter should be available as a Microsoft .NET assembly.
NOTE It can also be used a special developed adapter (e.g. Java). This must be identical to the interface used in the example service.

The service basically consists of the API controllers and the wrapper class. The controllers are the interfaces to "outside" while the wrapper ensures the communication "inside", namely to the adapter. In addition, there are the usual Startup and program classes for the service, which are used for the entry point. Further configurations are stored in the Dockerfile and appsettings.json.
3.1 SourceController
The SourceController is a important API controller for the communication with the SourceAdapter. There, the SourceAdapter interfaces (ISourceAdapter, ISource) are implemented. These interfaces provide the configuration and adapter instantiation as API calls.
The interface for the adapter is defined like this:
public interface ISourceAdapter
{
Task<SourceResult<AdapterDefinition>> Define(AdapterDefineRequest request);
Task<SourceResult<ConfigurationStep>> Config(AdapterConfigLaunchRequest request);
Task<SourceResult<ConfigurationStep>> Config(AdapterConfigHandleRequest request);
Task<SourceResult<ISource>> Connect(AdapterConnectRequest request);
}
public interface ISource
{
Task<SourceResult<SourceCapabilityMap>> GetCapabilityMap(SourceCapabilityRequest request);
TCapability GetCapability<TCapability>() where TCapability : ISourceCapability;
}
The corresponding methods in the API Controller look very similar (simplified method body):
public async Task<ActionResult<SourceResult<AdapterDefinition>>> Define(AdapterDefineRequest request)
{
SourceResult<AdapterDefinition> result = await AdapterWrapper.Instance.Define(request);
return result;
}
public Task<SourceResult<ISource>> Connect(AdapterConnectRequest request)
{
return AdapterWrapper.Instance.Connect(request).Result;
}
public async Task<ActionResult<SourceResult<ConfigurationStep<ParameterStep>>>> Config()
{
SourceResult<ConfigurationStep> result = await AdapterWrapper.Instance.Config(new AdapterConfigLaunchRequest());
return new SourceResult<ConfigurationStep>(result.Value as ConfigurationStep<ParameterStep>);
}
public async Task<ActionResult<SourceResult<ConfigurationStep>>> Config([FromBody] AdapterConfigHandleRequest value)
{
SourceResult<ConfigurationStep> result = await AdapterWrapper.Instance.Config(value);
return new SourceResult<ConfigurationStep>(result.Value as ConfigurationStep<ParameterStep>);
}
public async Task<ActionResult<SourceResult<SourceCapabilityMap>>> GetCapability()
{
return await AdapterWrapper.Instance.GetCapabilityMap(new SourceCapabilityRequest());
}
Except for Connect, all methods are asynchronous. And it is noticeable that another ActionResult type is given as a template. This is a standard Microsoft return type ActionResult<T>
for asp.net core. and was just added for comfortable response generation. This type is not necessary for your own service. Otherwise the methods correspond to those of the interface ISourceAdapter.
Source / Connect
POST
https://adapterservice/api/source/connect
The connect method configured the adapter inside the service. The request consists a bulk of configurations in JSON format or what else.
Request Body
json object
string
{
"configuration": "string"
}
{
"messages": []
}
Request example for a configuration of user and password:
{
"configuration": "{\"User\":\"test\",\"Password\":\"sd2rwe\"}"
}
Source / Define
POST
https://adapterservice/api/source/define
The define method returns the basic information of the adapter. Properties are name and description. Additionally a thumbnail can be added. The request for define method can also be empty.
Request Body
json object
string
Empty request body.
{
"value": {
"name": {
"de-DE": "Basis Adapter",
"en-EN": "Basic Adapter"
},
"description": {
"de-DE": "Adapter zum testen der Schnittstelle.",
"en-EN": "Adapter for testing the interface."
},
"thumbnail": {}
},
"messages": []
}
Source / Config
GET
https://adapterservice/api/source/config
Starts the configuration process. Returns a configuration step with initial parameters. The parameters represent a form input on the platform configuration.
Request Body
json object
string
Empty request body.
{
"value": {
"key": "string",
"step": {
"parameters": [
{
"key": "string",
"type": 0,
"name": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
},
"description": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
},
"error": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
},
"value": {},
"values": [
{
"key": "string",
"name": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
],
"required": true
}
]
}
}
}
Source / config
POST
https://adapterservice/api/source/config
Handle the configuration process. In the request object AdapterConfigHandleRequest a list of configuration parameters are send to the service. This parameters are the input values from a configuration form.
Request Body
json object
string
{
"key": "string",
"parameters": {
"additionalProp1": {},
"additionalProp2": {},
"additionalProp3": {}
}
}
{
"messages": []
}
In the adapter source you can now validate this parameter values. If the values are valid you returned a success step. Otherwise you send a configuration step back, similar the start conf method (further up). But at this time with error messages in failed parameter objects.
Source / Capability
GET
https://adapterservide/api/source/capability
Get a list of capabilities which are supported by the source adapter (service).
Path Parameters
json object
string
Empty request body.
{
"value": {
"capabilities": [
0,
6,
5,
3
]
},
"messages": []
}
The available capability types ar listed under the ISource topic.
3.2 ActionController
For the adapter call with the method Execute, an ActionController exist with the same method. This controller has only this one method as well.
public async Task<ActionResult<SourceResult<object>>> Execute([FromBody] ActionExecuteRequest value)
{
return await AdapterWrapper.Instance.Execute(value);
}
There are three properties in the ActionExecuteRequest.
parameter name
type
description
ClassName
str
Name of the class where action executed (optional)
ActionName
str
Name of the action, it could be multiple actions in adapter
Parameters
Dictionary
List of object, comparable with method parameters (key=param-name, value=object)
Action / Execute
GET
https://adapterservice/api/action/execute
Request Body
json object
string
example:
{
"className": "myClass",
"actionName": "myAction",
"parameters": {
"parameter1": "myValue",
"parameter2": { 3, 5 },
"parameter3": { "text 1", "text2" }
}
}
{
"value": {
"object as serialized string",
},
"messages": []
}
In this example response the object is a translation object, in property "value" (JSON):
{
"value": {
"en-EN": "Result text. This is the request: 'XXy'.",
"de-DE": "Ergebnis Text. Hier ist der Request: 'XXy'."
},
"messages": []
}
3.3 SchemaController
The SchemaController has only one Method "Get" (HTTP POST). This method returns a list of schema definitions.
public async Task<ActionResult<SourceResult<Schema>>> Get(SchemaGetRequest request)
{
return await AdapterWrapper.Instance.Get(request);
}
The SchemaGetRequest object is currently empty. For more information about the response object see the schema capability documentation.
Schema / Get
POST
https://adapterservice/api/schema/get
Method for getting schema definition information.
Request Body
json object
string
empty request body.
{
"value": {
"classes": [
{
"name": "DummyClass",
"fields": [
{
"name": "FieldStr",
"length": 20,
"defaultValue": null,
"isNullable": false,
"nameIntern": "intern-FieldStr",
"dataSourceOption": null,
"isReadOnly": false,
"dataType": 2
},
{
"name": "FieldDate",
"length": 20,
"defaultValue": null,
"isNullable": false,
"nameIntern": "intern-FieldDate",
"dataSourceOption": null,
"isReadOnly": false,
"dataType": 3
},
{
"name": "FieldInt",
"length": 20,
"defaultValue": null,
"isNullable": false,
"nameIntern": "intern-FieldInt",
"dataSourceOption": null,
"isReadOnly": false,
"dataType": 4
},
{
"name": "Created",
"length": 20,
"defaultValue": null,
"isNullable": false,
"nameIntern": "intern-Created",
"dataSourceOption": null,
"isReadOnly": false,
"dataType": 3
}
]
}
],
"links": [
{
"name": "#lnk-002c",
"primaryClassName": "ClVehicle",
"primaryFieldName": "TestField-ClVehicle-8",
"foreignClassName": "ClMotor",
"foreignFieldName": "TestField-ClMotor-2"
}
]
},
"messages": []
}
3.4 QueryController
The QueryController has only one method and passes the request through to the adapter.
public async Task<ActionResult<SourceResult<ModelList>>> Execute(QueryExecuteRequest request)
{
return await AdapterWrapper.Instance.Execute(request);
}
The QueryExecuteRequest object needs a Query with the class ("From"), the columns and a list of conditions. Optional we can add Joins and Paginations. You find a detailed description of the individual properties in the SourceAdapter Documentation.
Query / Execute
POST
https://adapterservice/apiquery/execute
Request Body
json object
string
serialized query object. See example below.
{
"value": {
"models": [
{
"additionalProp1": { "property value" },
"additionalProp2": {},
"additionalProp3": {}
}
],
"total": 1
}
}
! Important: specify the expression type in json request.
Example request (json): For more information about the Query object see the SourceAdapter Documentation.
{
"Query": {
"From": {
"ClassName": "TestClass",
"Alias": "c"
},
"Joins": null,
"Columns": [
{
"Expression": {
"$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
"ClassAlias": null,
"FieldName": "Field1"
},
"Alias": null
},
{
"Expression": {
"$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
"ClassAlias": null,
"FieldName": "Field3"
},
"Alias": null
}
],
"Conditions": [
{
"LeftParentheses": 1,
"LeftExpression": {
"$type": "Tivity.Plugins.Models.ExpressionQuery, Tivity.Plugins",
"From": {
"ClassName": "TestClass",
"Alias": "c"
},
"Joins": null,
"Column": {
"Expression": {
"$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
"ClassAlias": null,
"FieldName": "Field1"
},
"Alias": null
},
"Conditions": null
},
"IsNegated": false,
"Operator": 5,
"RightExpression": {
"$type": "Tivity.Plugins.Models.ExpressionValue, Tivity.Plugins",
"Value": 3
},
"RightParentheses": 1,
"LogicalOperator": 1
},
{
"LeftParentheses": 1,
"LeftExpression": {
"$type": "Tivity.Plugins.Models.ExpressionQuery, Tivity.Plugins",
"From": {
"ClassName": "TestClass",
"Alias": "c"
},
"Joins": null,
"Column": {
"Expression": {
"$type": "Tivity.Plugins.Models.ExpressionField, Tivity.Plugins",
"ClassAlias": null,
"FieldName": "Field2"
},
"Alias": null
},
"Conditions": null
},
"IsNegated": true,
"Operator": 0,
"RightExpression": {
"$type": "Tivity.Plugins.Models.ExpressionValue, Tivity.Plugins",
"Value": "mySearchTxt"
},
"RightParentheses": 1,
"LogicalOperator": 0
}
],
"Pagination": null
},
"Credentials": null,
"TransactionKey": null
}
3.5 ModelController
For manipulate an instance (or model) the ModelController is responsible. This controller has three methods: create, update and delete.
public async Task<ActionResult<SourceResult<Model>>> Create(ModelCreateRequest request)
{
return await AdapterWrapper.Instance.Create(request);
}
public async Task<ActionResult<SourceResult>> Update(ModelUpdateRequest request)
{
return await AdapterWrapper.Instance.Update(request);
}
public async Task<ActionResult<SourceResult>> Delete(ModelDeleteRequest request)
{
return await AdapterWrapper.Instance.Delete(request);
}
For more information about the model methods create, update and delete see the SourceAdapter Documentation about ModelCapability.
Model / Create
POST
https://adapterservice/api/model/create
To create a new instance of a class, the create method can be used.
Request Body
json object
string
The request object needs a class name of an existing class and the new instance as a list of key-value pairs.
{
"className": "myClass",
"model": {
"property1": "value1",
"property2": "value2"
}
}
{
"value": {
"FieldStr": "value1",
"FieldInt": 5,
"FieldDate": "2019-07-29T17:16:42.9744742+02:00",
"Created": "2019-07-29T18:05:22.0143352+02:00"
},
"messages": []
}
Model / Update
PUT
https://adapterservice/api/model/update
To manipulate an existing instance usually the update method is used. The ModelUpdateRequest needs the class name and a key for identify the model and the instance itself with all changed properties.
Request Body
json object
string
- className: underlying class of the instance
- key: instance identifier
- model: properties which are updated
{
"className": "myClass",
"key": {
"propertyId": "identifier"
},
"model": {
"property1": "newValue1",
"property2": "newValue2"
}
}
{
"messages": []
}
Model / Delete
DELETE
https://adapterservice/api/model/delete
For delete an instance (model) call the delete method. The request needs the class name and a key for identify the instance.
Path Parameters
json object
string
- className: underlying class of the instance
- key: instance identifier
{
"className": "myClass",
"key": {
"propertyId": "identifier"
}
}
{
"messages": []
}
3.6 DocumentController
In order to obtain a document you have to use the Download method. As a result, only the content of a document is returned. This is a (byte) stream with the content type information in response header.
public async Task<IActionResult> Download(DocumentDownloadRequest request)
For better understanding of the method and the request object see chapter IDocumentCapability in SourceAdapter.
Document / Download
POST
https://adapterservice/api/document/download
For download a content you need to send the information about the class and a document identifier. These values are set in the DocumentDowloadRequest. The class name is useful to assign the document content to a certain class. The key value is required to uniquely identify a document. This identifier can be a unique name, a path or a GUID as well.
Request Body
json object
string
- className: name of the assigned class (optional)
- key: unique identifier (requred)
{
"className": "NameOfClass",
"key": "identifier"
}
Example request (json):
{
"className": "DummyDocumentClass",
"key": "75a1bec0-c885-41c7-a93b-59e11441eac5"
}
3.7 FileUpload-StorageController
When files need to be uploaded, the StorageController is also required. This controller ensures that the file stream and an identity key (FileId) are received on server site. The REST service provides for storage the file contents (streams) e.g. save on disk, in database or on memory cache.
public async Task<ActionResult<SourceResult>> Upload([FromForm]StoragePutRequest request)
Storage / Upload
POST
https://adapterservice/api/storage/upload
The upload request is a multipart/form-data content-type which consists of the file stream and the formdata FileId.
Request Body
FileId
string
unique identifier of the file
(file) stream
object
the file stream
{
"messages": []
}
During the upload process the platform additionally trigger an execute call (see 3.2 ActionController). There is an entry in the 'parameters' dictionary with the file metadata or a list of files. For Details see the SourceAdapter documentation.
Example execute request for a list of files (json):
{
"parameters": {
"parameter1": [
{
"FileId": "40765ec8-492f-47ed-9797-f9ff5dc35041",
"MimeType": "text/plain",
"FileName": "example01.txt"
},
{
"FileId": "21a3b5da-fd63-4b4c-be43-87a8a07121e2",
"MimeType": "text/plain",
"FileName": "example02.txt"
}
]
}
}
Upload asynchronous calls as sequence diagram:

It is essential for success uploading files that the upload method exist in the REST service and the adapter capability IActionCapability with the execute method was implemented as well. This execute method must be able to process the file parameters accordingly (see example request above).
Last updated
Was this helpful?