Quantcast
Channel: Brian Pedersen's Sitecore and .NET Blog
Viewing all articles
Browse latest Browse all 285

Connect to Salesforce Data Cloud Ingestion API using C# and HttpClient

$
0
0

As a C# developer, to properly connect to Salesforce Data Cloud, you need to do 3 steps:

  1. Request an Access Token using the credentials provided for you.
  2. Exchange the Access Token for a Data Cloud Token
  3. POST data to the Ingestion API using the Data Cloud Token

So in other words, you do authorization twice before you can POST your JSON to an Ingestion endpoint. So let’s get started.

BEFORE WE BEGIN: THE PREREQUISITES

As a responsible developer, we use the HttpClientFactory to create a HttpClient. So in the Program.cs, we have implemented an IHttpClientFactory instance:

services.AddHttpClient("httpClient");

STEP 1: REQUEST AN ACCESS TOKEN USING BASIC AUTHENTICATION

The AccessToken is in the format of an OAuth token, so we create a class for that:

using System.Text.Json.Serialization;

namespace AzureFunctionApp.Salesforce
{
  public class OAuthToken
  {
        [JsonPropertyName("access_token")]
        public string AccessToken { get; set; }

        [JsonPropertyName("signature")]
        public string Signature { get; set; }

        [JsonPropertyName("scope")]
        public string Scope { get; set; }

        [JsonPropertyName("instance_url")]
        public string InstanceUrl { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("token_type")]
        public string TokenType { get; set; }

        [JsonPropertyName("issued_at")]
        public string IssuedAt { get; set; }
    } 
}

Then we can create a function that gets an OAuthToken from Salesforce:

using System.Net.Http.Json;
using System.Text;

namespace MyCode
{
  public class SalesforceRepository
  {
    private IHttpClientFactory _httpClientFactory;

    public SalesforceAccessRepository(IHttpClientFactory httpClientFactory)
    {
      _httpClientFactory = httpClientFactory;
    }

    public async Task<OAuthToken?> GetOAuthTokenAsync()
    {
      string? username = "xxxxx";
      string? password = "xxxxx";
      string? oAuthUrl = "https://xxxxx.my.salesforce.com/services/oauth2/token"

      var client = _httpClientFactory.CreateClient("HttpClient");
      var authToken = Encoding.ASCII.GetBytes($"{username}:{password}");
      client.DefaultRequestHeaders.Authorization = 
	      new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
      var content = new MultipartFormDataContent { { new StringContent("client_credentials"), "grant_type" } };

      var response = await client.PostAsync(oAuthUrl, content);
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

      var responseJson = await response.Content.ReadFromJsonAsync<OAuthToken>();
      return responseJson;
    }
  }
}

STEP 2: EXCHANGE THE ACCESS TOKEN FOR A DATA CLOUD TOKEN

The Data Cloud token is in the format of a Json Web Token (JWT), so we need a class for that:

using System.Text.Json.Serialization;

namespace MyCode
{
  public class JsonWebToken
  {
    [JsonPropertyName("access_token")]
    public string AccessToken {  get; set; } 

    [JsonPropertyName("instance_url")]
    public string InstanceUrl {  get; set; } 

    [JsonPropertyName("token_type")]
    public string TokenType { get; set; }  

    [JsonPropertyName("issued_token_type")]
    public string IssuedTokenType { get; set; }  

    [JsonPropertyName("expires_in")]
    public int ExpiresIn { get; set; } 

  }
}

Then we can create another method in the SalesforceRepository from STEP1:

    public async Task<JsonWebToken?> GetJsonWebTokenAsync(string bearerToken)
    {
      string? url = "https://xxxxx.my.salesforce.com/services/a360/token";

      var client = _httpClientFactory.CreateClient("HttpClient");
      var data = new[]
      {
        new KeyValuePair<string, string>("grant_type", "urn:salesforce:grant-type:external:cdp"),
        new KeyValuePair<string, string>("subject_token", bearerToken),
        new KeyValuePair<string, string>("subject_token_type", "urn:ietf:params:oauth:token-type:access_token")
      };
      var content = new FormUrlEncodedContent(data);

      var response = await client.PostAsync(url, content);
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

      var responseJson = await response.Content.ReadFromJsonAsync<JsonWebToken>();
      return responseJson;
    }

STEP 3: POST DATA TO THE INGESTION API

With the 2 authorization function, we are now read to POST some data to an ingestion endpoint:

    public async Task<string> Ingest(string jsonData)
    {
      OAuthToken? oauthToken = await GetOAuthTokenAsync();
      JsonWebToken? jwt = await GetJsonWebTokenAsync(oauthToken.AccessToken);
      string? accessToken = jwt?.AccessToken;  

      var client = _httpClientFactory.CreateClient("HttpClient");
      client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

      using (var content = new StringContent(jsonData, Encoding.UTF8, "application/json"))
      {
        var response = await client.PostAsync("https://xxxxx.salesforce.com/api/v1/ingest/sources/xxxxx", content);
        if (!response.IsSuccessStatusCode)
          throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

        var responseJson = await response.Content.ReadAsStringAsync();
        return responseJson;
      }
    }

Please note that the Salesforce Ingestion API returns 202 Accepted, not 200 OK. 202 Accepted means that Salesforce have received the data and will look at them at some point. It does not mean that the JSON you posted is in the correct format nor that the data write is successful.

That’s it. You are now a Salesforce expert. Happy coding.

MORE TO READ:


Viewing all articles
Browse latest Browse all 285

Trending Articles