C# Language-Specific Library

The C# IC library provides a .NET implementation to communicate with IC servers via the REST interface. The library is largely a one-to-one pass-through to the REST service. It provides a collection of objects that represent resources and objects made available by the API. It abstracts away the work of performing low-level HTTP requests and packaging up data to send back and forth. With the C# layer, you don’t have to know the exact URLs for things and it takes the guesswork out of supplying valid data objects since it enforces that for you.

This topic contains the following sections:

Overview

The library components are provided in an assembly called ININ.ICWS.Managed.dll. Each namespace from the REST API is duplicated into namespaces provided by this assembly. Inside each namespace you will find the resources and data contracts necessary to work with the ICWS API. A resource is an object that will contain one or more methods, such as connection.CreateConnection(...) (where connection is an instance of type ININ.ICWS.Connection.ConnectionResource ). The first parameter to the function will always be a request parameter object that is required for that method, such as ININ.ICWS.Connection.ConnectionResource.CreateConnectionRequestParameters. These parameters are the headers, templates, and query options used by this resource. The second parameter is a data contract object that contains the request body. In both cases, the required fields are taken in through the constructor and optional properties can be specified via properties.

On the result of the REST call, the library will return a .NET Task that will give you a specific responses object, such as ININ.ICWS.Connection.ConnectionResources.CreateConnectionResponses. In that object you can access the status code result and any headers that were returned in the result. In addition, it will allow you to perform actions for a specific status code by calling the respective PerformIf, such as PerformIfRsponseIs201. In this case the action you pass in will only execute if that status code was received in the response. The action will also have data contact parameter that represents that ICWS response body. It is important to note that the task can return in an exception or cancellation state in the case that the web request did not complete, such as a timeout or abort case.

Connecting

The first thing you will need is an implementation of ININ.WebServices.Core.IWebServiceUtility. There is a default implementation provided by ININ.WebServices.Core.WebServiceUtility. Once you have a WebServiceUtility populated with your server information, you can construct any resource objects from the library.

The ININ.ICWS.Connection.ConnectionResource object has a CreateConnection method that will create the connection to the IC server. This method requires the CreateConnectionRequestParameters object which takes the requested language to set in the request header. The method also requires a derived implementation of the BaseAuthConnectionRequestSettingsDataContract. This object will contain the essential connection parameters needed for that authentication type, such as the user name and password for IC Authentication.

Once a connection has been established, the WebServiceUtility.SessionParameters property should be set with a new instance of the AuthenticationParameters class to provide the session information to all subsequent requests that require it.

Messaging

The ININ.ICWS.Messaging.MessagesResource object is used to retrieve messages from the IC server. The messages are events that are raised on the IC server, such as a status changing or an interaction being added. If using short-polling, the messages are queued on the server and should be retrieved on a regular interval as the queue will start filling up again as soon as a batch of messages is retrieved. If using server-sent events, the messages are not queued on the server, and are sent as they occur. The client application should check the messaging feature version to determine if server-sent events is supported on the server. Server-sent events are supported with messaging version 2 and higher. See the Versioning page for further information on handling versioning.

If using short-polling, calling MessagesResource.GetMessages will request the batch of messages from the server. Like other resource method calls, the second parameter is a task that returns the response information. If you receive a 200 status code from the GetMessages call, the data contract it returns will contain the list of messages that were received. The array may be empty.

Putting It Together: Simple Example

using System;
using System.Net;
using System.Threading.Tasks;

using ININ.ICWS.Common;
using ININ.ICWS.Connection;
using ININ.ICWS.Messaging;
using ININ.WebServices.Core;

namespace ININ.ICWS.Examples
{
    public class ExampleApp
    {
        private readonly WebServiceUtility _webServiceUtility;

        public ExampleApp()
        {
            // Create the WebServiceUtility using correct server information.
            // DocumentationExample is your application's user agent used during web requests.
            _webServiceUtility = new WebServiceUtility("DocumentationExample")
            {
                Port = 8019,
                Server = "HostServerName",
                IsHttps = true
            };
        }

        public void PerformConnection()
        {
            // Connect to the IC server.
            var connectionResource = new ConnectionResource(_webServiceUtility);
            var requestParameters = new ConnectionResource.CreateConnectionRequestParameters();
            requestParameters.Accept_Language = "en-US";
            // Request that the connection response return the supported feature versions.
            requestParameters.Include = "features";
            var dataContract = new IcAuthConnectionRequestSettingsDataContract();
            dataContract.ApplicationName = "Example";
            dataContract.UserID = "username";
            dataContract.Password = "password";

            Task<ConnectionResource.CreateConnectionResponses> createConnectionTask 
            = connectionResource.CreateConnection(requestParameters, dataContract);
            createConnectionTask.ContinueWith(t =>
                {
                    ConnectionResource.CreateConnectionResponses createConnectionResponses = t.Result;

                    createConnectionResponses.PerformIfResponseIs201(response => HandleConnection201(response, createConnectionResponses.Set_Cookie));
                    createConnectionResponses.PerformOnFailureResponse(HandleError);
                    createConnectionResponses.PerformDefault(HandleDefault);
                }, TaskContinuationOptions.OnlyOnRanToCompletion);
            createConnectionTask.ContinueWith(t => HandleError(t.Exception), 
                    TaskContinuationOptions.OnlyOnFaulted);
        }

