Consume SAP Webservice data with DotNet C# ECMA2 MIM Connector

I have already discussed in Part 1 how to setup SAP and publish the functions you want via a Webservice. In this part 2 I will discuss how to consume the data in ECMA2. As discussed in Part 1, you just publish the functions and then you can use the functions. The Webservice does not return data results like the DotNet Webservice we created in an earlier blogpost. This Webservice returns functions and actually brings you into the SAP ABAP environment where you can call the functions and get the results directly. This is why you need to create a proxy client, the client will take your login credentials, acting as you will query the Webservice and get the functions advertised then these functions are consumed into a library which you can call like any other local library and then work with the results. This is the simplest way I can explain what we are about to do.

Create Webservice Description WSDL for the Proxy client.

WSDL is a description of the services or functions that are advertised by the Webservice. There are two ways to supply the information of the of the Webservice to the Proxy client, you can do it via the url or you can put the SOAP data into a file and supply the data. I prefer the second option, there are some items to work out but I find it is a faster way to grab the data.

Open IE and enter the web service url you create in SAP. Enter the SAP credentials when prompted.

The WSDL for the webservice will be retured. On the web page, right click and click “save target as”. Save it on a folder in your drive C:\sapblog1. The file should of extension *.wsdl. I will call it “mimsapconnector2.wsdl”

Open the saved file with Notepad or Notepad++. Replace “System” with “SYSTEM” and “parameters” with “parameter”. Match the case. These changes are important for VStudio.

Generate the Proxy Client

In VStudio open a new solution. Chose Class Library, rename it

Right click on the Project and select “Add service reference”

Click Advanced

Click “Add web reference”

Enter the save file path

Click on the ‘Allow block content’ as shown below. Which show the generated WSDL. Enter ‘SAPWebserviceNamespace’ in the ‘Web reference name’ text box and click on ‘Add Referance’ button. Which will generate the .NET proxy class of the client to consume.

In VStudio, compile the solution

Using the Proxy client in ECMA2

Creating the ECMA2 Dll file

  1. In the Synchronization Manager, at the top, select Management Agents.
  2. At the top, select Actions, select Create Extension Projects, and choose Extensible Connectivity 2.0 Extension. This will open a Create Extension Project dialog box.
  3. In the Create Extension Project dialog box, next to Programming Language: select Visual C#
  4. In the Create Extension Project dialog box, next to Visual Studio Version: select Visual Studio 2010.
  5. In the Create Extension Project dialog box, next to Project Name: enter WSC1_ECMA2.
  6. Click OK. This will launch Visual Studio 2010.
  7. On the right, at the top, under the Solutions Explorer, double-click WSC1_ECMA2. This will open the file with code that has already been written.
  8. Add the following references

    Lets talk about something interesting that will be very useful to most MIM consultants connecting to SAP. Most times it is done to get HR data. The HR function is not present in NetWeaver, you have to get the full SAP package with the HR module to get it. The function we are going to call is called BAPI_USER_GETLIST and BAPI_USER_GET_DETAIL, this is similar to the HR function BAPI_EMPLOYEE_GETDATA.

    Search or find

    BAPI_USER_GETLIST – Search function, you do not have to declare a specific search criteria to use it. This will return the firstname, lastname, full name of the user.

    BAPI_USER_GET_DETAIL– It is a find function so you must declare a search criteria, the Userid. It will not return name details but will return information like the Roles the user belongs to.

    BAPI_EMPLOYEE_GETDATA – It is a find function so you must declare a search criteria. To get all employees you can use a search for Firstname = “%”. It will return tables with information about the user. You can query these tables with the employee number returned.

    Return data

    These functions return the usernames or personnel numbers of users, it does not return the key user attributes like department, office address etc. But both functions return tables. For the BAPI_USER_GET_DETAIL and the BAPI_EMPLOYEE_GETDATA they return a table and with the key attribute (username or personnel number) we can get the details we want. For this blogpost we are going to call BAPI_USER_GET_DETAIL, this returns a table called “Activity Groups”, we are going to use the username to return some attributes from that table, like the Roles assigned to the Users. For BAPI_EMPLOYEE_GETDATA, it returns a several tables

    * org_assignment = ” bapip0001b Results List for Organizational Assignment

    * personal_data = ” bapip0002b Results List for Personal Data

    * internal_control = ” bapip0032b Results List for Internal Control

    * communication = ” bapip0105b Results List for Communication

    * archivelink = ” bapitoav0 Results List for Optical Archive

    You have to go through these tables to find the information you want. Discuss with the SAP HR admin to get the mapping of data you want to the different tables and their attributes. You can see in the attribute information in the wsdl returned by the webservice. E.g Here is info on Table bapip0032b

