-
Notifications
You must be signed in to change notification settings - Fork 14
Tutorial 04 08 Add Helper Service Class

For each routine that you expose through Traditional Bridge on the traditional Synergy side, you will need to write a client-callable wrapper method back in Harmony Core on the .NET side. Ultimately you're going to be exposing your routines as endpoints of your web service. Or put another way, you're going to be exposing them as Methods in a Controller class in your Services.Controllers project. So that's where we'll be creating the wrappers.
To get started you will need to create a class that inherits from the class Harmony.Core.Context.DynamicCallProvider. This class contains the types and code necessary to be a client to a Traditional Bridge host application, by marshaling method calls into JSON-RPC requests, sending the request, receiving and decoding the response, and then handing appropriate response data back to the calling routine. For the purposes of this tutorial, we will name this class TraditionalBridgeService.
-
In
Solution Explorer, right-click on theServices.Controllersclass and selectAdd > Class. -
Name the new class source file
TraditionalBridgeService.dbland click theAddbutton. -
Copy and paste the following code into the new class, replacing the default code:
;;***************************************************************************** ;; ;; Title: TraditionalBridgeService.dbl ;; ;; Description: Exposes example traditional Synergy routines via Traditional ;; Bridge JSON-RPC calls. ;; ;;***************************************************************************** import Harmony.Core import Harmony.Core.Context import Harmony.Core.Interface import Harmony.Core.EF.Extensions import Harmony.OData import Harmony.OData.Adapter import System.Threading.Tasks import System import System.Collections.Generic import System.Text import Services import Services.Models import Microsoft.AspNetCore.Mvc import Microsoft.AspNet.OData import Microsoft.AspNetCore.Authorization import Newtonsoft.Json.Linq import System.Linq import Services.Controllers import Services.Models namespace Services.Controllers public partial class TraditionalBridgeService extends DynamicCallProvider ;;; <summary> ;;; Constructor ;;; </summary> public method TraditionalBridgeService connection, @IDynamicCallConnection parent(connection) proc endmethod endclass endnamespace -
Save the code.
You will notice that the code includes a constructor method that is taking advantage of the Dependency Injection environment that is provided by Harmony Core. In this case, the method is requesting that an instance of IDynamicCallConnection, which represents a connection from Harmony Core to a Traditional Bridge host application, which is passed directly into the base class via a constructor initializer.
The task now is to add a helper method for each of the routines exposed by your Traditional Bridge environment. The basic idea is that code in a controller class wants to call a Traditional Bridge routine, it will obtain a copy of this class via dependency injection and call the appropriate wrapper method.
All helper methods must be public async methods.
Helper methods should have parameters matching the underlying traditional Synergy routine being called. Remember that we're in .NET here, and we're ultimately calling a traditional Synergy routine, so the parameter data types are often not a direct match. For example, you would generally use a string parameter in .NET when interacting with an alpha field in traditional Synergy.
Because an async pattern is being used here, all helper methods must return a Task object. If the underlying traditional Synergy routine is a subroutine then the return value of the helper method should be defined as @Task.
If the underlying traditional Synergy routine is a function then the return value of the helper method should be defined as @Task<T>, with the generic type T being based on the return value type of the function. Again, appropriate mappings between traditional Synergy and .NET need to be used.
The actual call to the underlying traditional Synergy routine is done by calling an inherited method named CallMethod, and the return value of this method is a Tuple. A tuple is a data structure that has a specific number and sequence of elements, and those elements can be of different types. An example of a tuple is a data structure with three elements, item 1 might be an integer, item 2 a decimal, and item 3 a string. When dealing with a Tuple the items are always named Item1, Item2, and so on.
It's not important that you know how this particular Tuple is structured, because Harmony Core provides a static helper class named ArgumentHelper to assist you in decoding returned parameters and function return values. The helper class has a generic method named Argument, and accepts two parameters.
The first parameter is the return value or argument number, where 0 is the function return value, 1 is argument 1, 2 is argument 2, and so on.
The second parameter is the Tuple that's was returned by CallMethod.
Here is an example of using the helper to extract the value of an alpha function return value:
data retval = ArgumentHelper.Argument<string>(0,returnTuple)
And here is an example of using the helper to extract the numeric value of out or inout parameter 3:
data param3value = ArgumentHelper.Argument<decimal>(3,returnTuple)
Each client-callable wrapper is a public async method in the TraditionalBridgeService class. Client-callable wrappers:
- Return an @Task or @Task value.
- Call the traditional Synergy routine.
- Translate argument types:
- Trom the types used in the OData calls to the Synergy types used in the traditional Synergy routine.
- If a traditional Synergy routine has more than one argument type, the client-callable wrapper must translate each one.
The following sections describe these translations:
- Wrapper Code for Primitive Types
- Wrapper Code for Structures
- Wrapper Code for Collections
- Wrapper Code for Optional Parameters
Parameters being represented in .NET as integer, float, double, decimal and string are translated into underlying traditional Synergy data types on the server side. This means that client-callable wrapper code for these types is very simple. For example:
public async method PassParameters, @Task<string>
intParam, int
stringParam, string
decParam, decimal
proc
;; CallMethod takes the method name to call along with the parameters and a dummy value
;; used to determine the expected return type if there is a return value
data resultTuple = await CallMethod("PassParameters", intParam, stringParam, decParam, String.Empty)
;; Return the string from the return value
mreturn ArgumentHelper.Argument<string>(0,resultTuple)
endmethod
To support structure arguments in Traditional Bridge, you must have matching data object classes (classes that extend DataObjectBase) on the server (traditional Synergy code) and the client (Synergy .NET code). Start by generating the client-side data object classes:
-
Add the structure names to the
set BRIDGE_SRUCTURE=line in regen.bat, and optionally specify aliases for the structures (see comments in regen.bat for details). For the client-side, CodeGen uses the same data object templates used elsewhere in .NET code: ODataMetadata.tpl and ODataModel.tpl. -
Remove
remfrom the following line in regen.bat:rem set ENABLE_TRADITIONAL_BRIDGE_GENERATION=YES -
If your traditional Synergy routines have optional parameters (see Wrapper Code for Optional Parameters below), remove
remfrom the following line in regen.bat:rem set ENABLE_BRIDGE_OPTIONAL_PARAMETERS=YES -
Run regen.bat to generate the code.
See Code Generation for more information.
Once client-side data object classes are generated, use these classes in the client-side wrapper (TraditionalBridgeService), in the same way you would pass a primitive argument type. For example, the following passes the Customer data object:
public async method ProcessCustomerStructure, @Task<Customer>
customer, @Customer
proc
;; CallMethod takes the method name to call along with the parameters and a dummy value
;; used to determine the expected return type if there is a return value
data resultTuple = await CallMethod("ProcessCustomerStructure", customer)
;; Return the customer object from parameter 1
mreturn ArgumentHelper<Customer>(1,resultTuple)
endmethod
Collection support on the server is currently limited to ArrayList, memory handles, dynamic arrays, and pseudo arrays. This support allows for element types of a collection to be primitives, Synergy structures, or data objects. For example:
public async method GetAllCustomers, @Task<List<Customer>>
proc
;;CallMethod takes the method name to call along with the parameters and a dummy value
;;used to determine the expected return type if there is a return value
data resultTuple = await CallMethod("GetAllCustomers", new List<Customer>())
;; Return the collection of customer objects from parameter 1
mreturn ArgumentHelper.Argument<IEnumerable<Customer>>(1,resultTuple)
endmethod
For optional parameters, Traditional Bridge supports the primitive arguments a, d, i, and n. This approximates the type support for xfNetLink COM. Traditional Synergy code written for xfNetLink COM access frequently uses optional parameters, so if your code was originally written for xfNetLink COM, you may need to add code for optional parameters. The following example shows how this is done:
public partial class TraditionalBridgeService extends DynamicCallProvider
public async method Arbitrario_Optional, @Task<ArbitrarioOptionalReturnType>
parm, @ArbitrarioOptionalParameter
proc
;;calls to ArgumentHelper.MaybeOptional translate null's into not passed data types under the hood
data resultTpl = await CallMethod("arbitrario_optional", parm.p1, ArgumentHelper.MaybeOptional(parm.p2), ArgumentHelper.MaybeOptional(parm.p3), ArgumentHelper.MaybeOptional(parm.p4))
data resultArray = resultTpl.Item2.ToList()
data returnValue = new ArbitrarioOptionalReturnType()
returnValue.p3 = ^as(resultArray[2], string)
;;If a value type is optional, like the int below, you will need to define it as nullable to allow an un-passed value
returnValue.p4 = ^as(resultArray[3], Nullable<int>)
mreturn returnValue
endmethod
public class ArbitrarioOptionalParameter
public readwrite property p1, int
public readwrite property p2, string
public readwrite property p3, string
public readwrite property p4, int?
endclass
public class ArbitrarioOptionalReturnType
public readwrite property p3, string
public readwrite property p4, int?
endclass
endclass
-
Copy and paste the following code into the
TraditionalBridgeServiceclass, between theendmethodandendclassstatements:public async method GetEnvironment, @Task<string> proc ;; Call the method data resultTuple = await CallMethod("GetEnvironment") Extract the return value data returnToken = ArgumentHelper.Argument<string>(0,resultTuple) ;; And return it mreturn returnToken endmethod -
Save the file.
The first statement calls the underlying traditional Synergy routine, assigning the information returned by the routine into a Tuple named resultTuple.
The second statement extracts Item1 (the return value) from the Tuple, casting it as a string type, because the underlying traditional Synergy routine returns an alpha, and that value is then returned to the calling routine.
-
Copy and paste the following code into the
TraditionalBridgeServiceclass, between theendmethodandendclassstatements:public async method GetLogicalName, @Task<string> required in aLogicalName, string proc ;; Call the method data resultTuple = await CallMethod("GetLogicalName",aLogicalName) ;; Extract the return value data returnToken = ArgumentHelper.Argument<string>(0,resultTuple) ;; And return it mreturn returnToken endmethod -
Save the file.
The code in this example is very similar to the preceding example, except for the addition of a single inbound parameter. In this case the parameter is already defined as a string, which is already an appropriate type to pass along to the underlying traditional Synergy routine (which expects an alpha), so the parameter value is passed directly to the CallMethod method.
-
Copy and paste the following code into the
TraditionalBridgeServiceclass, between theendmethodandendclassstatements:public async method AddTwoNumbers, @Task<decimal> required in aNumber1, decimal required in aNumber2, decimal proc ;; Prepare a variable indicating the type of the returned third parameter data sum, decimal ;; Call the method passing parameters and return value type data resultTuple = await CallMethod("AddTwoNumbers",aNumber1,aNumber2,sum) ;; Extract the result from parameter 3 sum = ArgumentHelper.Argument<decimal>(3,resultTuple) ;; And return it mreturn sum endmethod -
Save the file.
The code in this example is again similar, except that there are now two inbound parameters, and the underlying traditional Synergy routine exposes a third parameter, which is also an out parameter. In that scenario we must pass to CallMethod a variable that indicates the type of the additional parameter that we expect to be returned. Note that this variable is not updated with the actual returned value by CallMethod, as in previous examples out parameters are encoded into the returned tuple.
-
Before moving on, make sure the project builds:
-
Right-click on the
Services.Controllersproject and selectBuild.
Check the Output window and verify that the build was successful.
1>------ Build started: Project: Services.Controllers, Configuration: Any CPU ------ ========== Build: 1 succeeded, 0 failed, 2 up-to-date, 0 skipped ==========
That's all you can do for now because you don't yet have any code that attempts to call the helper methods. Later you will add some code to the Startup class to register your TraditionalBridgeService as a Service within the Dependency Injection container.
Next topic: Add Controller Class
-
Tutorial 2: Building a Service from Scratch
- Creating a Basic Solution
- Enabling OData Support
- Configuring Self Hosting
- Entity Collection Endpoints
- API Documentation
- Single Entity Endpoints
- OData Query Support
- Alternate Key Endpoints
- Expanding Relations
- Postman Tests
- Supporting CRUD Operations
- Adding a Primary Key Factory
- Adding Create Endpoints
- Adding Upsert Endpoints
- Adding Patch Endpoints
- Adding Delete Endpoints
-
Harmony Core CLI Tool
-
OData Aware Tools
-
Advanced Topics
- CLI Tool Customization
- Adapters
- API Versioning
- Authentication
- Authorization
- Collection Counts
- Customization File
- Custom Field Types
- Custom File Specs
- Custom Properties
- Customizing Generated Code
- Deploying to Linux
- Dynamic Call Protocol
- Environment Variables
- Field Security
- File I/O
- Improving AppSettings Processing
- Logging
- Optimistic Concurrency
- Multi-Tenancy
- Publishing in IIS
- Repeatable Unit Tests
- Stored Procedure Routing
- Suppressing OData Metadata
- Traditional Bridge
- Unit Testing
- EF Core Optimization
- Updating a Harmony Core Solution
- Updating to 3.1.90
- Creating a new Release
-
Background Information