        private void HandleConnection201(ConnectionResponseDataContract response, string cookie)
        {
            _webServiceUtility.SessionParameters =
                new AuthenticationParameters
                    {
                        SessionId = response.SessionId,
                        Cookie = cookie,
                        ININ_ICWS_CSRF_Token = response.CsrfToken
                    };

            // A 201 represents a successful connection, response.alternateHostList are the paths
            // to alternate servers.
            // We can now set our station.
            ChangeStation();
            
            var messagingFeatureVersion = 0;
            
            // Get the messaging version, for use in determining if short-polling or 
            // server-sent events should be used.
            if (response.FeaturesHasValue)
            {
                foreach (FeatureInfoDataContract feature in response.Features)
                {
                    if (!feature.FeatureIdHasValue || !feature.VersionHasValue) continue;
                    if (feature.FeatureId != "messaging") continue;

                    messagingFeatureVersion = feature.Version;
                    break;
                }
            }
            
            // Start processing messages.
            StartMessageProcessing(messagingFeatureVersion);
        }

        private void ChangeStation()
        {
            var stationResource = new StationResource(_webServiceUtility);
            var requestParameters 
            = new StationResource.ChangeStationConnectionRequestParameters();
            var dataContract = new WorkstationSettingsDataContract();
            dataContract.Workstation = "ExampleStation";

            Task<StationResource.ChangeStationConnectionResponses> changeStationConnectionTask 
            = stationResource.ChangeStationConnection(requestParameters, dataContract);
            changeStationConnectionTask.ContinueWith(t => HandleError(t.Exception), 
            TaskContinuationOptions.OnlyOnFaulted);
        }
        
        private void StartMessageProcessing(int messagingFeatureVersion)
        {
            // Here we check the messaging feature version and start the appropriate message handler.
            if (messagingFeatureVersion < 2)
            {
                StartShortPollingMessageProcessing();
            }
            else
            {
                StartServerSentEventsMessageProcessing();
            }
        }

        private void StartShortPollingMessageProcessing()
        {
            // Poll the server of any events that have occurred.  This should be expected
            // on a regular interval.
            
            // TODO:  This example does not show putting this call in an interval.
        
            var messagesResource = new MessagesResource(_webServiceUtility);
            var requestParameters = new MessagesResource.GetMessagesRequestParameters();
            Task<MessagesResource.GetMessagesResponses> messagesTask
            = messagesResource.GetMessages(requestParameters);
            messagesTask.ContinueWith(t =>
                {
                    var result = t.Result;
                    result.PerformIfResponseIs200(messages =>
                        {
                            // messages contains an array of server side events that were retrieved
                            foreach (MessageDataContract dataContract in messages)
                            {
                                HandleMessage(dataContract);
                            }
                        });
                    result.PerformOnFailureResponse(HandleError);
                    result.PerformDefault(HandleDefault);
                }, TaskContinuationOptions.OnlyOnRanToCompletion);
            messagesTask.ContinueWith(t => HandleError(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
        }

        private void StartServerSentEventsMessageProcessing()
        {
            // TODO:  The following code assumes a private variable exists for an appropriate
            // EventSource object, and hooks into a MessageReceived event.  The MessageReceived
            // event assumes a string JSON parameter that is then converted to an appropriate
            // MessageDataContract that can then be processed.

            /*
            if (_EventSource == null)
            {
                // TODO:  This URI will need to be updated appropriately.
                _EventSource = new EventSource("https://HostServerName:8019/icws/{sessionId}/messaging/messages");
            }

            _EventSource.MessageReceived += (json) =>
            {
                var dataContract = MessageDataContract.CreateFromJson(json);

                HandleMessage(dataContract);
            };
            */
        }
        
        private void HandleMessage(MessageDataContract dataContract)
        {
            // Omitted:  TODO, handle dataContract.
        }

        private void HandleDefault(HttpStatusCode statusCode)
        {
            // Omitted:  TODO, handle statusCode.
        }

        private void HandleError(HttpStatusCode statusCode)
        {
            // Omitted:  TODO, handle statusCode.
        }

        private void HandleError(Exception exception)
        {
            // Omitted:  TODO, handle exception.
        }
    }
}