The ActivityGroup table returned by the BAPI_USER_GET_DETAIL shows the roles of the user

Copy the code below into your cs file and compile. There will be a mimsapconnector2.dll file also compiled. Copy that file to your extensions folder as well as the WSAP1_ECMA2.dll.

Elements of the Code

The page size is very important. The ECMA will first load the records into memory and then cycle through them. Make sure the page size is enough. If there are 4200 SAP users or employees then use this

private int m_importDefaultPageSize = 5200;

private int m_importMaxPageSize = 5500;

Also when creating the run profile make sure your page size is adequate. This initial load may cause a little “delay” when the connector runs a full import.

The Code

using System;

using System.Data;

using System.IO;

using System.Xml;

using System.Text;

using System.Collections.Specialized;

using Microsoft.MetadirectoryServices;

using System.Collections.ObjectModel;

using System.Collections.Generic;

using System.Net;

using mimsapconnector2.SAPWebserviceNamespace;

namespace FimSync_Ezma

{

public class EzmaExtension :

//IMAExtensible2CallExport,

IMAExtensible2CallImport,

//IMAExtensible2FileImport,

//IMAExtensible2FileExport,

//IMAExtensible2GetHierarchy,

IMAExtensible2GetSchema,

IMAExtensible2GetCapabilities,

IMAExtensible2GetParameters

//IMAExtensible2GetPartitions

{

private int m_importDefaultPageSize = 1200;

private int m_importMaxPageSize = 1500;

public string Password;

public string Username;

public string path;

public string myFirst;

public string myLast;

public string myEmail;

public string myEmpID;

public string myFull;

public string myAccount;

public string myActivityGroupName=””;

//

// Constructor

//

public EzmaExtension()

{

//

// TODO: Add constructor logic here

//

}

public MACapabilities Capabilities

{

get

{

MACapabilities myCapabilities = new MACapabilities();

myCapabilities.ConcurrentOperation = true;

myCapabilities.ObjectRename = false;

myCapabilities.DeleteAddAsReplace = true;

myCapabilities.DeltaImport = true;

myCapabilities.DistinguishedNameStyle = MADistinguishedNameStyle.None;

myCapabilities.ExportType = MAExportType.AttributeUpdate;

myCapabilities.NoReferenceValuesInFirstExport = false;

myCapabilities.Normalizations = MANormalizations.None;

return myCapabilities;

}

}

public int ImportDefaultPageSize

{

get

{

return m_importDefaultPageSize;

}

}

public int ImportMaxPageSize

{

get

{

return m_importMaxPageSize;

}

}

public CloseImportConnectionResults CloseImportConnection(CloseImportConnectionRunStep importRunStep)

{

return new CloseImportConnectionResults();

}

public IList<ConfigParameterDefinition> GetConfigParameters(KeyedCollection<string, ConfigParameter> configParameters, ConfigParameterPage page)

{

List<ConfigParameterDefinition> configParametersDefinitions = new List<ConfigParameterDefinition>();

switch (page)

{

case ConfigParameterPage.Connectivity:

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“path”, “”));

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“Username”, “”));

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“Password”, “”));

break;

//case ConfigParameterPage.Global:

// break;

//case ConfigParameterPage.Partition:

// break;

//case ConfigParameterPage.RunStep:

// break;

}

return configParametersDefinitions;

}

public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRunStep)

