Page tree
Skip to end of metadata
Go to start of metadata

Purpose

The purpose of this document is to serve as an introduction to the SAP Afaria API services, discuss important relevant topics and describe how to make a proof of concept application that consumes the API.

Overview

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

 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 a later section.

*NOTE: With the release of Afaria 7.0 SP5, session-less bindings, which use Basic authentication, have been made available, which allow for broader programming language use. These bindings are covered in SAP Afaria API 301. See the Related Content section below for link.

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.

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 Explorerright-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 " https :// <IPAddress>:7981/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.

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.

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 help.sap.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).
     

Binding Options and Editing the App.config file

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 inter-operable 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.

*NOTE: With the release of Afaria 7.0 SP5, session-less bindings, which use Basic authentication, have been made available, which allow for broader programming language use. These bindings are covered in SAP Afaria API 301. See the Related Content section below for link.

 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 the section Establishing the Context, User and Connection.

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 to match each property modified in the app.config (closeTimeout, openTimeout, receiveTimeout):

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

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 :

<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 edited in the above app.config (maxReceivedMessageSize, maxDepth, maxStringContentLength, maxArrayLength). (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

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.

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,

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); 

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>"; 

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 the section A few words about the Context.

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

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);  

 

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>");

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);  

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());  
} 

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)


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();  
        }  
    }  

 

Related Content

Related Documents

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

SAP Afaria API 201 - Build Your Own Enrollment Portal - Get an Activation ID and MDM-First Enrlloment Link

SAP Afaria API 202 - Build Your Own Enrollment Portal - Server Settings + Non-iOS Enrollment

SAP Afaria API 203 - Build Your Own Self Service Portal - View User Device Information & Inventory

SAP Afaria API 204 - Build Your Own Self Service Portal - User Self Service (Apply Policies/Lock/Unlock/Wipe/Delete)

SAP Afaria API 301 - Session-less Binding, Basic Authentication and Consuming the Afaria API through SOAP

SAP Afaria API 401 - Using the SchemaQuery Service

SAP Afaria API Samples

SAP Afaria API - How To Guide - Debugging the API and Creating a New Policy via API

1 Comment