Using the Salesforce Metadata API in .Net

The SalesForce Metadata API provides a way of deploying and retrieving customisations in your Salesforce instance such as custom objects. Like with the SOAP API for transactional data the documentation for setting up a connection in C# and .net is not great. So here I’ve outlined the process to connect and retrieve data from the API in this environment. I’ve used Visual Studio 2017 in these examples.

The example I’ll use is retrieving a Global Pick List and its values. Here’s the sample Pick List on Salesforce:

A salesforce global value set (or global picklist)

And here’s the output I’ll be getting back from the Metadata API:

<?xml version="1.0" encoding="UTF-8"?>
<GlobalValueSet xmlns="http://soap.sforce.com/2006/04/metadata">
    <customValue>
        <fullName>val1</fullName>
        <default>false</default>
        <label>value 1</label>
    </customValue>
    <customValue>
        <fullName>val2</fullName>
        <default>false</default>
        <label>value 2</label>
    </customValue>
    <customValue>
        <fullName>val3</fullName>
        <default>false</default>
        <label>value 3</label>
    </customValue>
    <masterLabel>TEST</masterLabel>
    <sorted>false</sorted>
</GlobalValueSet>

Salesforce API WSDL services

To start you’ll need to create to create a new C# console app in Visual Studio and add new services for both the Salesforce Partner API and the Metadata API. Follow the instructions here for setting up the Partner API. Once this is done download and add the Metadata API WSDL file to the project in the same way.

Generate the Salesforce Metadata API WSDL file

The Metadata API WSDL has the same issue as the Partner API so you’ll need to go through the same process of updating the Reference.cs file to remove the duplicate []s from the following lines:

private QuickActionLayoutItem[][] quickActionLayoutColumnsField;
public QuickActionLayoutItem[][] quickActionLayoutColumns {
private NavigationMenuItem[][] navigationLinkSetField;
public NavigationMenuItem[][] navigationLinkSet {

Connecting to the Salesforce Metadata API

As with the Partner API connecting to the Metadata API is a two step process. First login and then create a new endpoint for the actual instance metadata API. Here’s the code:

using System;
using System.IO;
using System.IO.Compression;
using System.ServiceModel;
using SFP = SFAPI.sf_partner;
using SFM = SFAPI.sf_metadata;

namespace SFAPI
{
    class Program
    {
        static void Main(string[] args)
        {
            string User = "myUser";
            string Password = "myPassword";
            string Token = "myToken";

            EndpointAddress SFEndpointAddress;
            SFM.SessionHeader SFSessionHeader;

            SFP.SoapClient sc = new SFP.SoapClient();
            SFP.LoginResult lr = sc.login(null, null, User, Password + Token);

            SFEndpointAddress = new EndpointAddress(lr.metadataServerUrl);
            SFSessionHeader = new SFM.SessionHeader();
            SFSessionHeader.sessionId = lr.sessionId;
            sc.Close();

            SFM.MetadataPortTypeClient metadata_client = new SFM.MetadataPortTypeClient("Metadata", SFEndpointAddress);

        }
    }
}

This is similar to using the Partner API but the SessionHeader is from the metadata service and the final endpoint is the metadataServerUrl in the login result. Finally the metadata client is created using:

SFM.MetadataPortTypeClient metadata_client = new SFM.MetadataPortTypeClient("Metadata", SFEndpointAddress);

You can now execute requests against the metadata client.

Creating a Metadata API Package

In order to retrieve any metadata you have to define a package detailing the requested data. The package I’ll create is very simple but Salesforce documents some complex examples. The package is a simple XML document that specifies a metadata type name – for my example a GlobalValueSet. And the name of one or members of that metadata type – for my example the Global Picklist called TEST:

<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>TEST</members>
<name>GlobalValueSet</name>
</types>
<version>51.0</version>
</Package>

The Metadata API does not require you to create the XML however. The package can be constructed as follows:

// Create a package
SFM.Package p = new SFM.Package();
SFM.PackageTypeMembers ptm = new SFM.PackageTypeMembers();
ptm.name = "GlobalValueSet";
string[] members = new string[1];
members[0] = "TEST";
ptm.members = members;
SFM.PackageTypeMembers[] ptms = new SFM.PackageTypeMembers[1];
ptms[0] = ptm;
p.types = ptms;

The package has a types property that is an array of Package Type Members. Each package type member has a name which you set to the metadata type to retrieve (for this example GlobalValueSet) and an array of members to which you add the names of the members of the metadata type (for this example the picklist name TEST).

Retrieving a Metadata API Package

To retrieve the metadata you create a retrieve request and attach the package to the (confusingly named) unpackaged property. The retrieval is processed asynchronously so the request returns an AsyncResult that you can use to check the status of the request.

//create a retrieve request
SFM.RetrieveRequest rr = new SFM.RetrieveRequest();
rr.unpackaged = p;
SFM.AsyncResult ar;
SFM.RetrieveResult retResult;

//execute a retrieve request and wait for it to complete
ar = metadata_client.retrieve(SFSessionHeader, null, rr);
retResult = metadata_client.checkRetrieveStatus(SFSessionHeader, null, ar.id, true);

while (retResult.status.ToString() == "Pending") {

                System.Threading.Thread.Sleep(1000);
                retResult = metadata_client.checkRetrieveStatus(SFSessionHeader, null, ar.id, true);

}

Opening the Metadata Zip File

The result of the retrieve request is a zip file containing the requested metadata. Not an ideal format but .net provides some simple tools for processing zips. First save the zip file so you can examine the contents.

//output to a file
Byte[] file;
string path = "C:\\Temp\\output.zip";
file = retResult.zipFile;
File.WriteAllBytes(path, file);
MemoryStream ms = new MemoryStream(file);

The saved zip looks like this:

A salesforce metadata API zip file result containing global value set values

Each zip contains the package.xml definition of the type and members requested, about which we care not very much, and the response XML with the data we want. The response data file name is <member>.<type> and in the directory structure it will be in unpackaged\<type>s\. So this is the file we need. Using the Zip classes in System.IO.Compression this is simple enough. Don’t forget to add a reference to this DLL.

//extract the metadata
ZipArchive za = new ZipArchive(ms);
ZipArchiveEntry zae;

zae = za.GetEntry("unpackaged/globalValueSets/TEST.globalValueSet");
StreamReader sr = new StreamReader(zae.Open());
Console.WriteLine(sr.ReadToEnd());
Console.ReadLine();

This outputs the response that I want – as shown at the top of this post. This really just scratches the surface of using the Salesforce Metadata API. But as with the Partner API should get you over the initial connection hurdles.