Skip to content

Tutorial 04 08 Add Helper Service Class

Steve Ives edited this page May 26, 2020 · 37 revisions

Harmony Core Logo

Add a 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.

Create the Service Class

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.

  1. In Solution Explorer, richt-click on the Services.Controllers class and select Add > Class.

  2. Name the new class source file TraditionalBridgeService.dbl and click the Add button.

  3. 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
    
    

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.

Helper Methods

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.

Parameters

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.

Return Value

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.

Sample Wrapper Code

Each client-callable wrapper is a public async method in the ExternalCallContext class. Client-callable wrappers:

  • Return an @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

Wrapper Code for Primitive Data Types

Integer, float, double, string, and decimal data types are translated into underlying Synergy data types on the server side. This means that client-callable wrapper code for these types is very simple. For example:

public class ExternalCallContext extends DynamicCallProvider
	public method ExternalCallContext
		connection, @IDynamicCallConnection
	endparams
		parent(connection)
	proc
        endmethod

	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 resultTpl = await CallMethod("PassParameters", intParam, stringParam, decParam, String.Empty)
		;;resultTpl contains the result of the remote procedure call
		;;Item1 is the return value if one exists
		;;Item2 is an array of the returned parameter values for inout and out parameters
		mreturn (@string)resultTpl.Item1
	endmethod
endclass

Wrapper Code for Structures

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:

  1. 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.
  2. Remove rem from the following line in regen.bat: rem set ENABLE_TRADITIONAL_BRIDGE_GENERATION=YES
  3. If your traditional Synergy routines have optional parameters (see Wrapper Code for Optional Parameters below), remove rem from the following line in regen.bat: rem set ENABLE_BRIDGE_OPTIONAL_PARAMETERS=YES
  4. 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 (ExternalCallContext in the example below), in the same way you would pass a primitive argument type. For example, the following passes the Customer data object:

public class ExternalCallContext extends DynamicCallProvider
	public method ExternalCallContext
		connection, @IDynamicCallConnection
	endparams
		parent(connection)
	proc
        endmethod

	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 resultTpl = await CallMethod("ProcessCustomerStructure", customer)
		;;resultTpl contains the result of the remote procedure call
		;;Item1 is the return value if one exists
		;;Item2 is an array of the returned parameter values for inout and out parameters
		mreturn (@Customer)resultTpl.Item2[1]
	endmethod
endclass

Wrapper Code for Collections

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 class ExternalCallContext extends DynamicCallProvider
	public method ExternalCallContext
		connection, @IDynamicCallConnection
	endparams
		parent(connection)
	proc
        endmethod

	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 resultTpl = await CallMethod("GetAllCustomers", new List<Customer>())
		;;resultTpl contains the result of the remote procedure call
		;;Item1 is the return value if one exists
		;;Item2 is an array of the returned parameter values for inout and out parameters
		mreturn ((@IEnumerable<Customer>)resultTpl.Item2[1]).ToList()
	endmethod
endclass

Wrapper Code for Optional Parameters

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 class ExternalCallContext extends DynamicCallProvider
	public method ExternalCallContext
		connection, @IDynamicCallConnection
	endparams
		parent(connection)
	proc
        endmethod

	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

Next topic: Add Controller Class


Clone this wiki locally