Skip to Content

Afaria API 101 - Consuming the Afaria API Services and Creating a Proof of Concept Application

Introduction

The Afaria API services offer a range of possibilities in customizing how Users, Administrators or Help Desk personnel interact with an Afaria environment. From managing existing users through updates to user information or complete user removal, to monitoring system health, the API services offer all the functionality that the Afaria Administrator does and more. This article will detail how to consume the services available, the requirements and restrictions of using the Afaria API, and some basic functions that are available the SAP Afaria API Server Namespace.

Requirements & Assumptions

  • SAP Afaria 7.0
  • Microsoft Visual Studio
  • Afaria API Service Account user information
  • Experience programming with .NET based languages

Table of Contents

SAP Afaria Services Overview

SAP Afaria API services help third-party developers to develop solutions for their enterprises. SAP Afaria API services are implemented using Windows Communication Foundation (WCF) for .NET Framework 4.0. The services are installed as a part of the Afaria Administrator services, and are available as "Afaria API Service" in the Add/Remove program list and as "Afaria API" in the Service management console.

1. Programming Languages Available

The SAP Afaria Service is very versatile but in its current incarnation there are some restrictions to the Programming Languages that can utilize it. The SAP Afaria API Services can only be consumed using Windows based languages(C#, Visual Basic, Etc.). This derives from the way the API authenticates with Windows. The service account for the Afaria API Service is the only account that can authenticate against the API service and utilizes the Windows NTLM authentication protocol to do so. Due to the use of NTLM, Java and other languages are unable to properly authenticate against the service because they are not able to impersonate the Service User. Impersonation will be covered in the section 6.

2. Creating the Project in Visual Studio

The API services can be utilized by any of the default Visual Studio project types. For simplicity of this example we will be creating a Console Based Application using C# in Visual Studio 2010.

  1. Open Visual Studio.
  2. Select File > New > Project, as seen below.
  3. Select Console Application and provide a name for the application.
  4. Select OK.

3. Creating the Service Reference

By default the API Service is set to allow requests over port 7980 for HTTP and 7981 for HTTPS but this can be changed based on environment and need. If a change of port is necessary in your environment it can be done via the AfariaServiceHost.exe.config. This will be covered in sub-section on Changing Ports.

  1. Consuming the Service via HTTP

    1. In Solution Explorer, right-click Service References and select "Add Service Reference...".
    2. In the Add Service Reference pop-up, enter "http://<IPAddress>:7980/AfariaService/<ServiceName>" like pictured below and select Go to discover the service.
    3. Once discovered, provide a Namespace for the service. For the purposes of this demo, we will choose to name it Server.
    4. Select OK.

  2. Consuming the Service Via HTTPS Part 1

    To consume the service via HTTPS there are some additional steps that will need to be taken in order to expose the service, including generating either a self-signed certificate or obtaining a public certificate and binding them in IIS on the SAP Afaria API sever. The following steps will be performed on the machine that has the API Service Installed

    1. Obtain a Certificate from a Trusted Third Party source or generate a Self-Signed Certificate.
    2. Open Internet Information Services Manager.
    3. Expand Sites.
    4. Select Default Website.
    5. Select Add...
    6. Choose HTTPS.
    7. Enter Port 7981.
    8. Select the Certificate that was obtained or generated.

  3. Consuming the Service via HTTPS Part 2

    Once the previous steps are completed, you will be able to consume the Services via HTTPS by following these steps:

    1. Open the Visual Studio Project created previously.
    2. In Solution Explorer, right-click Service References and select Add Service Reference...
    3. In the Add Service Reference pop-up, enter "http ://<IPAddress>:7980/AfariaService/<ServiceName>" like pictured below and select Go to discover the service.

      Note: When using a Self-Signed Certificate, a pop-up will occur that notifies you that the certificate is not trusted unless you install the Root Certificate of the Certificate Authority that issued the Self-Signed Certificate onto your machine. This is an example of the message:
    4. Once discovered, provide a Namespace for the service. For the purposes of this demo, we will choose to name it Server.
    5. Select OK.

  4. Changing Ports

    If a change of port is necessary in your environment, it can be done via the AfariaServiceHost.exe.config file. This file can be found in the folder:

    <Afaria API Install Directory>\AfariaApiService\Bin. The entries related to the consumption of the API services are:

    <add key="AS_BaseHttpAddress" value="http://localhost:7980/AfariaService"/>

    <add key="AS_BaseHttpsAddress" value="https://localhost:7981/AfariaService"/>

    After changing the port numbers, a restart of the API services is required.

4. Viewing Available Services/Classes/Methods

Each Service provided by the Afaria API contains a set of Classes and Methods that are made available when the Service Reference is created. Using the Object Explorer in Visual Studio will allow you to see what Classes/Methods available. A list of Services provided by the Afaria API is published in the “Afaria 7 – Developers Guide”, available on SAP Service MarketPlace and Frontline.Sybase.com.

  1. In Visual Studio, right-click the previously created Service Reference in Solution Explorer.
  2. Select View in Object Browser.
  3. Expand your project (For this demo the name is API101).
  4. Expand ProjectName.ServiceReferenceName(For this demo it is API101.Server).
  5. Explore the Classes, Objects, and Methods Available. If you want to view the calls specific to the service, view the I<ServiceName>Service (For this demo, it is IServerService).


5. Binding Options and Editing the App.config file

  1. Overview of Bindings

    The program you generate can call into the API using three different binding methods, WSHttpBinding, NetTcpBinding, and NetNamedPipeBinding.

    • WSHttpBinding is a secure interoperable binding that supports distributed transactions and secure, reliable sessions and can be used to establish connections across different machines. It is the slowest of the three bindings available.
    • NetTcpBinding generates a run-time communication stack by default, which uses transport security, TCP for message delivery and a binary message encoding. NetTcpBindings are faster than WSHttpBindings and are also viable for establishing connections across different machines.
    • NetNamePipeBinding is the fastest of the three bindings and generates a run-time communication stack by default, which uses transport security, named pipes for message delivery, and a binary message encoding. NetNamedPipeBinding is only viable for on-machine communications so it is not viable for connecting across different machines.

      These binding methods are configured with default settings in the app.config that is created in the project when the API Service is consumed.  In some circumstances these default configurations will need to be edited in order to properly use the API Services. The following sections cover some changes that may be necessary.

  2. Editing the Endpoint Address

    Depending on the API Service configuration, you may need to edit the Endpoint Address. This address defines where the API service that will accept the calls is located. The following excerpt shows the fields related to the connection for the wsHttpBinding and netTcpBinding:

    <endpoint address = "http://localhost:7980/AfariaService/Server" binding="wsHttpBinding"

    bindingConfiguration="WSHttpBinding_IServerService"
    contract="Device.IServerService" name="WSHttpBinding_IServerService">

        <identity>

              <userPrincipalName value="domain\svc_account" />

        </identity>

    </endpoint>

    <endpoint address="net.tcp://localhost:7982/AfariaService/Server" binding="netTcpBinding"

    bindingConfiguration="NetTcpBinding_IServerService"

    contract="Device.IServerService" name="NetTcpBinding_IServerService">

        <identity>

              <userPrincipalName value="domain\svc_account" />

        </identity>

    </endpoint>



    As you can see each Binding type has its own endpoint address. If no port changes have been made to the API Service, no edits will be necessary to the endpoint address. If the development environment is on a different computer from the API Service, it is possible to specify the API Service address in the app.config file by editing the endpoint address, but it is advisable to set this through code, as described in Section 6

  3. Changing the Timeout Settings

    The following information is quoted from the Afaria Developers Guide:

    In WCF, a timeout occurs if a service does not respond to a request within a reasonable amount of time. The default timeout period for a WCF binding is one minute. If a service API does not respond within the specified amount of time, a timeout occurs and an exception is generated on the client, which renders the service channel unusable.

    In the app.config file inside your project the setting for the sendTimeout can be adjusted to give more time for the Service to respond. Below shows a sample of the default configurations for wsHTTPBindings. As you can see from the example, timeouts are in HH:MM:SS format and can be configured as necessary.

    <wsHttpBinding>

        <binding name="WSHttpBinding_IServerService" closeTimeout="00:01:00"

          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

          bypassProxyOnLocal="false" transactionFlow="false"
          hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288"
          maxReceivedMessageSize="65536"
    messageEncoding="Text"
          textEncoding="utf-8" useDefaultWebProxy="true"
    allowCookies="false">

              <readerQuotas maxDepth="32" maxStringContentLength="8192"  maxArrayLength="16384"

                maxBytesPerRead="4096"  maxNameTableCharCount="16384" />

              <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />

              <security mode="Message">

                  <transport clientCredentialType="Windows"  proxyCredentialType="None" realm="" />

                  <message clientCredentialType="Windows" negotiateServiceCredential="true"

                    algorithmSuite="Default" />

              </security>

        </binding>

    </wsHttpBinding>


    These properties are also dynamically editable via code by specifying like the following (svcClient is the Service’s Client object), and changing the property in red to match each property mentioned in red in the app.config:

    ((System.ServiceModel.NetTcpBinding)svcClient.ChannelFactory.Endpoint.Binding).CloseTimeout = new TimeSpan(0,1,0);

  4. Increasing the Buffer Size

    The following information is quoted from the Afaria Developers Guide

    By default, WCF limits the receive buffer size for each binding. Many of the methods available from the Afaria API services return enough data to exceed this size. When this occurs, exception is be generated on the client, which renders the service channel unusable.

    In order to increase the amount of data that can be transferred, the maxReceivedMessageSize, maxBufferSize, maxDepth, maxStringContentLength, and maxArrayLength parameters will need to be changed in the app.config. The following is a sample of the wsHTTPBinding with the properties to edit in red:

    <wsHttpBinding>

        <binding name="WSHttpBinding_IServerService" closeTimeout="00:01:00"

          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"

          bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"

          maxBufferPoolSize="524288" maxReceivedMessageSize="65536"

          messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"

          allowCookies="false">

              <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"

              maxBytesPerRead="4096" maxNameTableCharCount="16384" />

              <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />

              <security mode="Message">

              <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />

              <message clientCredentialType="Windows"  negotiateServiceCredential="true" algorithmSuite="Default" />

              </security>

        </binding

    </wsHttpBinding>


    These properties are also dynamically editable via code by specifying the settings like as follows, and changing the property to match each property highlighted in red in the above app.config. (svcClient is the service's client object)

    ((System.ServiceModel.NetTcpBinding)svcClient.ChannelFactory.Endpoint.Binding).MaxReceivedMessageSize
        = 65536;

    ((System.ServiceModel.NetTcpBinding)svcClient.ChannelFactory.Endpoint.Binding).ReaderQuotas.MaxDepth
        = 32;


    Note: maxBufferSize only appears in the bindings configuration for netNamePipe and netTcp. maxRecievedMessageSize is the equivalent to maxBufferSize for wsHttp.

    Note: Microsoft details the maximum values for each of these configuration settings in their documentation. Please use Microsoft documentation to determine the max allowable size

  5. Increasing the Maximum Object Size

    The following information is quoted from the Afaria Developers Guide

    By default, WCF limits the number of objects, which can be serialized or deserialized.

    An object represents a class (specifically a DataContract) used as an input or output to an API call. These objects may have other objects embedded within (and so on).

    Many of the methods available from Afaria API services return a complex number of objects, which may exceed the default limit. When this occurs, an exception is generated on the client, which renders the service channel unusable.

    To address this, define an endpoint behavior needs to modify this value (specifically, the maxItemsInObjectGraph parameter in the app.config file). Once you define the behavior, reference it in the specific service endpoint configuration of concern.

    The maxItemsInObjectGraph allows for an int32 as defined by Microsoft. Anything larger will not compile.

    <behaviors>

        <endpointBehaviors>

              <behavior name="test">

                  <dataContractSerializer maxItemsInObjectGraph="32768"/>

              </behavior>

        </endpointBehaviors>

    </behaviors>


    After adding the behavior to the app.config, you will need to reference it in the binding as follows:

    <endpoint address=http://localhost:7980/AfariaService/Server behaviorConfiguration="test" binding="wsHttpBinding"  bindingConfiguration="WSHttpBinding_IServerService" contract="Server.IServerService" name="WSHttpBinding_IServerService">


    Note: Test is a placeholder name used for this document. In your environment, you can name the behavior to any valid string.

6. Establishing the Context, User and Connection

In the following section, we will start programming our application. The application will establish a connection to the API server, impersonate the API Service User, establish a Context and pull down the server information,

  1. Create a Service Client


    It is possible to create the Service Client without specifying the API Server address, but it is recommended to specify the address at this point so that changes to the environment will not require recompilation.

    string apiAddr = "net.tcp://" + "<API Service Server Address>" + & ":7982/AfariaService/Server";

    ServerServiceClient svc = new ServerServiceClient("NetTcpBinding_IServerService", apiAddr);

  2. Impersonate the Afaria API Service Account


    Specify the Domain, UserName and Password for the API Server Service account. At this point, this is the only valid account for authorization to the API service.

    svc.ClientCredentials.Windows.ClientCredential.Domain = "<Afaria Service Account Domain>";

    svc.ClientCredentials.Windows.ClientCredential.UserName = "<Afaria Service Account UserName>";

    svc.ClientCredentials.Windows.ClientCredential.Password = "<Afaria Service Account Password>";

  3. Define the Context


    A Context ID can be any string, but it is advisable to specify a GUID for the context, so that unwanted clashes of Context do not occur. For more, very important information about Context, please see section 8.

    string context = Guid.NewGuid().ToString();

  4. Initiate the Context for the Service


    This call associates the Context with the Service Client. Any further calls will operate within the context established by the InitContext call. It is possible to share the context among multiple service clients by calling InitContext with the same Context ID.

    svc.InitContext(context);

  5. Set the Server Context


    This can be found by looking at the registry key [HKLM\Software\Afaria\Afaria\Server\TransmitterId] on a server with the Afaria Server service.

    svc.SetServerIdContext("<Transmitter ID>");

  6. Get the status of the server and report it


    Let's get the Server Status for our example. The ServerStatus object provides information such as the ServerId, State, LastStartTime and LastStopTime, but we will just report the State for illustration purposes.

    ServerStatus status = svc.GetServerStatus();

    Console.WriteLine(status.ServerId + “ is “ + status.State);

7. Closing the Context

Whenever you use a context or a service channel, you should make sure that it is closed out properly to free up resources on the server. Since a context can be shared amongst multiple services, you would want to assure that all services were done operating within that context before closing. Below is an example on how to do this, with comments explaining each step.

try

{

    if (svc != null)

    {

          //get the state of the service

          System.ServiceModel.CommunicationState commState = svc.State;

          //if the service is faulted, we still need to abort

          if (commState == System.ServiceModel.CommunicationState.Faulted)

          {

              svc.Abort();

          }//If the state is not already closed or in the process of closing, we should close the context

          else if (commState != System.ServiceModel.CommunicationState.Closing &&

              commState != System.ServiceModel.CommunicationState.Closed)

          {

              //get the context info to get the context ID, although we saved this in the context string variable

              ContextInfo ci = svc.GetContextInfo();

              //assure that there is a valid context ID

              if (!string.IsNullOrWhiteSpace(ci.ContextId))

              {

                    //close the context

                    svc.CloseContext();

              }

              //now close the service

              svc.Close();

          }

          else //if the channel is closing/closed, attempt to close out the context, but don't need to close state

          {//we will likely throw an exception here.

              svc.CloseContext();

          }

          svc = null;

    }

}

catch (Exception ex)

{ //Just ouput the exception, proper handling should initiate a new service, initiate the context to the previous,

  // and then close out the context and service

    Console.WriteLine(ex.Message.ToString());

}

8. A Few Words About the Context

After creating each instance of a service channel, a "Context" must be initialized via the InitContext call. No other service calls are permitted unless the channel has made this call.

A "Context" can be thought of as the environment or context within which a consumer of the API is operating. A client can set and make changes to their operating environment as needed (e.g., change tenant ID, server ID, logged on user, farm ID). All services which use the same Context will automatically see the changes (e.g., a change to the context associated with an instance of the Devices service will impact an instance of Outbound service if it uses the same Context ID). Any changes to the environment should be coordinated among all service channels sharing the same Context, so that changes made to the Context don’t inappropriately impact another services.

Instead of creating a new context for each service channel opened, since a user or client will likely be operating under the same tenant and\or server when performing tasks that span multiple services, the context should be shared amongst the service channels. Doing so will reduce overhead in the consuming code, and reduce resources consumed by the API service.

A client can, and should, close its own context, to force a resource cleanup by calling CloseContext.  This operation should be coordinated among all channels sharing the same Context.

The Afaria API service performs a cleanup on the following items every 5 minutes (this value and the unused time periods below are configurable via the API Service config file, but modifications are not generally recommended):

  • Server ID (if unused for 30 minutes): COM object released.
  • Service (if unused for 60 minutes): any unused resource is freed.
  • Context (if unused 1440 minutes)

9. Full Example Program

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using API101.Server;

namespace API101

{

    class Program

    {

        static void Main(string[] args)

        {

            string apiAddr = "net.tcp://" + "127.0.0.1" + ":7982/AfariaService/Server";

            ServerServiceClient svc = new ServerServiceClient("NetTcpBinding_IServerService", apiAddr);

            svc.ClientCredentials.Windows.ClientCredential.Domain = "<API Service Account Domain>";

            svc.ClientCredentials.Windows.ClientCredential.UserName = "<API Service Account Username>";

            svc.ClientCredentials.Windows.ClientCredential.Password = "<API Service Account Password>";

            string context = Guid.NewGuid().ToString();

            svc.InitContext(context);

            svc.SetServerIdContext("<Transmitter ID>");

            ServerStatus status = svc.GetServerStatus();

            Console.WriteLine(status.ServerId + " is " + status.State);

            try

            {

                if (svc != null)

                {

                    //get the state of the service

                    System.ServiceModel.CommunicationState commState = svc.State;

                    //if the service is faulted, we still need to abort

                    if (commState == System.ServiceModel.CommunicationState.Faulted)

                    {

                        svc.Abort();

                    }//If the state is not already closed or in the process of closing, we should close the context

                    else if (commState != System.ServiceModel.CommunicationState.Closing &&

                        commState != System.ServiceModel.CommunicationState.Closed)

                    {

                        //get the context info to get the context ID, although we saved this in the context string variable

                        ContextInfo ci = svc.GetContextInfo();

                        //assure that there is a valid context ID

                        if (!string.IsNullOrWhiteSpace(ci.ContextId))

                        {

                            //close the context

                            svc.CloseContext();

                        }

                        //now close the service

                        svc.Close();

                    }

                    else //if the channel is closing/closed, attempt to close out the context, but don't need to close state

                    {//we will likely throw an exception here.

                        svc.CloseContext();

                    }

                    svc = null;

                }

            }

            catch (Exception ex)

            {//Just ouput the exception, proper handling should initiate a new service, initiate the context to the previous,

            // and then close out the context and service

                Console.WriteLine(ex.Message.ToString());

            }

            Console.Write("\r\n\r\nPress any key to close.");

            Console.ReadKey();

        }

    }

}

Tags: