从 Salesforce 获取文件的 base64 数据
Get base64 data of file from Salesforce
我需要从 Salesforce 获取机会文件并将它们复制到某个文件夹。我正在使用 .NET library 连接到 Salesforcre。我可以得到我想要的任何数据,除了 [ContentVersion]
table 中的 [VersionData]
字段,它包含我想要的文件的 base64
数据。我可以使用 Workbench 工具获取数据,但我通过 .NET 库唯一获取的是 link 文件。我可以使用适当的 headers 创建 HttpClient 并调用它 URL,但我不喜欢这样做。我可以通过 .NET 库获取文件吗?
在 REST API 中,它必须通过 url 你得到的。它将是原始二进制数据流,很难在正常查询结果的 JSON 中一起表示。 REST API 专注于移动应用程序,最小化网络使用和 base64 解码是我猜的一些处理能力。
不过应该不难吧?只需 GET 到 URL 你用 header Authorization: Bearer <session id here>
如果你想要 base64,你需要使它成为一个 SOAP API 请求(这是 Workbench 真正使用的,注意 "REST explorer" 是一个单独的菜单选项)。
POST 请求 https://<redacted>.my.salesforce.com/services/Soap/u/48.0
有效负载如
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>nice try ;) you can reuse same session id</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:query>
<urn:queryString>SELECT VersionData FROM ContentVersion WHERE Id = '068...'</urn:queryString>
</urn:query>
</soapenv:Body>
</soapenv:Envelope>
会给你这样的回报
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf="urn:sobject.partner.soap.sforce.com">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>12</current>
<limit>5000000</limit>
<type>API REQUESTS</type>
</limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<queryResponse>
<result xsi:type="QueryResult">
<done>true</done>
<queryLocator xsi:nil="true"/>
<records xsi:type="sf:sObject">
<sf:type>ContentVersion</sf:type>
<sf:Id xsi:nil="true"/>
<sf:VersionData>/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/
(bla bla bla)
/X/lf0eG9Kl61//Z</sf:VersionData>
</records>
<size>1</size>
</result>
</queryResponse>
</soapenv:Body>
</soapenv:Envelope>
我在使用 Force.com Toolkit for .NET 时遇到了同样的问题,不想切换到解决方法(例如做一个单独的 SOAP调用或实现我自己的 HttpClient)。
所以我发现它实际上是使用 Toolkit 原生支持的。我没有在文档中的任何地方找到它(无论如何都不广泛),所以我 post 在这里,也许它会帮助其他一些开发人员。
Console.WriteLine($"ContentDocumentLink ID: {contentDocumentLink.Id}");
Console.WriteLine($"ContentDocument ID: {contentDocumentLink.ContentDocumentId}");
// Get ContentDocument record
var contentDocument = await client.QueryByIdAsync<ContentDocument>("ContentDocument", contentDocumentLink.ContentDocumentId);
Console.WriteLine($"ContentVersion ID: {contentDocument.LatestPublishedVersionId}");
// Get ContentVersion record
var contentVersion = await client.QueryByIdAsync<ContentVersion>("ContentVersion", contentDocument.LatestPublishedVersionId);
// convert Bytes to KiloBytes presentation
string fullFileName = $"{contentVersion.Title}.{contentVersion.FileExtension}";
int FileSizeInBytes = contentVersion.ContentSize;
int FileSizeInKiloBytes = (int)ByteSize.FromBytes(FileSizeInBytes).LargestWholeNumberBinaryValue;
Console.WriteLine($"Filename: {fullFileName} ({FileSizeInKiloBytes} KB)");
// Get VersionData as a stream
var versionData = await client.GetBlobAsync("ContentVersion", contentDocument.LatestPublishedVersionId, "VersionData");
var fileStream = File.Create($"C:\Temp\{contentVersion.Title}.{contentVersion.FileExtension}");
versionData.CopyTo(fileStream);
fileStream.Close();
所以诀窍是在 SalesForce 的 base64 字段上使用 GetBlobAsync 方法。
这将在所需端点上发起 GET 请求:'/services/data/v49.0/sobjects/ContentVersion//VersionData'。
响应将是 'Content-Type: application/octetstream' 捕获到流中的响应。
这是我的解决方案(模型class、端点方法、身份验证方法):
public class ContentVersion
{
[JsonIgnoreSerialization]
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty("ContentDocumentId")]
public string ContentDocumentId { get; set; }
[JsonProperty("FileExtension")]
public string FileExtension { get; set; }
[JsonProperty("Title")]
public string Title { get; set; }
[JsonProperty("VersionNumber")]
public int VersionNumber { get; set; }
[JsonProperty("IsLatest")]
public bool IsLatest { get; set; }
[JsonProperty("VersionData")]
public string VersionDataURL { get; set; }
public Stream VersionDataStream { get; set; }
}
public async Threading.Task<ContentVersion> GetContentNewestVersion(string EntityId)
{
// Authenticate if not already
if (client == null) await Authenticate();
// Create query string
string query = @"SELECT
Id,
ContentDocumentId,
FileExtension,
Title,
VersionNumber,
IsLatest,
VersionData
FROM ContentVersion
WHERE ContentDocumentId = '" + EntityId + "'";
List<ContentVersion> results = new List<ContentVersion>();
QueryResult<ContentVersion> queryResult = await client.QueryAsync<ContentVersion>(query);
results.AddRange(queryResult.Records);
while (!queryResult.Done)
{
queryResult = await client.QueryContinuationAsync<ContentVersion>(queryResult.NextRecordsUrl);
results.AddRange(queryResult.Records);
}
// get only the newest Content version
ContentVersion latestContentVersion = results.Where(r => r.IsLatest).OrderByDescending(r => r.VersionNumber).FirstOrDefault();
// Get file stream via returned URL
using (HttpClient httpClient = new HttpClient())
{
// Add access token to request
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken);
// call server
var response = await httpClient.GetAsync(InstanceUrl + latestContentVersion.VersionDataURL);
// read stream and append it to object
latestContentVersion.VersionDataStream = await response.Content.ReadAsStreamAsync();
}
return latestContentVersion;
}
protected async Threading.Task Authenticate()
{
// Check if not already connected
if (client == null)
{
// Security settings
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Create Auth client
var auth = new AuthenticationClient();
// Authorize user
await auth.UsernamePasswordAsync(LoginDetails.ClientId, LoginDetails.ClientSecret, LoginDetails.Username, LoginDetails.Password, LoginDetails.TokenRequestEndpoint);
_instanceURL = auth.InstanceUrl;
AccessToken = auth.AccessToken;
// Create and return client with session variables
client = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
}
这就是我将接收到的流写入文件的方式。
// deisred folder
string PathToFolder = @"C:\destination\";
// get stream from Salesforce
ContentVersion documentContent = await forceAPI.GetContentNewestVersion(contentDocumentlink.ContentDocumentId);
// write file from stream
using (FileStream file = new FileStream(PathToFolder + documentContent.Title + "." + documentContent.FileExtension, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
documentContent.VersionDataStream.CopyTo(file);
}
我需要从 Salesforce 获取机会文件并将它们复制到某个文件夹。我正在使用 .NET library 连接到 Salesforcre。我可以得到我想要的任何数据,除了 [ContentVersion]
table 中的 [VersionData]
字段,它包含我想要的文件的 base64
数据。我可以使用 Workbench 工具获取数据,但我通过 .NET 库唯一获取的是 link 文件。我可以使用适当的 headers 创建 HttpClient 并调用它 URL,但我不喜欢这样做。我可以通过 .NET 库获取文件吗?
在 REST API 中,它必须通过 url 你得到的。它将是原始二进制数据流,很难在正常查询结果的 JSON 中一起表示。 REST API 专注于移动应用程序,最小化网络使用和 base64 解码是我猜的一些处理能力。
不过应该不难吧?只需 GET 到 URL 你用 header Authorization: Bearer <session id here>
如果你想要 base64,你需要使它成为一个 SOAP API 请求(这是 Workbench 真正使用的,注意 "REST explorer" 是一个单独的菜单选项)。
POST 请求 https://<redacted>.my.salesforce.com/services/Soap/u/48.0
有效负载如
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">
<soapenv:Header>
<urn:SessionHeader>
<urn:sessionId>nice try ;) you can reuse same session id</urn:sessionId>
</urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<urn:query>
<urn:queryString>SELECT VersionData FROM ContentVersion WHERE Id = '068...'</urn:queryString>
</urn:query>
</soapenv:Body>
</soapenv:Envelope>
会给你这样的回报
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf="urn:sobject.partner.soap.sforce.com">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>12</current>
<limit>5000000</limit>
<type>API REQUESTS</type>
</limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<queryResponse>
<result xsi:type="QueryResult">
<done>true</done>
<queryLocator xsi:nil="true"/>
<records xsi:type="sf:sObject">
<sf:type>ContentVersion</sf:type>
<sf:Id xsi:nil="true"/>
<sf:VersionData>/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/
(bla bla bla)
/X/lf0eG9Kl61//Z</sf:VersionData>
</records>
<size>1</size>
</result>
</queryResponse>
</soapenv:Body>
</soapenv:Envelope>
我在使用 Force.com Toolkit for .NET 时遇到了同样的问题,不想切换到解决方法(例如做一个单独的 SOAP调用或实现我自己的 HttpClient)。 所以我发现它实际上是使用 Toolkit 原生支持的。我没有在文档中的任何地方找到它(无论如何都不广泛),所以我 post 在这里,也许它会帮助其他一些开发人员。
Console.WriteLine($"ContentDocumentLink ID: {contentDocumentLink.Id}");
Console.WriteLine($"ContentDocument ID: {contentDocumentLink.ContentDocumentId}");
// Get ContentDocument record
var contentDocument = await client.QueryByIdAsync<ContentDocument>("ContentDocument", contentDocumentLink.ContentDocumentId);
Console.WriteLine($"ContentVersion ID: {contentDocument.LatestPublishedVersionId}");
// Get ContentVersion record
var contentVersion = await client.QueryByIdAsync<ContentVersion>("ContentVersion", contentDocument.LatestPublishedVersionId);
// convert Bytes to KiloBytes presentation
string fullFileName = $"{contentVersion.Title}.{contentVersion.FileExtension}";
int FileSizeInBytes = contentVersion.ContentSize;
int FileSizeInKiloBytes = (int)ByteSize.FromBytes(FileSizeInBytes).LargestWholeNumberBinaryValue;
Console.WriteLine($"Filename: {fullFileName} ({FileSizeInKiloBytes} KB)");
// Get VersionData as a stream
var versionData = await client.GetBlobAsync("ContentVersion", contentDocument.LatestPublishedVersionId, "VersionData");
var fileStream = File.Create($"C:\Temp\{contentVersion.Title}.{contentVersion.FileExtension}");
versionData.CopyTo(fileStream);
fileStream.Close();
所以诀窍是在 SalesForce 的 base64 字段上使用 GetBlobAsync 方法。
这将在所需端点上发起 GET 请求:'/services/data/v49.0/sobjects/ContentVersion/
这是我的解决方案(模型class、端点方法、身份验证方法):
public class ContentVersion
{
[JsonIgnoreSerialization]
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty("ContentDocumentId")]
public string ContentDocumentId { get; set; }
[JsonProperty("FileExtension")]
public string FileExtension { get; set; }
[JsonProperty("Title")]
public string Title { get; set; }
[JsonProperty("VersionNumber")]
public int VersionNumber { get; set; }
[JsonProperty("IsLatest")]
public bool IsLatest { get; set; }
[JsonProperty("VersionData")]
public string VersionDataURL { get; set; }
public Stream VersionDataStream { get; set; }
}
public async Threading.Task<ContentVersion> GetContentNewestVersion(string EntityId)
{
// Authenticate if not already
if (client == null) await Authenticate();
// Create query string
string query = @"SELECT
Id,
ContentDocumentId,
FileExtension,
Title,
VersionNumber,
IsLatest,
VersionData
FROM ContentVersion
WHERE ContentDocumentId = '" + EntityId + "'";
List<ContentVersion> results = new List<ContentVersion>();
QueryResult<ContentVersion> queryResult = await client.QueryAsync<ContentVersion>(query);
results.AddRange(queryResult.Records);
while (!queryResult.Done)
{
queryResult = await client.QueryContinuationAsync<ContentVersion>(queryResult.NextRecordsUrl);
results.AddRange(queryResult.Records);
}
// get only the newest Content version
ContentVersion latestContentVersion = results.Where(r => r.IsLatest).OrderByDescending(r => r.VersionNumber).FirstOrDefault();
// Get file stream via returned URL
using (HttpClient httpClient = new HttpClient())
{
// Add access token to request
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken);
// call server
var response = await httpClient.GetAsync(InstanceUrl + latestContentVersion.VersionDataURL);
// read stream and append it to object
latestContentVersion.VersionDataStream = await response.Content.ReadAsStreamAsync();
}
return latestContentVersion;
}
protected async Threading.Task Authenticate()
{
// Check if not already connected
if (client == null)
{
// Security settings
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Create Auth client
var auth = new AuthenticationClient();
// Authorize user
await auth.UsernamePasswordAsync(LoginDetails.ClientId, LoginDetails.ClientSecret, LoginDetails.Username, LoginDetails.Password, LoginDetails.TokenRequestEndpoint);
_instanceURL = auth.InstanceUrl;
AccessToken = auth.AccessToken;
// Create and return client with session variables
client = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
}
这就是我将接收到的流写入文件的方式。
// deisred folder
string PathToFolder = @"C:\destination\";
// get stream from Salesforce
ContentVersion documentContent = await forceAPI.GetContentNewestVersion(contentDocumentlink.ContentDocumentId);
// write file from stream
using (FileStream file = new FileStream(PathToFolder + documentContent.Title + "." + documentContent.FileExtension, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
documentContent.VersionDataStream.CopyTo(file);
}