Calling the Force.com REST API from BizTalk Server - Multiple Endpoints
Introduction
This article is an extension of the article Calling the Force.com REST API from BizTalk Server posted by one of our BizTalk Gurus, Richard Seroter. A small change has been made to the shared code to accommodate a Force.com multi-endpoints scenario.
Problem
When we have to call multiple Endpoints of SalesForce REST API from BizTalk Server, we mostly get API error: INVALID_SESSION_ID.
Root Cause
As explained in the above article, we use five credential values to authenticate any REST API Endpoint Address:
- Consumer Key
- Consumer Secret
- Password
- Security Token
- Username
Imagine a scenario in which we have two different Endpoint Addresses with different credentials, (suppose: https://ns17.salesforce.com/services/data/v25.0/ and https://ns18.salesforce.com/services/data/v25.0/) and we want to integrate with both in two separate BizTalk Applications (suppose: App1 and App2). We have setup the WCF-WebHTTP ports in both the applications accordingly.
That will result in the following:
- App1 calls the REST API.
Result: A new token will be generated ("New SFDC token acquired"). Success. - App1 calls the REST API again within 60 Minutes of the last call.
Result: Existing token will be used ("Existing SFDC token returned"). Success. - App2 calls the REST API within 60 Minutes of the last call by App1.
Result: Existing token will be used ("Existing SFDC token returned") Error: INVALID_SESSION_ID
The Error is of course a bit of a problem. That happens because App2 has used the token App1 supplied, which is invalid for the App2 Endpoint. A similar problem will happen with App1 calls when it tries to use App2 token.
The problem is in the below portion of the code where we are not getting a new token when the elapsed time since the token was accessed is less than or equal to 60 minutes:
public static class SfdcTokenManager
{
private static DateTime _sessionLastAccessDate = DateTime.Now;
private static string _sessionId = string.Empty;
public static string GetSession(string oauthkey, string oauthsecret, string uname, string password, string token)
{
//get current date time
DateTime now = DateTime.Now;
//get the difference from the last time the token was accessed
TimeSpan diff = now.Subtract(_sessionLastAccessDate);
//if this is the first time we're calling class, or the token is stale, get a new token
if (_sessionId == string.Empty || (diff.TotalMinutes >= 60))
{
//refresh token
try
{
HttpClient authClient = new HttpClient();
//create login password value
string loginPassword = password + token;
//create payload consisting of required values
HttpContent content = new FormUrlEncodedContent(new Dictionary<string,string>
{
{"grant_type","password"},
{"client_id",oauthkey},
{"client_secret",oauthsecret},
{"username",uname},
{"password",loginPassword}
}
);
//issue request to the Force.com login endpoint
//var response = authClient.PostAsync("https://login.salesforce.com/services/oauth2/token", content);
var response = authClient.PostAsync("https://test.salesforce.com/services/oauth2/token", content);
if (response.Result.IsSuccessStatusCode)
{
//get the result
var responseContent = response.Result.Content;
string responseString = responseContent.ReadAsStringAsync().Result;
//load result into JSON object
JObject obj = JObject.Parse(responseString);
//extract the access token
_sessionId = (string)obj["access_token"];
System.Diagnostics.EventLog.WriteEntry("Application", "New SFDC token acquired");
}
}
catch (Exception ex)
{
System.Diagnostics.EventLog.WriteEntry("Application", "Error getting token: " + ex.ToString());
}
}
else
{
System.Diagnostics.EventLog.WriteEntry("Application", "Existing SFDC token returned");
}
//update last access time since session is good for an hour since last call
_sessionLastAccessDate = DateTime.Now;
//give the session ID to the caller
return _sessionId;
}
Solution
There is a simple solution to this problem. We just have to add another condition to check if any credential value is different than the previous one (i.e; Username is a unique value).
We can do this using the following code:
public static class SfdcTokenManager
{
private static DateTime _sessionLastAccessDate = DateTime.Now;
private static string _sessionId = string.Empty;
private static string _uname = string.Empty;
public static string GetSession(string oauthkey, string oauthsecret, string uname, string password, string token)
{
//get current date time
DateTime now = DateTime.Now;
//get the difference from the last time the token was accessed
TimeSpan diff = now.Subtract(_sessionLastAccessDate);
//if this is the first time we're calling class, or the token is stale, get a new token
if (_sessionId == string.Empty || (diff.TotalMinutes >= 60) || uname != _uname)
{
//refresh token
try
{
HttpClient authClient = new HttpClient();
//create login password value
string loginPassword = password + token;
//create payload consisting of required values
HttpContent content = new FormUrlEncodedContent(new Dictionary<string,string>
{
{"grant_type","password"},
{"client_id",oauthkey},
{"client_secret",oauthsecret},
{"username",uname},
{"password",loginPassword}
}
);
//issue request to the Force.com login endpoint
//var response = authClient.PostAsync("https://login.salesforce.com/services/oauth2/token", content);
var response = authClient.PostAsync("https://test.salesforce.com/services/oauth2/token", content);
if (response.Result.IsSuccessStatusCode)
{
//get the result
var responseContent = response.Result.Content;
string responseString = responseContent.ReadAsStringAsync().Result;
//load result into JSON object
JObject obj = JObject.Parse(responseString);
//extract the access token
_sessionId = (string)obj["access_token"];
System.Diagnostics.EventLog.WriteEntry("Application", "New SFDC token acquired");
}
}
catch (Exception ex)
{
System.Diagnostics.EventLog.WriteEntry("Application", "Error getting token: " + ex.ToString());
}
}
else
{
System.Diagnostics.EventLog.WriteEntry("Application", "Existing SFDC token returned");
}
//update last access time since session is good for an hour since last call
_sessionLastAccessDate = DateTime.Now;
_uname = uname;
//give the session ID to the caller
return _sessionId;
}
Source Code
Source Code for this article can be downloaded at the MSDN Code Gallery: Calling the Force.com REST API from BizTalk Server - Multiple Endpoints.
See Also
Another important place to find an extensive amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.