{

List<CSEntryChange> csentries = new List<CSEntryChange>();

GetImportEntriesResults importReturnInfo;

//Get the SAP data

BAPIUSNAME[] userName = new BAPIUSNAME[100];

var cred = new System.Net.NetworkCredential(this.Username, this.Password);

BAPI_USER_GETLIST bapi_user_getList = new BAPI_USER_GETLIST();

bapi_user_getList.USERLIST = userName;

zmimsapconnector2 webService = new zmimsapconnector2();

{

webService.Credentials = cred;

var respUserList = webService.BAPI_USER_GETLIST(bapi_user_getList);

for (int i = 0; i < respUserList.USERLIST.Length; i++)

{

myFirst = respUserList.USERLIST[i].FIRSTNAME.ToString().Trim();

myLast = respUserList.USERLIST[i].LASTNAME.ToString().Trim();

myFull = respUserList.USERLIST[i].FULLNAME.ToString().Trim();

myAccount = respUserList.USERLIST[i].USERNAME.ToString().Trim();

// Lets info from the Activity table

BAPI_USER_GET_DETAIL bpi_user_detail = new BAPI_USER_GET_DETAIL();

bpi_user_detail.USERNAME = myAccount;

bpi_user_detail.ADDCOMREM = new BAPICOMREM[100];

bpi_user_detail.ACTIVITYGROUPS = new BAPIAGR[100];

bpi_user_detail.ADDFAX = new BAPIADFAX[100];

bpi_user_detail.ADDPAG = new BAPIADPAG[100];

bpi_user_detail.ADDPRT = new BAPIADPRT[100];

bpi_user_detail.ADDRFC = new BAPIADRFC[100];

bpi_user_detail.ADDRML = new BAPIADRML[100];

bpi_user_detail.ADDSMTP = new BAPIADSMTP[100];

bpi_user_detail.ADDSSF = new BAPIADSSF[100];

bpi_user_detail.ADDTEL = new BAPIADTEL[100];

bpi_user_detail.ADDTLX = new BAPIADTLX[100];

bpi_user_detail.ADDTTX = new BAPIADTTX[100];

bpi_user_detail.ADDURI = new BAPIADURI[100];

bpi_user_detail.ADDX400 = new BAPIADX400[100];

bpi_user_detail.EXTIDHEAD = new BAPIUSEXTIDHEAD[100];

bpi_user_detail.EXTIDPART = new BAPIUSEXTIDPART[100];

bpi_user_detail.GROUPS = new BAPIGROUPS[100];

bpi_user_detail.PARAMETER = new BAPIPARAM[100];

bpi_user_detail.PARAMETER1 = new BAPIPARAM1[100];

bpi_user_detail.PROFILES = new BAPIPROF[100];

bpi_user_detail.RETURN = new BAPIRET2[100];

bpi_user_detail.SYSTEMS = new BAPIRCVSYS[100];

bpi_user_detail.UCLASSSYS = new BAPIUCLASSSYS[100];

zmimsapconnector2 userDetailWebService = new zmimsapconnector2();

{

myActivityGroupName = “”;

userDetailWebService.Credentials = cred;

var respUserDetail = userDetailWebService.BAPI_USER_GET_DETAIL(bpi_user_detail);

for (int k = 0; k < respUserDetail.ACTIVITYGROUPS.Length; k++)

{

myActivityGroupName = myActivityGroupName + respUserDetail.ACTIVITYGROUPS[k].AGR_NAME.ToString().Trim() + “;”;

}

}

CSEntryChange csentry1 = CSEntryChange.Create();

csentry1.ObjectModificationType = ObjectModificationType.Add;

csentry1.ObjectType = “Person”;

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Firstname”, myFirst));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Lastname”, myLast));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Fullname”, myFull));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“AccountName”, myAccount));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“SAPRole”, myActivityGroupName));

csentries.Add(csentry1);

}

}

importReturnInfo = new GetImportEntriesResults();

importReturnInfo.MoreToImport = false;

importReturnInfo.CSEntries = csentries;

return importReturnInfo;

}

public Schema GetSchema(KeyedCollection<string, ConfigParameter> configParameters)

