I created the following class which I am posting to help others:
class SalesForceBulkApi
{
public static string UserName;
public static string Password;
public static SforceService SfdcBinding = null;
public static LoginResult CurrentLoginResult = null;
public static string operation = string.Empty;
public static string serverUrl = string.Empty;
static SalesForceBulkApi()
{
UserName = "YourUserName";
Password = "YourPassWord";
serverUrl = "https://naYOUR_SERVER_NUMBER.salesforce.com/services/async/28.0";
SfdcBinding = new SforceService();
try
{
CurrentLoginResult = SfdcBinding.login(UserName, Password);
}
catch (System.Web.Services.Protocols.SoapException e)
{
// This is likley to be caused by bad username or password
SfdcBinding = null;
throw (e);
}
catch (Exception e)
{
// This is something else, probably comminication
SfdcBinding = null;
throw (e);
}
//Change the binding to the new endpoint
SfdcBinding.Url = CurrentLoginResult.serverUrl;
//Create a new session header object and set the session id to that returned by the login
SfdcBinding.SessionHeaderValue = new SessionHeader();
SfdcBinding.SessionHeaderValue.sessionId = CurrentLoginResult.sessionId;
}
public static JobInfo CreateJob(string _operation, string objectType, string externalIdFieldName = null)
{
_operation = _operation.ToLower();
operation = _operation;
XNamespace dl = "http://www.force.com/2009/06/asyncapi/dataload";
XElement jobInfo;
if (externalIdFieldName == null)
{
jobInfo = new XElement(dl + "jobInfo",
new XElement(dl + "operation", operation),
new XElement(dl + "object", objectType),
new XElement(dl + "contentType", "CSV")
);
}
else
{
jobInfo = new XElement(dl + "jobInfo",
new XElement(dl + "operation", operation),
new XElement(dl + "object", objectType),
new XElement(dl + "externalIdFieldName", externalIdFieldName),
new XElement(dl + "contentType", "CSV")
);
}
Console.WriteLine(jobInfo.ToString());
string url = serverUrl + "/job";
string response = WebRequestPostData(url, jobInfo.ToString(), SfdcBinding.SessionHeaderValue.sessionId, "application/xml");
// parse response to get the job ID
var serializer = new XmlSerializer(typeof(JobInfo));
JobInfo result;
using (var stream = new StringReader(response))
using (var reader = XmlReader.Create(stream))
{
result = (JobInfo)serializer.Deserialize(reader);
}
return result;
}
public static JobInfo CloseJob(string jobId)
{
XNamespace dl = "http://www.force.com/2009/06/asyncapi/dataload";
XElement jobInfo = new XElement(dl + "jobInfo",
new XElement(dl + "state", "Closed")
);
Console.WriteLine(jobInfo.ToString());
string url = serverUrl + "/job/" + jobId;
Console.WriteLine(url);
string response = WebRequestPostData(url, jobInfo.ToString(), SfdcBinding.SessionHeaderValue.sessionId, "application/xml");
var serializer = new XmlSerializer(typeof(JobInfo));
JobInfo result;
using (var stream = new StringReader(response))
using (var reader = XmlReader.Create(stream))
{
result = (JobInfo)serializer.Deserialize(reader);
}
return result;
}
public static BatchInfoList GetBatches(string jobId)
{
string url = serverUrl + "/job/" + jobId + "/batch";
Console.WriteLine(url);
string response = WebRequestPostData(url, null, SfdcBinding.SessionHeaderValue.sessionId, "application/xml");
var serializer = new XmlSerializer(typeof(BatchInfoList));
BatchInfoList result;
using (var stream = new StringReader(response))
using (var reader = XmlReader.Create(stream))
{
result = (BatchInfoList)serializer.Deserialize(reader);
}
return result;
}
public static BatchInfo CreateBatch(IEnumerable<string> columns, IEnumerable<IEnumerable<string>> rows, string jobId)
{
// begin validation
if (rows.Count() >= 10000)
{
throw new ArgumentException("The SalesForce Bulk API does not permit more than 10,000 rows per batch", "rows");
}
if (rows.First().Count() != columns.Count())
{
throw new InvalidOperationException("The number of columns in 'rows' does not match the number of 'columns'.");
}
if (operation == "update")
{
if (string.Join(",", columns).Contains("Id") == false)
{
throw new ArgumentException("You have specified an update operation but have not provided an 'Id' column.", "columns");
}
}
// end validation
string batchTest2 = CreateCsvFromIEnumerable(columns, rows);
Console.Write(batchTest2);
string batchUrl = serverUrl + "/job/" + jobId + "/batch";
string batchResponse = WebRequestPostData(batchUrl, batchTest2, SfdcBinding.SessionHeaderValue.sessionId, "text/csv");
var serializer = new XmlSerializer(typeof(BatchInfo));
BatchInfo result;
using (var stream = new StringReader(batchResponse))
using (var reader = XmlReader.Create(stream))
{
result = (BatchInfo)serializer.Deserialize(reader);
}
return result;
}
private static string WebRequestPostData(string url, string postData, string sessionId, string contentType)
{
System.Net.WebRequest req = System.Net.WebRequest.Create(url);
req.Headers.Add("X-SFDC-Session: " + sessionId);
//req.Headers.Add("Content-Encoding: gzip");
//req.ContentType = "application/xml";
//req.ContentType = "text/csv";
req.ContentType = contentType + "; UTF-8";
req.Method = "GET";
if (postData != null)
{
req.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(postData);
req.ContentLength = bytes.Length;
using (Stream os = req.GetRequestStream())
{
os.Write(bytes, 0, bytes.Length);
}
}
using (System.Net.WebResponse resp = req.GetResponse())
{
if (resp == null) return null;
using (System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream()))
{
return sr.ReadToEnd().Trim();
}
}
}
private static string CreateCsvFromIEnumerable(IEnumerable<string> columns, IEnumerable<IEnumerable<string>> rows)
{
string columnNames = "";
int i = 0;
foreach (string column in columns)
{
columnNames += column + ",";
i++;
if (i >= columns.Count())
columnNames = columnNames.TrimEnd(',');
}
columnNames += "\r\n";
string data = "";
i = 0;
foreach (IEnumerable<string> row in rows)
{
foreach (string _row in row)
{
data += "\"" + _row.Replace("\"", "\"\"") + "\",";
i++;
if (i >= row.Count())
data = data.TrimEnd(',');
}
i = 0;
data += "\r\n";
}
return columnNames + data;
}
}
[XmlRoot("batchInfoList", Namespace = "http://www.force.com/2009/06/asyncapi/dataload")]
public class BatchInfoList
{
[XmlElement("batchInfo")]
public List<BatchInfo> BatchInfo { get; set; }
}
[XmlRoot("jobInfo", Namespace = "http://www.force.com/2009/06/asyncapi/dataload")]
public class JobInfo
{
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("operation")]
public string Operation { get; set; }
[XmlElement("object")]
public string Object { get; set; }
[XmlElement("createdById")]
public string CreatedById { get; set; }
[XmlElement("createdDate")]
public DateTime CreatedDate { get; set; }
[XmlElement("systemModstamp")]
public DateTime SystemModstamp { get; set; }
[XmlElement("state")]
public string State { get; set; }
[XmlElement("concurrencyMode")]
public string ConcurrencyMode { get; set; }
[XmlElement("contentType")]
public string ContentType { get; set; }
[XmlElement("numberBatchesQueued")]
public string NumberBatchesQueued { get; set; }
[XmlElement("numberBatchesInProgress")]
public string NumberBatchesInProgress { get; set; }
[XmlElement("numberBatchesCompleted")]
public string NumberBatchesCompleted { get; set; }
[XmlElement("numberBatchesFailed")]
public string NumberBatchesFailed { get; set; }
[XmlElement("numberBatchesTotal")]
public string NumberBatchesTotal { get; set; }
[XmlElement("numberRecordsProcessed")]
public string numberRecordsProcessed { get; set; }
[XmlElement("numberRetries")]
public string NumberRetries { get; set; }
[XmlElement("apiVersion")]
public string ApiVersion { get; set; }
}
[XmlRoot("batchInfo", Namespace = "http://www.force.com/2009/06/asyncapi/dataload")]
public class BatchInfo
{
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("jobId")]
public string JobId { get; set; }
[XmlElement("state")]
public string State { get; set; }
[XmlElement("createdDate")]
public string CreatedDate { get; set; }
[XmlElement("systemModstamp")]
public string SystemModStamp { get; set; }
[XmlElement("numberRecordsProcessed")]
public string NumberRecordsProcessed { get; set; }
[XmlElement("numberRecordsFailed")]
public string NumberRecordsFailed { get; set; }
[XmlElement("totalProcessingTime")]
public string TotalProcessingTime { get; set; }
[XmlElement("apiActiveProcessingTime")]
public string ApiActiveProcessingTime { get; set; }
[XmlElement("apexProcessingTime")]
public string ApexProcessingTime { get; set; }
}
To use:
Modify these lines accordingly:
UserName = "YourUserName";
Password = "YourPassWord";
serverUrl = "https://naYOUR_SERVER_NUMBER.salesforce.com/services/async/28.0";
And, use like so:
IEnumerable<string> columns = new List<string>()
{
//"Id",
"FirstName",
"LastName",
"Department",
"Birthdate",
"Description",
"CustomId__c"
};
IEnumerable<IEnumerable<string>> rows = new List<List<string>>()
{
new List<string>()
{
//"",
"Tom",
"Jones",
"Marketing",
"1940-06-07Z",
"Self-described as \"the top\" branding guru on the West Coast",
"1000"
},
new List<string>()
{
//"",
"Ian",
"Dury",
"R&D",
"",
"World-renowned expert in fuzzy logic design. Influential in technology purchases.",
"1001"
}
};
JobInfo job = SalesForceBulkApi.CreateJob(_operation: "upsert", objectType: "Contact", externalIdFieldName: "CustomId__c");
BatchInfo batch = SalesForceBulkApi.CreateBatch(columns: columns, rows: rows, jobId: job.Id);
//JobInfo response = SalesForceBulkApi.CloseJob("750i0000000QOSq");
//BatchInfoList batches = SalesForceBulkApi.GetBatches("750i0000000Q8vD");
Console.ReadLine();