Step By Step Guide to create EMA C# API application project and solution with VS Code

Wasin Waeosri
Developer Advocate Developer Advocate

Introduction

Last Update: Oct 2024.

The Real-Time SDK (C# Edition) (RTSDK, formerly known as Elektron SDK) is a suite of modern and open source APIs (GitHub) that aim to simplify development through a strong focus on ease of use and standardized access to LSEG Real-Time Platform via the proprietary TCP connection named RSSL and proprietary binary message encoding format named OMM Message. The capabilities range from low latency/high-performance APIs right through to simple streaming Web APIs.

The RTSDK C# Edition can run on Windows, Oracle Linux Server, Red Hat Enterprise Server and Ubuntu Linux platforms. It supports the Visual Studio 2022 IDE for the full features development experience but the IDE is available for Windows developers only. Fortunately, the RTSDK C# Edition also supports the cross-platform .NET CORE and the Visual Studio Code (aka VS Code) editor is available for all major OS platforms. Linux and Windows developers who are using the VS Code editor can implement the real-time streaming application with LSEG Real-Time platform using the RTSDK C# Edition.

This example project shows a step-by-step guide to create the EMA API .NET project and solution with the RTSDK C# Edition connect to the Real-Time Optimized (RTO) server on VS Code and the C# extension. I am demonstrating with the RTSDK C# version 2.1.3.L1 on Ubuntu Linux, and this step-by-step guide can be applied to any supported OS platforms.

Note: For developers who connect to the Real-Time Distribution System (RTDS), you can apply the steps to set up the project, solution and the libraries. However, please check the 100_MP_Streaming example and Cons100 section on the RTSDK C# QuickStart page for the source code part.

Prerequisite

Before I am going further, there is some prerequisite, dependencies, and libraries that the project is needed.

.NET SDK

Firstly, you need .NET 6 SDK for this project. You can download the SDK based on your system from Microsoft .NET 6 website.

Please check How to check that .NET is already installed to verify installed .NET versions on your machine.

Note: Please check the RTSDK C# versions and .NET versions supported from the API Compatibility Matrix document.

Visual Studio Code

Next, the VS Code editor tool with the free C# extension.

Note:

  1. There is also the C# Dev Kit extension that gives developers more "Visual Studio like" experience and features than the C# extension. However, the C# Dev Kit extension requires Visual Studio License.
  2. The C# extension requires .NET 8.0 (As of April 2024), but you can setup and compile .NET 6.0 project with the .NET 8.0 SDK.

Access to the RTO

This project uses RTO access credentials for Version 2 Authentication (Service ID).

Please contact your LSEG representative to help you with the RTO account and services.

Note: For developers who connect to the Real-Time Distribution System (RTDS), you need the following information:

  • Your ADS Server or Interactive-Provider application hostname or IP Address
  • Your ADS Server or Interactive-Provider application RSSL Port number (14002 by default)
  • Your Market Data Service Name
  • Your Data Access Control System (DACS) User name

Internet Access

The RTSDK C# libraries (both ETA and EMA APIs) are available on the NuGet package manager and distribution platform. You can use the built-in VS Code CLI command to download the EMA and ETA libraries from NuGet over internet.

That covers the prerequisite of this project.

Create the EMA API Real-Time Application Project

Let's me start with how to create a single EMA C# .NET project using VS Code and the .NET CLI tool.

Initialize A Project

Firstly, create a new folder name ema_project in your workbench environment.

    	
            $>mkdir ema_project
        
        
    

Next, access the ema_project folder and create a new console project with the following command:

    	
            dotnet new console --framework net6.0 --use-program-main
        
        
    

Example result:

    	
            

root:/mnt/c$cd ema_project

 

root:/mnt/c/ema_project$ dotnet new console --framework net6.0 --use-program-main

The template "Console App" was created successfully.

 

Processing post-creation actions...

Running 'dotnet restore' on /mnt/c/ema_project/ema_project.csproj...

  Determining projects to restore...

  Restored /mnt/c/ema_project/ema_project.csproj (in 77 ms).

Restore succeeded.

The dotnet new console --framework net6.0 --use-program-main command creates a new console project with .NET 6.0 framework for you. The command also create a simple application Program.cs as a starting point for the project as follows:

dotnet new project command result

We can test our project creation and .NET SDK setup verification by running this Program.cs with the following command in a terminal:

    	
            

dotnet run

 

Hello, World!

Add EMA Library

Now we come to how to add the EMA library to the project. The RTSDK C# libraries are available on the NuGet package manager and distribution platform.

EMA library on NuGet

We can add the EMA Core library into the project with the dotnet add package LSEG.Ema.Core --version {version} command as follows.

    	
            dotnet add package LSEG.Ema.Core --version 3.1.0
        
        
    

Result:

    	
            

root:/mnt/c/ema_project$ dotnet add package LSEG.Ema.Core --version 3.1.0

  Determining projects to restore...

  Writing /tmp/tmp9S2ovj.tmp

info : X.509 certificate chain validation will use the default trust store selected by .NET.

info : X.509 certificate chain validation will use the default trust store selected by .NET.

info : Adding PackageReference for package 'LSEG.Ema.Core' into project '/mnt/c/ema_project/ema_project.csproj'.info : Restoring packages for C:\ema_project\ema_project.csproj...

info :   GET https://api.nuget.org/v3-flatcontainer/lseg.ema.core/index.json

info :   OK https://api.nuget.org/v3-flatcontainer/lseg.ema.core/index.json 1492ms

info :   GET https://api.nuget.org/v3-flatcontainer/lseg.ema.core/3.1.0/lseg.ema.core.3.1.0.nupkg

info :   OK https://api.nuget.org/v3-flatcontainer/lseg.ema.core/3.1.0/lseg.ema.core.3.1.0.nupkg 829ms

....

info : Package 'LSEG.Ema.Core' is compatible with all the specified frameworks in project '/mnt/c/ema_project/ema_project.csproj'.

info : PackageReference for package 'LSEG.Ema.Core' version '3.1.0' added to file '/mnt/c/ema_project/ema_project.csproj'.

info : Writing assets file to disk. Path:  /mnt/c/ema_project/obj/project.assets.json

log  : Restored /mnt/c/ema_project (in 6.7 sec).

You can use dotnet list package command to verify the EMA library package installation.

    	
            

root:/mnt/c/ema_project$ dotnet list package

Project 'ema_project' has the following package references

   [net6.0]:

   Top-level Package      Requested   Resolved

   > LSEG.Ema.Core        3.1.0       3.1.0  

 

Now the ema_project is ready for implementing the real-time application with EMA C# API.

Add the Real-Time Application Source Code with EMA

Please let me remind you again, if you environment connects to the Real-Time Distribution System (RTDS), please check the 100_MP_Streaming example source code and Cons100 section on the RTSDK C# QuickStart page for the source code part. The code below on this article connects to the RTO (Real-Time Optimized on the Cloud).

So, now let’s look at the real-time application source code. The next step is to changing the Program.cs file source code to call EMA library to connect and consume data from RTO.

To handle a .env file that store you RTO Authentication version 2 credential, we add the DotNetEnv library to the project with the following command:

    	
            root:/mnt/c/ema_project$ dotnet add package DotNetEnv --version 3.0.0
        
        
    

Then add a .env file to the ema_project folder with the following Authentication V2 content:

    	
            CLIENT_ID=<Your Auth V2 Client-ID>
CLIENT_SECRET=<Your Auth V2 Client-Secret>

Now we come to the coding part. You can open the project in VS Code editor by running the code . command inside the ema_project folder.

EMA Project in VS Code

The first step is to adding the AppClient class to the Program.cs file. The AppClient class can be a simple class that print Refresh, Update, and Status messages as follows:

    	
            

namespace ema_project;

 

using LSEG.Ema.Access;

using LSEG.Ema.Domain.Login;

using System;

using System.IO;

using System.Threading;

using DotNetEnv;

 

internal class AppClient: IOmmConsumerClient

{

    public void OnRefreshMsg(RefreshMsg refreshMsg, IOmmConsumerEvent consumerEvent)

    {

        Console.WriteLine(refreshMsg);

    }

    public void OnUpdateMsg(UpdateMsg updateMsg, IOmmConsumerEvent consumerEvent)

    {

        Console.WriteLine(updateMsg);

    }

    public void OnStatusMsg(StatusMsg statusMsg, IOmmConsumerEvent consumerEvent)

    {

        Console.WriteLine(statusMsg);

    }

    public void OnAllMsg(Msg msg, IOmmConsumerEvent consumerEvent) { }

    public void OnAckMsg(AckMsg ackMsg, IOmmConsumerEvent consumerEvent) { }

    public void onGenericMsg(GenericMsg genericMSg, IOmmConsumerEvent consumerEvent) { }

}

 

class Program

{

  ...

}

Moving on the the Program class that act as the Consumer:

    	
            

//Program.cs

internal class AppClient: IOmmConsumerClient

{

  ...

}

 

class Program

{

    static void Main(string[] args)

    {

        DotNetEnv.Env.Load();

        OmmConsumer? consumer = null;

        try{

            // instantiate callback client

            AppClient appClient = new();

            Console.WriteLine("Connecting to market data server");

 

            string? clientID = Environment.GetEnvironmentVariable("CLIENT_ID");

            string? clientSecret = Environment.GetEnvironmentVariable("CLIENT_SECRET");

            OmmConsumerConfig config = new OmmConsumerConfig().ClientId(clientID).ClientSecret(clientSecret);

            // create OMM consumer

            consumer = new OmmConsumer(config);

 

            LoginReq loginReq = new();

            consumer.RegisterClient(loginReq.Message(), appClient);

 

            Console.WriteLine("Subscribing to market data");

 

            consumer.RegisterClient(new RequestMsg().ServiceName("ELEKTRON_DD").Name("JPY="), appClient);

            Thread.Sleep(60000); //

 

        }catch (OmmException excp){

            Console.WriteLine($"Exception subscribing to market data: {excp.Message}");

        }

        finally

        {

             consumer?.Uninitialize();

        }

    }

}

The final step is to create the EmaConfig.xml file with the configurations based on this Enterprise Message API (EMA) - Configuration Overview article. The main point is set the Channel configuration to connect to RTO.

    	
            

<?xml version="1.0" encoding="UTF-8"?>

<EmaConfig>

 

<!-- ConsumerGroup provides set of detailed configurations to be used by named consumers                -->

<!-- Application specifies which configuration to use by setting OmmConsumerConfig::consumerName()      -->

<ConsumerGroup>

    <!-- DefaultConsumer parameter defines which consumer configuration is used by OmmConsumer          -->

    <!-- if application does not specify it through OmmConsumerConfig::consumerName()                   -->

    <!-- first consumer on the ConsumerList is a DefaultConsumer if this parameter is not specified     -->

    <DefaultConsumer value="Consumer_4"/>

    <ConsumerList>

        <Consumer>

            <Name value="Consumer_4"/>

            <!-- ChannelSet specifies an ordered list of Channels to which OmmConsumer will attempt to  -->

            <!-- connect, one at a time, if the previous one fails to connect                           -->

            <ChannelSet value="Channel_4"/>

            <Logger value="Logger_1"/>

            <Dictionary value="Dictionary_1"/>

            <XmlTraceToStdout value="0"/>

        </Consumer>

    </ConsumerList>

</ConsumerGroup>

 

<ChannelGroup>

    <ChannelList>

        <Channel>

            <Name value="Channel_4"/>

            <ChannelType value="ChannelType::RSSL_ENCRYPTED"/>

            <CompressionType value="CompressionType::None"/>

            <GuaranteedOutputBuffers value="5000"/>

            <!-- EMA discovers a host and a port from RDP service discovery for the specified location

                when both of them are not set and the session management is enable. -->

            <Location value="ap-southeast-1"/>

            <EnableSessionManagement value="1"/>

            <EncryptedProtocolType value="EncryptedProtocolType::RSSL_SOCKET"/>

        </Channel>

    </ChannelList>

</ChannelGroup>

...

</EmaConfig>

That covers the coding part of the project.

Build and Run Real-Time Application Source Code

That brings us to build and run our source code step we just created. To build the project, we use the dotnet build command inside the ema_project folder.

    	
            dotnet build --configuration {Debug or Release}
        
        
    

Please note that the default configuration value is Debug.

Example:

    	
            

root:/mnt/c/ema_project$ dotnet build

MSBuild version 17.3.2+561848881 for .NET

  Determining projects to restore...

  All projects are up-to-date for restore.

  ema_project -> /mnt/c/ema_project/bin/Debug/net6.0/ema_project.dll

 

Build succeeded.

    0 Warning(s)

    0 Error(s)

 

Time Elapsed 00:00:02.44

Then the generated executable project_name.dll files (and project_name.exe files if you are on Windows) will be available in the <project folder>/bin/<Debug/Release>/<dotnet target version> folder as follows:

dotnet build result

Please keep in mind that the product of dotnet build command isn't ready to be transferred to another machine to run. To create a version of the application that can be deployed, you need to publish it (for example, with the dotnet publish command).

For more detail about the dotnet build command options, please check the dotnet build document page.

Next, we come to the dotnet run command. This command provides a convenient option to run your application from the source code with one command.

    	
            dotnet run
        
        
    

Please note that the dotnet run command automatically build the project using dotnet build command if necessary.

Example:

    	
            

root:/mnt/c/ema_project$ dotnet run

Connecting to market data server

 

INFO|: loggerMsg

    ClientName: ChannelCallbackClient

    Severity: Info    Text:    Received ChannelUp event on channel Channel_4

        Instance Name Consumer_4_1

        Component Version ads3.7.0.E4.linux.rrg 64-bit

loggerMsgEnd

Subscribing to market data

RefreshMsg

    streamId="1"

    domain="Login Domain"

    solicited

    RefreshComplete

    state="Open / Ok / None / 'Login accepted by host ads-fanout-sm-az1-apse1-prd.'"

    ...

    Attrib dataType="ElementList"

        ElementList

            ElementEntry name="AllowSuspectData" dataType="UInt" value="1"

            ElementEntry name="ApplicationId" dataType="Ascii" value="256"

            ElementEntry name="ApplicationName" dataType="Ascii" value="RTO"

            ...

        ElementListEnd

    AttribEnd

    Payload dataType="NoData"

        NoData

        NoDataEnd

    PayloadEnd

RefreshMsgEnd

 

RefreshMsg

    streamId="5"

    domain="MarketPrice Domain"

    solicited

    ...

    name="JPY="

    nameType="1"

    serviceId="257"

    serviceName="ELEKTRON_DD"

    Payload dataType="FieldList"

        FieldList FieldListNum="99" DictionaryId="1"

            FieldEntry fid="1" name="PROD_PERM" dataType="UInt" value="526"

            FieldEntry fid="2" name="RDNDISPLAY" dataType="UInt" value="153"

            FieldEntry fid="3" name="DSPLY_NAME" dataType="Rmtes" value="BARCLAYS     LON"

            FieldEntry fid="5" name="TIMACT" dataType="Time" value="08:58:00:000:000:000"

            FieldEntry fid="11" name="NETCHNG_1" dataType="Real" value="0.15"

            FieldEntry fid="12" name="HIGH_1" dataType="Real" value="147.88"

            FieldEntry fid="13" name="LOW_1" dataType="Real" value="147.49"

            FieldEntry fid="15" name="CURRENCY" dataType="Enum" value="392"

            FieldEntry fid="17" name="ACTIV_DATE" dataType="Date" value="26 JAN 2024 "

            FieldEntry fid="19" name="OPEN_PRC" dataType="Real" value="147.61"

            FieldEntry fid="21" name="HST_CLOSE" dataType="Real" value="147.65"

            FieldEntry fid="22" name="BID" dataType="Real" value="147.8"

            FieldEntry fid="23" name="BID_1" dataType="Real" value="147.81"

            FieldEntry fid="24" name="BID_2" dataType="Real" value="147.79"

            FieldEntry fid="25" name="ASK" dataType="Real" value="147.83"

            ...

            FieldEntry fid="14208" name="BID_HR_MS" dataType="Time" value="08:00:00:289:000:000"

        FieldListEnd

    PayloadEnd

RefreshMsgEnd

 

UpdateMsg

    streamId="5"

    domain="MarketPrice Domain"

    updateTypeNum="0"

    name="JPY="

    serviceId="257"

    serviceName="ELEKTRON_DD"

    Payload dataType="FieldList"

        FieldList

            FieldEntry fid="22" name="BID" dataType="Real" value="147.81"

            FieldEntry fid="393" name="PRIMACT_1" dataType="Real" value="147.81"

            FieldEntry fid="25" name="ASK" dataType="Real" value="147.82"

        FieldListEnd

    PayloadEnd

UpdateMsgEnd

...

The dotnet run command is used in the context of projects, not built assemblies. If you're trying to run a framework-dependent application DLL instead, you must use dotnet without a command inside <project folder>/bin/<Debug/Release>/<dotnet target version> folder like the following example:

    	
            /mnt/c/ema_project/bin/Debug/net6.0/$ dotnet ema_project.dll
        
        
    

Please note that when running a framework-dependent application DLL above, you need to copy all necessary files (like the EmaConfig.xml or .env file) to the <project folder>/bin/<Debug/Release>/<dotnet target version> folder as well.

For more detail about the run options, please check the dotnet run document page.

Let’s leave the build and run step there.

Publishing the Project

Now let me turn to how to publish our project. The dotnet publish command compiles the application, reads through its dependencies specified in the project file, and publishes the resulting set of files to a directory (containing dll, configuration json files, dependencies files, etc). The command output is ready for deployment to a hosting system for execution. You can publish the .NET application in 2 modes as follows:

  • self-contained mode: This mode produces an application that includes the .NET runtime and libraries, and your application and its dependencies. Users of the application can run it on a machine that doesn't have the .NET runtime installed.
  • framework-dependent: This mode produces an application that includes only your application itself and its dependencies. Users of the application have to separately install the .NET runtime.

Please note that both publishing modes produce a platform-specific executable by default. Framework-dependent applications can be created without an executable, and these applications are cross-platform.

Example: creates a framework-dependent executable for the current platform.

    	
            

#On Linux

 

root:/mnt/c/ema_project$ dotnet publish

MSBuild version 17.7.4+3ebbd7c49 for .NET

  Determining projects to restore...

  Restored /mnt/c/ema_project/ema_project.csproj (in 708 ms).

  ema_project -> /mnt/c/ema_project/bin/Debug/net6.0/ema_project.dll

  ema_project -> /mnt/c/ema_project/bin/Debug/net6.0/publish/

Result:

Publish a framework-dependent executable result

Example: creates a self-contained executable for the Windows platform.

    	
            

# On Linux

 

root:/mnt/c/ema_project$ dotnet publish --configuration Release --runtime win-x64 --self-contained

MSBuild version 17.3.2+561848881 for .NET

  Determining projects to restore...

  Restored /mnt/c/ema_project/ema_project.csproj (in 648 ms).

  ema_project -> /mnt/c/ema_project/bin/Release/net6.0/win-x64/ema_project.dll

  ema_project -> /mnt/c/ema_project/bin/Release/net6.0/win-x64/publish/

Result:

Publish a self-contained executable for Windows x64 result

Then you can copy a result directory with all dependencies files to deploy and run on your target machine using the ema_project binary file (based on your runtime).

To learn more about the publish and deployment options, please check the following resources:

That all I have to say about creating single EMA C# API Project using VS Code.

Create the EMA API Real-Time Application Solution

My next point is creating a solution. When you are doing a software development project, your application may needs multiple source code projects for easy management and implementation by multiple developers. A Visual Studio solution is a container to organize one or more related projects. When you open a solution, Visual Studio and Visual Studio Code automatically loads all the projects that the solution contains.

Initialize A Solution

Firstly, create a new folder name ema_solution in your workbench environment.

    	
            $>mkdir ema_solution
        
        
    

Next, access the ema_solution folder and create a new console project with the following command:

    	
            dotnet new sln
        
        
    

Example result:

    	
            

root:/mnt/c$cd ema_solution

 

root:/mnt/c/ema_solution$ dotnet new sln

The template "Solution File" was created successfully.

Our EMA Solution has 2 projects as follows:

  • JSONUtil class library project that contains a POCO object for storing RIC data and transform the RIC data into a JSON message format
  • A EMA C# console project that connects and consume data from RTO, and print the market price data in JSON message format using a JSONUtil class library above.

Initialize A Class Library Project

Let's start with a class library project. Firstly, create a new project inside ema_solution folder with the following dotnet CLI command.

    	
            dotnet new classlib -f net6.0 -o JSONUtil
        
        
    

Result:

    	
            

root:/mnt/c/ema_solution$ dotnet new classlib -f net6.0 -o JSONUtil

The template "Class Library" was created successfully.

 

Processing post-creation actions...

Running 'dotnet restore' on /mnt/c/ema_solution/JSONUtil/JSONUtil.csproj...

  Determining projects to restore...

  Restored /mnt/c/ema_solution/JSONUtil/JSONUtil.csproj (in 67 ms).

Restore succeeded.

Next, add this JSONUtil project to solution with the following command

    	
            dotnet sln add JSONUtil/JSONUtil.csproj
        
        
    

Result:

    	
            Project `JSONUtil/JSONUtil.csproj` added to the solution.
        
        
    

This JSONUtil project uses Newtonsoft.Json package from the NuGet platform for serialize/deserialize JSON data, so we add this package from NuGet to the project via the following command.

    	
            dotnet add package Newtonsoft.Json --version 13.0.3
        
        
    

Now the JSONUtil project is ready for implementing the class to JSON source code.

Add the Class Library Source Code

Once the package install succeed, open a solution with VS Code with the code . command in a ema_solution folder. The VS Code editor will be opened the EMA_SOLUTION solution like shown in the following image.

class lib project init

Open a Class.cs file and add the following POCO code to the file.

    	
            

namespace JSONUtil;

 

using Newtonsoft.Json;

 

public class RIC{

   

    public string Name {get; set;} = string.Empty;

    public string ServiceName {get; set;} = string.Empty;

    public double BID {get; set;}

    public double ASK {get; set;}

    public double NETCHNG_1 {get; set;}

 

    public RIC() {}

 

    public RIC(string ric, string serviceName, double bid = 0D, double ask = 0D, double netChange = 0D)

    {

        this.Name = ric;

        this.ServiceName = serviceName;

        this.BID = bid;

        this.ASK = ask;

        this.NETCHNG_1 = netChange;

    }

    public string ToJSON()

    {

        return JsonConvert.SerializeObject(this);

    }

}

This POCO code just store RIC information such as NameServiceName, and Market Data information BIDASK, and NETCHNG_1 fields data. It also contains a ToJSON() method that serialize self data from RIC object to JSON string message and sends that JSON string to a caller.

You can build entire solution by running dotnet build command inside a ema_solution folder or build just a JSONUtil project with a dotnet build command inside a ema_solution\JSONUtil folder.

Example (build entire solution):

    	
            

root:/mnt/c/ema_solution$ dotnet build

MSBuild version 17.3.2+561848881 for .NET

  Determining projects to restore...

  All projects are up-to-date for restore.

  JSONUtil -> /mnt/c/ema_solution/JSONUtil/bin/Debug/net6.0/JSONUtil.dll

 

Build succeeded.

    0 Warning(s)

    0 Error(s)

 

Time Elapsed 00:00:02.42

That covers the JSONUtil class library project implementation.

Initialize A EMA API Project

Moving on to the EMAConsumer project. Please go back to the ema_solution folder and add new EMA console application project with the following command:

    	
            dotnet new console --framework net6.0 -o EMAConsumer --use-program-main
        
        
    

Next, add this EMAConsumer project to solution with the following command

    	
            dotnet sln add EMAConsumer/EMAConsumer.csproj
        
        
    

The newly created EMAConsumer project does not have access to the JSONUtil class library project, so we need to add a project reference to the JSONUtil project with the following command.

    	
            dotnet add EMAConsumer/EMAConsumer.csproj reference JSONUtil/JSONUtil.csproj
        
        
    

Example:

    	
            root:/mnt/c/ema_solution$ dotnet add EMAConsumer/EMAConsumer.csproj reference JSONUtil/JSONUtil.csproj
Reference `..\JSONUtil\JSONUtil.csproj` added to the project.
root:/mnt/c/ema_solution$

Now we are ready to add the EMA and DotNetEnv packages into the EMAConsumer project. Please go to the ema_solution\EMAConsumer and run the following command.

    	
            dotnet add package LSEG.Ema.Core --version 3.1.0

dotnet add package DotNetEnv --version 3.0.0

Once the EMAConsumer project is finished setup, the VS Code workspace contains both projects that you can develop in the same editor.

EMAConsumer and JSONUtil projects in VS Code solution

Add the Real-Time Application Source Code with EMA

That brings us to updating the source code to call EMA API interfaces and JSONUtil library to consume data from RTO and print that data as JSON message format.

Firstly, add a .env file to the ema_solution/EMAConsumer folder with the following Authentication Version 2 content:

    	
            

CLIENT_ID=<Your Auth V2 Client-ID>

CLIENT_SECRET=<Your Auth V2 Client-Secret>

Secondly, open the EMAConsumer/Program.cs file and add the following library import to the file header.

    	
            

using JSONUtil;

using System;

using System.IO;

using System.Threading;

using LSEG.Ema.Access;

using LSEG.Ema.Domain.Login;

using static LSEG.Ema.Access.DataType;

using LSEG.Ema.Rdm;

using DotNetEnv;

 

class Program

{

    ...

}

Next, modify the main() method to call the EMA API interfaces as follows:

    	
            

class Program

{

    static void Main(string[] args)

    {

 

        string RicName = "THB=";

        string ServiceName = "ELEKTRON_DD";

        RIC ric = new RIC(RicName,ServiceName);

 

        DotNetEnv.Env.Load();

        OmmConsumer? consumer = null;

        try{

            // instantiate callback client

            AppClient appClient = new();

            appClient.SetRicObj(ric);

            Console.WriteLine("Connecting to market data server");

 

            string clientID = Environment.GetEnvironmentVariable("CLIENT_ID") ?? "<Client_ID>";

            string clientSecret = Environment.GetEnvironmentVariable("CLIENT_SECRET") ?? "<Client_Secret>";

            OmmConsumerConfig config = new OmmConsumerConfig().ClientId(clientID).ClientSecret(clientSecret);

            // create OMM consumer

            consumer = new OmmConsumer(config);

 

            Console.WriteLine("Subscribing to market data");

 

            LoginReq loginReq = new();

            consumer.RegisterClient(loginReq.Message(), appClient);

 

            OmmArray array = new()

            {

                FixedWidth = 2

            };

 

            array.AddInt(11) //NETCHNG_1

                .AddInt(22) //BID

                .AddInt(25) //ASK

                .Complete();

           

            var view = new ElementList()

                .AddUInt(EmaRdm.ENAME_VIEW_TYPE, 1)

                .AddArray(EmaRdm.ENAME_VIEW_DATA, array)

                .Complete();

           

            RequestMsg reqMsg = new();

 

            consumer.RegisterClient(reqMsg.ServiceName(ServiceName).Name(RicName).Payload(view), appClient);

            Thread.Sleep(60000); //

 

        }catch (OmmException excp){

            Console.WriteLine($"Exception subscribing to market data: {excp.Message}");

        }

        finally

        {

             consumer?.Uninitialize();

        }

    }

}

The code above perform the following tasks:

  1. Create JSONUtil's RIC object.
  2. Create AppClient object and pass a RIC object to AppClient for storing data from the API.
  3. Create a OmmConsumerConfig object with the RTO Authentication Version 2 credential.
  4. Register the Login stream.
  5. Create a View request message that subscribes for NETCHNG_1BID, and ASK fields, then subscribes item.

Now we come to the AppClient class path that needs to get a RIC object from the main class, update RIC object data with incoming data via the OnRefreshMsg and OnUpdateMsg callback methods and call RIC.ToJSON() method to print out incoming data as a JSON message.

    	
            

namespace EMAConsumer;

 

using JSONUtil;

....

 

internal class AppClient: IOmmConsumerClient

{

    RIC _ric;

    private readonly LoginRefresh _loginRefresh = new();

    private readonly LoginStatus _loginStatus = new();

 

    public void SetRicObj(RIC ric)

    {

        this._ric = ric;

    }

    public void OnRefreshMsg(RefreshMsg refreshMsg, IOmmConsumerEvent consumerEvent)

    {

 

        Console.WriteLine("Refresh Message");

        if(refreshMsg.DomainType() == EmaRdm.MMT_LOGIN)

        {

            _loginRefresh.Clear();

            Console.WriteLine(_loginRefresh.Message(refreshMsg).ToString());

        } else

        {

            if (DataType.DataTypes.FIELD_LIST == refreshMsg.Payload().DataType)

                Decode(refreshMsg.Payload().FieldList());

 

            Console.WriteLine(_ric.ToJSON());

        }

 

        Console.WriteLine();

    }

    public void OnUpdateMsg(UpdateMsg updateMsg, IOmmConsumerEvent consumerEvent)

    {

        Console.WriteLine("Update Message");

        if (DataTypes.FIELD_LIST == updateMsg.Payload().DataType)

            Decode(updateMsg.Payload().FieldList());

       

        Console.WriteLine(_ric.ToJSON());

        Console.WriteLine();

    }

    public void OnStatusMsg(StatusMsg statusMsg, IOmmConsumerEvent consumerEvent)

    {

    ...

    }

}

Moving on the the Decode() method that iterates incoming FieldList message and updates a RIC object's NETCHNG_1BID, and ASK properties.

    	
            

internal class AppClient: IOmmConsumerClient

{

    ...

    void Decode(FieldList fieldList)

    {

        foreach (var fieldEntry in fieldList)

        {

            var FieldName = fieldEntry.Name;

            _ric.GetType().GetProperty(FieldName).SetValue(_ric, Decode(fieldEntry));

        }

    }

 

    double Decode(FieldEntry fieldEntry)

    {

        double ReturnValue = 0D;

        if (Data.DataCode.BLANK == fieldEntry.Code)

        {

            return ReturnValue;

        }

        else

        {

            switch (fieldEntry.LoadType)

            {

                case DataTypes.REAL:

                    ReturnValue = fieldEntry.OmmRealValue().AsDouble();

                    break;

                default:

                    ReturnValue = 0D;

                    break;

            }

        }

        return ReturnValue;

    }

}

The final step is to create the EmaConfig.xml file with the configurations like the ema_project above to configure the API to connect to RTO endpoint.

That covers the EMAConsumer project part.

Build and Run Real-Time Application Solution

Now, what about building the solution. You can build entire solution by running the dotnet build command in the ema_solution folder or build only this EMAConsumer project by running the dotnet build command in the ema_solution\EMAConsumer folder.

Example: Build entire solution

    	
            

root:/mnt/c/ema_solution$ dotnet build

MSBuild version 17.3.2+561848881 for .NET

  Determining projects to restore...

  All projects are up-to-date for restore.

  JSONUtil -> /mnt/c/ema_solution/JSONUtil/bin/Debug/net6.0/JSONUtil.dll

  EMAConsumer -> /mnt/c/ema_solution/EMAConsumer/bin/Debug/net6.0/EMAConsumer.dll

 

Build succeeded.

    0 Warning(s)

    0 Error(s)

 

Time Elapsed 00:00:03.17

Unlike the Visual Studio IDE that you can set a solution default project and click the "Run" button on the IDE. With VS Code (and .NET CLI), you need to run the project in side a project folder using the dotnet run command or with dotnet run --project <project/project.csproj> (All required runtime file must be located on the solution home folder).

Run Result: Run inside the EMAConsumer project folder.

    	
            

root:/mnt/c/ema_solution$ cd EMAConsumer/

root:/mnt/c/ema_solution/EMAConsumer$ dotnet run

Connecting to market data server

 

INFO|: loggerMsg

    ClientName: ChannelCallbackClient

    Severity: Info    Text:    Received ChannelUp event on channel Channel_4

        Instance Name Consumer_4_1

        Component Version ads3.7.0.E4.linux.rrg 64-bit

loggerMsgEnd

Subscribing to market data

Refresh Message

 

AllowSuspectData : True

ApplicationId : 256

ApplicationName : RTO

...

UserNameType : 1

State : StreamState: 1 DataState: 1 StatusCode: 0 StatusText: Login accepted by host ads-fanout-sm-az2-apse1-prd.

 

Refresh Message

{"NETCHNG_1":-0.08,"BID":149.22,"ASK":149.24,"Name":"JPY=","ServiceName":"ELEKTRON_DD"}

 

Update Message

{"NETCHNG_1":-0.08,"BID":149.22,"ASK":149.23,"Name":"JPY=","ServiceName":"ELEKTRON_DD"}

 

Update Message

{"NETCHNG_1":-0.09,"BID":149.21,"ASK":149.24,"Name":"JPY=","ServiceName":"ELEKTRON_DD"}

 

Update Message

{"NETCHNG_1":-0.08,"BID":149.22,"ASK":149.23,"Name":"JPY=","ServiceName":"ELEKTRON_DD"}

 

Update Message

{"NETCHNG_1":-0.09,"BID":149.21,"ASK":149.24,"Name":"JPY=","ServiceName":"ELEKTRON_DD"}

...

That’s all I have to say about how to build and run the EMA solution.

Publishing the Solution

So, now let’s look at how to publish our solution. You can publish the solution using the dotnet publish like the project by specify the the path and filename of a solution file (.sln extension) after the command.

Example:

    	
            dotnet publish ./ema_solution.sln 
        
        
    

All dotnet publish command arguments are supported in the solution level too.

Example: creates a self-contained executable for the Windows platform.

    	
            

root:/mnt/c/ema_solution$ dotnet publish ./ema_solution.sln --configuration Release --runtime win-x64 --self-contained

MSBuild version 17.3.2+561848881 for .NET

  Determining projects to restore...

  Restored /mnt/c/ema_solution/JSONUtil/JSONUtil.csproj (in 361 ms).

  Restored /mnt/c/ema_solution/EMAConsumer/EMAConsumer.csproj (in 731 ms).

  JSONUtil -> /mnt/c/ema_solution/JSONUtil/bin/Release/net6.0/JSONUtil.dll

  JSONUtil -> /mnt/c/ema_solution/JSONUtil/bin/Release/net6.0/win-x64/JSONUtil.dll

  ...

  EMAConsumer -> /mnt/c/ema_solution/EMAConsumer/bin/Release/net6.0/win-x64/EMAConsumer.dll

  JSONUtil -> /mnt/c/ema_solution/JSONUtil/bin/Release/net6.0/win-x64/publish/

  EMAConsumer -> /mnt/c/ema_solution/EMAConsumer/bin/Release/net6.0/win-x64/publish/

The publish result will be available inside each Project' /bin/<Debug/Release>/<dotnet target version> folder like the following example of the Class Library project.

JSONUtil project publish result

About the EMAConsumer, the publish result also contains the JSONUtil class library's dll file because we have added a project reference to the JSONUtil project.

EMAConsumer project publish result

That covers the EMA Solution creation with VS Code.

Update June 2024, For the multiple .NET SDK versions

If you have multiple .NET SDK versions installed on your machine, you can run the following steps to configure the SDK version for the project/solution.

Firstly go to the project or solution root folder, and run the following command:

    	
            dotnet --list-sdks
        
        
    

It shows all available SDK in your machine (based on your installation) as follows:

    	
            #windows
2.1.526 [C:\Program Files\dotnet\sdk]
5.0.104 [C:\Program Files\dotnet\sdk]
6.0.423 [C:\Program Files\dotnet\sdk]
7.0.201 [C:\Program Files\dotnet\sdk]
8.0.206 [C:\Program Files\dotnet\sdk]
#linux
2.1.818 [/usr/share/dotnet/sdk]
3.1.426 [/usr/share/dotnet/sdk]
5.0.408 [/usr/share/dotnet/sdk]
6.0.419 [/usr/share/dotnet/sdk]
7.0.406 [/usr/share/dotnet/sdk]

Next, run the following command to create a dotnet global.json file:

    	
            dotnet new globaljson
        
        
    

Open a newly generated global.json file, and change the value of the version property from the latest version in your machine to version 6.x in your machine as follows:

    	
            

{

  "sdk": {

    "version": "6.0.419"

  }

}

Conclusion and Next Steps

Before I finish, let me just say the RTSDK C# give developers access to the LSEG Real-Time platform's real-time streaming data with both low-level and high-levels APIs interfaces for every developers' requirements. For ultra-high performance applications, there is the ETA API that provides high performance, low latency, and open source low-level API interfaces for developers. For the majority of use cases, there is the ease-of-use EMA API with high-level API interfaces for developers. The C# edition SDK supports the cross-platform .NET SDK 6 (aka .NET Core 6) which makes the application can be developed on various types of development environments such as the full-feature Visual Studio 2022 IDE on Windows, or the .NET CLI tool with any editors on the supported platforms.

Visual Studio Code (or just VSCode) is a free, cross-platform source code editor that took over developers' popularity based on its fast and lightweight, supports a variety of programming languages with IntelliSense, and has complete development operations like debugging, task running, and version control. With the the free C# extension, the VS Code can do basic development tasks with .NET development including the RTSDK C# APIs using the editor with the .NET CLI tool. The combination of the VS Code C# extension and .NET CLI tool make they suitable for developing the real-time applications on non-Windows platforms, or even on Windows for developers who do not have the Visual Studio Professional/Enterprise subscriptions.

If you want more powerful development feature on VS Code for the RTSDK C# edition, there is the C# Dev kit extension that gives you the development experience much closer to the full feature Visual Studio IDE while maintain its lightweight and supports all major OS platforms. However, the C# Dev kit extension requires the Visual Studio Professional or Enterprise subscriptions license.

That’s all I have to say about the RTSDK C# development with VS Code.

References

For further details, please check out the following resources:

For any question related to this article or the RTSDK page, please use the Developer Community Q&A Forum.