SampleApp: Your first synchronous Symphony C# client and service

Goal

This tutorial guides you through the process of building, packaging, deploying, and running the hello grid sample client and service. It also walks you through the sample application code.

You learn the minimum amount of code that you need to create a client and a service.

At a glance

  1. Build the sample client and service

  2. Package the sample service

  3. Add the application

  4. Run the sample client and service

  5. Walk through the code

Where to find the documentation

Additional documentation is included in the %SOAM_HOME%\docs directory, as follows:

Note:

%SOAM_HOME% is an environment variable that represents the Symphony DE installation directory; for example, C:\SymphonyDE\DE40.

  • .NET API Reference: %SOAM_HOME%\docs\symphonyde\4.1\dotnet\api_reference

  • Platform Symphony Reference: %SOAM_HOME%\docs\symphonyde\4.1\reference_sym

  • Error Reference: %SOAM_HOME%\docs\symphonyde\4.1\error_reference

  • Platform Symphony DE Knowledge Center: %SOAM_HOME%\docs\symphonyde\4.1\index.html

Build the sample client and service

  1. Navigate to %SOAM_HOME%\4.0\samples\DotNet\CS\SampleApp.
  2. Open the Visual C#.NET solution file that is supported by your version of Visual Studio, i.e., sampleApplication.NET.2003.sln, sampleApplication.NET.2005.sln, or sampleApplication.NET64.2005.sln.
  3. Build the .NET solution by pressing ctrl+shift+B.

    Compiled executables and libraries are in the %SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\output directory.

Package the sample service

You must package the files required by your service to create a service package.

Note:

Make sure the dlls are included in your service package.

  1. Go to the directory that contains the files for the service package:
    cd %SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\output
  2. Locate the SampleServiceDotNetCS.exe and Common.dll files. Add these files to an archive using a compression program such as gzip. Save the archive as SampleServiceDotNetCS.zip in the current directory.

Add the application

When you add an application through the DE PMC, you must use the Add Application wizard. This wizard defines a consumer location to associate with your application, deploys your service package, and registers your application. After completing the steps with the wizard, your application should be ready to use.

  1. In the DE PMC, click Symphony Workload > Configure Applications.

    The Applications page displays.

  2. Select Global Actions > Add/Remove Applications.

    The Add/Remove Application page displays.

  3. Select Add an application, then click Continue.

    The Adding an Application page displays.

  4. Select Use existing profile and add application wizard, and browse to your application profile.
  5. Select your application profile xml file, then click Continue

    For SampleApp, you can find your profile in the following location:

    • .NET:

      • Windows—%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\SampleAppDotNetCS.xml

    The Service Package location window displays.

  6. Browse to the service package you created in .zip format and select it, then, select Continue.

    The Confirmation window displays.

  7. Review your selections, then click Confirm.

    The window displays indicating progress. Your application is ready to use.

  8. Click Close.

    The window closes and you are now back in the Platform Management Console. Your new application is displayed as enabled.

Run the sample client and service

To run the service, run the client application. The service that a client application uses is specified in the application profile.

Before running the sample client, ensure that the SyncClient project is set as the StartUp project in Visual C#.NET.

Press F5 to run the application.

The client starts and the system starts the corresponding service. The client displays messages in the console window indicating that it is running.

Walk through the code

You review the sample client application code to learn how you can create a synchronous client application.

Locate the code samples

Solution file (Visual Studio .NET 2003)

%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\sampleApplication.NET.2003.sln

Solution file (Visual Studio 2005)

%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\sampleApplication.NET.2005.sln

or

sampleApplication.NET64.2005.sln

Client

%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\SyncClient\SyncClient.cs

Input/output object

%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\Common\MyMessage.cs

Service
%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\Service\SampleService.cs
Application profile

The service required to compute the input data along with additional application parameters are defined in the application profile:

%SOAM_HOME%\4.1\samples\DotNet\CS\SampleApp\SampleAppDotNetCS.xml

What the sample does

The client application sample sends 10 input messages with the data “Hello Grid !!”, and retrieves the results. The client application is a synchronous client that sends input and blocks the output until all the results are returned.

The service takes input data sent by client applications, returns the input data you have sent and replies "Hello Client !!".

Step 1: Initialize the client

In SyncClient.cs, when you initialize, you initialize the Symphony client infrastructure. You initialize once per client.

Important:

Initialization is required. Otherwise, API calls fail.

...
SoamFactory.Initialize();
...

Step 2: Implement the MyMessage class

Your client application needs to handle data that it sends as input, and output data that it receives from the service.

In MyMessage.cs, we implement methods to set and access the data, such as the message string, task ID, and sync flag.

