As a C# developer, to properly connect to Salesforce Data Cloud, you need to do 3 steps:
- Request an Access Token using the credentials provided for you.
- Exchange the Access Token for a Data Cloud Token
- 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:
- Salesforce Data Cloud Reference. Getting started from Salesforce
- Connect to the Data Cloud API from Salesforce
- C# HttpClient and IHttpClientFactory in .net core by briancaos
- C# POST x-www-form-urlencoded using HttpClient by briancaos
- How to retrieve data from SalesForce using C# from StackOverflow
- Integrating C# .NET and Salesforce’s REST API by Rachel Soderberg
- C# JWT Token Get Expiry Timestamp from briancaos