从 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);
    }