The MyMessage class must be marked with the serializable attribute. The .NET Framework provides the ability to serialize object data for the purpose of passing it by value across application domains. Making the class serializable means that the object can be deconstructed so that it can be passed through the network to the service. Similarly, the object can be reconstructed when it is received by the service.

[Serializable]
    public class MyMessage
    {
        public MyMessage()
        {
            m_id     = 0;
            m_isSync = false;
            m_string="";
        }
        public MyMessage(int id, bool isSync, string str)
        {
            m_id     = id;
            m_isSync = isSync;
            m_string = str;
        }
        public bool IsSync
        {
            get
            {
                return m_isSync;
            }
            set
            {
                m_isSync = value;
            }
        }
        public int Id
        {
            get
            {
                return m_id;
            }
            set
            {
                 m_id = value;
            }
        }
        public string StringMessage
        {
            get
            {
                 return m_string;
            }
            set
            {
                 m_string = value;
            }
        }
...

Step 3: Connect to an application

To send data to be calculated in the form of input messages, you connect to an application.

You specify an application name, a user name, and password. The application name must match that defined in the application profile.

For Symphony Developer Edition, there is no security checking and login credentials are ignored—you can specify any user name and password. Security checking is done however, when your client application submits workload to the actual grid.

The default security callback encapsulates the callback for the user name and password.

...
// Set up application specific information to be supplied to Symphony
                String applicationName="SampleAppDotNetCS";
                // Set up application authentication information using
                // the default security provider
                DefaultSecurityCallback securityCb = new
                    DefaultSecurityCallback("Guest", "Guest");
                Connection connection = null;
                try
                {
                    // Connect to the specified application
                    connection = SoamFactory.Connect(applicationName, securityCb);
                                    // Retrieve and print our connection ID
                    Console.WriteLine("connection ID: " + connection.Id );
...
Tip:

When you connect, a connection object is returned. You can retrieve the connection ID from the object. Save the connection ID. Should the client application fail, you can use the connection ID to reconnect to Symphony Developer Edition.

Important:

It should be emphasized that the creation and usage of the connection object be scoped in a try-finally block. The finally block, with the connection.Close() method, ensures that the connection is always closed whether exceptional behavior occurs or not. Failure to close the connection causes the connection to continue to occupy middleware resources.

...
try
{
         // Connect to the specified application
                    connection = SoamFactory.Connect(applicationName, securityCb);
       ...
}
                finally
                {
                    // Mandatory connection close
                    if (connection != null)
                    {
                        connection.Close();
                        Console.WriteLine("Connection closed");
                    }
                 }
...

Step 4: Create a session to group tasks

A session is a way of logically grouping tasks that are sent to a service for execution. The tasks are sent and received synchronously.

When creating a synchronous session, you need to specify the session attributes by using the SessionCreationAttributes object. In this sample, we create a SessionCreationAttributes object called attributes and set three parameters in the object.

                    ...
try
{
    // Set up session attributes
    SessionCreationAttributes attributes = new SessionCreationAttributes();
    attributes.SessionName ="mySession";
    attributes.SessionType ="ShortRunningTasks";
    attributes.SessionFlags = SessionFlags.AliasSync;
    // Create a synchronous session
    session = connection.CreateSession(attributes);
                    ...

In this example, note that:

  • The first parameter is the session description. This is optional. The session description can be any descriptive name you want to assign to your session. It is for information purposes, such as in the command-line interface.

  • The second parameter is the session type. The session type is optional. You can leave this parameter blank and system default values are used for your session.

    Important:

    The session type must be the same session type as defined in your application profile.

    In the application profile, with the session type, you define characteristics for the session.

  • The third parameter is the session flag. When creating a synchronous session, set the flag to SessionFlags.AliasSync. This flag indicates to Symphony that this is a synchronous session.

Important:

As is the case with the connection object, the creation and usage of the session object, i.e., sending and receiving data, must be scoped in a try-finally block. The finally block, with the session.Close() method, ensures that the session is always closed whether exceptional behavior occurs or not. Failure to close the session causes the session to continue to occupy middleware resources.

      ...
      try
      {
            session = connection.CreateSession(attributes);
      ...
      }
      finally
      {
            // Mandatory session close
            if (session != null)
            {
                 session.Close();
                 Console.WriteLine("Session closed");
            }
      }
      ...

Step 5: Send input data to be processed

In this step, we create 10 input messages to be processed by the service. We call the MyMessage constructor and pass three input parameters: ID (taskCount), the Boolean value (true) to indicate synchronous communication, and a message string ("Hello Grid !!"). When a message is sent, a task input handle is returned. This task input handle contains the ID for the task that was created for this input message.

...
for (int taskCount = 0; taskCount < numTasksToSend; taskCount++)
{
          // Create a message
          MyMessage inputMessage = new MyMessage(taskCount, true, "Hello Grid !!");
          // Set task submission attributes
          TaskSubmissionAttributes taskAttr = new TaskSubmissionAttributes();
          taskAttr.SetTaskInput(inputMessage);
          // Send it
          TaskInputHandle input = session.SendTaskInput(taskAttr);
          // Retrieve and print task ID
          Console.WriteLine("task submitted with ID: " + input.Id);
}
...

Step 6: Retrieve the output

Pass the number of tasks to the FetchTaskOutput() method to retrieve the output messages that were produced by the service instance. This method blocks until the output for all tasks is retrieved. The return value is an enumeration that contains the completed task results.

Iterate through the task results and extract the messages using the GetTaskOutput() method. Display the task ID, internal ID (taskCount), and the output message.

...
       EnumItems enumItems = session.FetchTaskOutput( (ulong) numTasksToSend);
       // inspect results
       foreach(TaskOutputHandle output in enumItems)
       {
           // check for success of task
           if ( output.IsSuccessful == true )
           {
               // get the message returned from the service
               MyMessage outputMessage = output.GetTaskOutput() as MyMessage;
               if(outputMessage == null)
               {
                   throw new SoamException("Received unexpected object type for task output.");
               }
               // display content of reply
               Console.WriteLine("Task Succeeded [" + output.Id + "]" );
               Console.WriteLine("Task Internal ID : " + outputMessage.Id );
               Console.WriteLine(outputMessage.StringMessage );                       
           }
           else
           {
               // get the exception associated with this task
               SoamException ex = output.Exception;
               Console.WriteLine( ex.ToString() );
           }
       }
 ...

Step 7: Catch exceptions

Any exceptions thrown take the form of SoamException. Catch all Symphony exceptions to know about exceptions that occurred in the client application, service, and middleware.

The sample code above catches exceptions of type SoamException.

Step 8: Uninitialize

Always uninitialize the client API at the end of all API calls. If you do not call uninitialize, the client API remains in an undefined state and resources used by the client are held indefinitely.

Important:

Once you uninitialize, all objects become invalid. For example, you can no longer create a session or send an input message.

...
SoamFactory.Uninitialize();
...

Step 9: Define a service container

For a service to be managed by Symphony, it needs to be in a container object. This is the service container.

In SampleService.cs, SampleServiceContainer inherits from the base class ServiceContainer.

...
class SampleServiceContainer : ServiceContainer
{
...

Step 10: Process the input

Symphony calls OnInvoke() on the service container once per task. Once you inherit from the ServiceContainer class, implement handlers so that the service can function properly. This is where the calculation is performed.

To gain access to the data set from the client, you call the GetTaskInput() method on the task context. The middleware is responsible for placing the input into the taskContext object.

The task context contains all information and functionality that is available to the service during an OnInvoke() call in relation to the task that is being processed.

In this sample, we use the StringBuilder object to build the output message, which includes the input message that is echoed back to the client. Since we are using the same service for sync and async clients, the if statement is used to indicate that the message was sent from a sync client. When the string in the output message is completely assembled, pass it to the SetTaskOutput() method, which sets the task output message that is sent to the client.

...
public override void OnInvoke(TaskContext taskContext)
{
       // get the input that was sent from the client
       MyMessage inputMsg = taskContext.GetTaskInput() as MyMessage;
       if(inputMsg == null)
       {
           throw new SoamException("Have got wrong type of outputMessage object."); 
       }
       // We simply echo the data back to the client
       MyMessage outputMsg = new MyMessage();
       outputMsg.Id = inputMsg.Id;
       StringBuilder reply = new StringBuilder();
       reply.Append("you sent : ");
       reply.Append(inputMsg.StringMessage);
       reply.Append("\nwe replied : Hello Client !!\n>>> ");
       if (inputMsg.IsSync)
       {
           reply.Append("Synchronously.\n");
       }
       else
       {
           reply.Append("Asynchronously.\n");
       }
       outputMsg.StringMessage = reply.ToString();
 
       // set our output message
       taskContext.SetTaskOutput(outputMsg);
}
...

Step 11: Run the container

The service is implemented within an executable. As a minimum, we need to create within our main function an instance of the service container and run it.

Note that our service is started by parameters.
...
static int Main(string[] args)
{
       // Return value of our service program
       int returnValue = 0;
       try
       {
              // Create a new service container and run it
              SampleServiceContainer myContainer = new SampleServiceContainer();
              myContainer.Run();
       }
...

Step 12: Catch exceptions

Catch exceptions in case the container fails to start running.

...
       catch( Exception ex )
       {
              // report exception
              Console.WriteLine( "Exception caught ... " + ex.ToString() );
              returnValue = -1;
       }
...