{

Microsoft.MetadirectoryServices.SchemaType personType = Microsoft.MetadirectoryServices.SchemaType.Create(“Person”, false);

//myname = configParameters[“myname”].Value;

string myattribute1 = “Firstname”;

string myattribute2 = “Lastname”;

string myattribute3 = “AccountName”;

string myattribute4 = “Fullname”;

string myattribute5 = “SAPRole”;

if (myattribute1 == “Firstname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute1, AttributeType.String));

}

if (myattribute2 == “Lastname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute2, AttributeType.String));

}

if (myattribute3 == “AccountName”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute3, AttributeType.String));

}

if (myattribute4 == “Fullname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute4, AttributeType.String));

}

if (myattribute5 == “SAPRole”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute5, AttributeType.String));

}

Schema schema = Schema.Create();

schema.Types.Add(personType);

return schema;

}

public OpenImportConnectionResults OpenImportConnection(KeyedCollection<string, ConfigParameter> configParameters, Schema types, OpenImportConnectionRunStep importRunStep)

{

this.path = configParameters[“path”].Value;

this.Username = configParameters[“Username”].Value;

this.Password = configParameters[“Password”].Value;

return new OpenImportConnectionResults();

}

public ParameterValidationResult ValidateConfigParameters(KeyedCollection<string, ConfigParameter> configParameters, ConfigParameterPage page)

{

ParameterValidationResult myResults = new ParameterValidationResult();

return myResults;

}

};

}

Create the Webservice ECMA2 connector

  1. In the Synchronization Manager, click Management Agents
  2. In the Synchronization Service, at the top, select Management Agents and over on the right, under Actions, select Create. This will open a Create Management Agent wizard.
  3. On the Create Management Agent screen, next to Management Agent for: select Extensible Connectivity 2
  4. On the Create Management Agent screen, next to Name: enter WSC1_ECMA2

  1. Remove the check from Run this management agent in a separate process. This will allow for debugging should the need arise.
  2. Click Next.
  3. On the Select Extension DLL screen, click Browse and select WSAP_ECMA2.dll. Click OK.
  4. On the Select Extension DLL screen, click Refresh interfaces. This will populate the box below. It should support Import

  1. Click Next.
  2. On the Connectivity screen, next to path enter: the Webservice url you have already tested. Actually I don’t user this value in the code since I have the proxy client, you can remove it in the code if you want.
  3. On the Connectivity screen, next to username enter: the id that you tested with
  4. On the Connectivity screen, next to password enter: password

  1. Click Next
  2. On the Configure Partitions and Hierarchies screen, leave the defaults.

  1. Click Next.
  2. On the Select Object Types screen, select Person.

  1. Click Next.
  2. On the Select Attributes screen, select all six.

  1. Click Next.
  2. On the Configure Anchors screen, click Specify Anchor. This will open a Set Anchor dialog box.
  3. On the Set Anchor dialog box, you may see several attributes there, click specify anchor and remove all attributes except AccountName and click Add>. Click OK.

  1. Click Next.
  2. On the Configure Connector Filter screen, click Next.

  1. On the Configure Join and Projection Rules screen, click New Projection Rule. This will open a Projection dialog box.
  2. On the Projection dialog box, verify Declared is selected.
  3. On the Projection dialog box, verify Person is in the box next to Metaverse object type.
  4. Click OK.
  5. On the Configure Join and Projection Rules screen, click New Join Rule. This will open a Join Rule for Person dialog box.
  6. Under Data source attribute select AccountName.
  7. Under Metaverse attribute select AccountName.
  8. Click OK. This will bring up a dialog box that states you are attempting to join a non-indexed metaverse attribute. Click OK. Click OK.

  1. Click Next
  2. Configure Attribute flow as shown

  1. Click Next
  2. On the Configure Deprovisioning screen, select “Make them disconnectors”

  1. Click Next
  2. On the Configure Extensions screen click finish.

  1. Configure Full Import run profile
  2. Run the Import, it should be successful and you can test sync via the preview screen to see that the person is provisioned to the MV.