Get base64 data of file from Salesforce
Asked Answered
P

3

2

I need to get opportunity files from Salesforce and copy them to some folder. I am using .NET library for connecting to Salesforcre. I can get any data I want, except the [VersionData] field in [ContentVersion] table, which contains base64 data of the files I want. I can get the data with Workbench tool, but the only thing I get via .NET library is a link to file. I could create HttpClient with appropriate headers and invoke that URL, but I don't like to go this ways. Can I get the file via .NET library?

Petrick answered 18/2, 2020 at 14:30 Comment(1)
Could you post screenshots or more info about what you get ?Bawdyhouse
P
0

Here is my solution (model class, endpoint method, authentication method):

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

And this is how I write recieved stream to file.

    // 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);
    }
Petrick answered 25/8, 2020 at 13:45 Comment(2)
How can I do that in nodejs. I am developing an application where I'm pulling the version data through Salesforce rest API endpoint '/services/data/v52.0/sobjects/ContentVersion/xxxxxxxxxxxxxx/VersionData' and it's giving me the response as a string. How can I convert it to buffer or stream and store it as a file in the system.Disannul
Try this: #41999606 or #12756497Petrick
O
2

In REST API it has to be pulled through that url you got. It'll be a raw binary stream of data which would be tricky to represent together within JSON of normal query results. REST API is focused on mobile apps, minimizing network usage and base64 decode is some processing power I guess.

It shouldn't be tricky though? Just a GET to the URL you got with header Authorization: Bearer <session id here>

Sample REST request with Postman

If you want base64 you need to make it a SOAP API request (which is what Workbench really uses, note that "REST explorer" is a separate menu option).

POST request to https://<redacted>.my.salesforce.com/services/Soap/u/48.0

with payload like

<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>

Will give you something like this back

<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>
Obryant answered 18/2, 2020 at 15:50 Comment(1)
Thank you, you saved my life! It was the Authentication that made me stop. It solved my problem.Petrick
Q
2

I was struggling with the same issue using the Force.com Toolkit for .NET and didn't want to switch to a workaround (for example doing a separate SOAP call or implement my own HttpClient). So I found out it is actually natively supported using the Toolkit. I didn't found it anywhere in the documentation (which is not extensive anyway), so I post it here, maybe it will help some other developers.

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();

So the trick is using the GetBlobAsync method on base64 fields in SalesForce. This will initiate a GET request on the desired endpoint: '/services/data/v49.0/sobjects/ContentVersion/<ContentVersion_ID>/VersionData'. And the response will be a 'Content-Type: application/octetstream' one captured into a Stream.

Quasar answered 24/8, 2020 at 10:51 Comment(0)
P
0

Here is my solution (model class, endpoint method, authentication method):

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

And this is how I write recieved stream to file.

    // 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);
    }
Petrick answered 25/8, 2020 at 13:45 Comment(2)
How can I do that in nodejs. I am developing an application where I'm pulling the version data through Salesforce rest API endpoint '/services/data/v52.0/sobjects/ContentVersion/xxxxxxxxxxxxxx/VersionData' and it's giving me the response as a string. How can I convert it to buffer or stream and store it as a file in the system.Disannul
Try this: #41999606 or #12756497Petrick

© 2022 - 2024 — McMap. All rights reserved.