3

I'm new to Salesforce. I am trying to download a large file (around 13 MB) from Salesforce which needs to be uploaded to Azure Storage using C#. Below is the URL sample:

https://mysampleinstance.my.salesforce.com/sfc/servlet.shepherd/document/download/0693h00000AUzfpAAD

I've already generated an access token which I tried to call as shown below:

 // file.ContentFileLink is the URL above
 var req = (HttpWebRequest)WebRequest.CreateHttp(file.ContentFileLink);
 req.Headers.Add("Authorization: Bearer " + sfAccessToken);
 req.Method = "GET";

 WebResponse resp = req.GetResponse();

 using (MemoryStream ms = new MemoryStream())
 {
       resp.GetResponseStream().CopyTo(ms);
       blockBlob.UploadFromStream(ms);     // upload file to Azure
 }

Upon testing, there was no error thrown, however when I checked in Azure Storage, the file size was 0 bytes. Am I missing something with my call above?

Any advice you can give will be greatly appreciated. Thank you.

0

1 Answer 1

7

The root cause of your problem is when you try to fetch image data from /sfc/servlet.shepherd/document/download/{ContentDocumentId} resource, you receive the next response, and only after that a browser redirects you for image download:

<script>
    var url = window.location.pathname + window.location.hash;
    if(window.location.search && window.location.search.length > 1){
        url += window.location.search; // #189617
    }
    var loc = 'https://mysampleinstance.my.salesforce.com?ec=302&startURL='+encodeURIComponent(url);
    window.location = loc;
</script>

To download

  1. ContentDocument files you can access VersionData field from specific related ContentVersion record: /services/data/v53.0/sobjects/ContentVersion/{Id}/VersionData
  2. Document files you can access sObject Blob Retrieve rest resource: /services/data/v53.0/sobjects/Document/{Id}/body
  3. Attachment files you can access sObject Blob Retrieve rest resource: /services/data/v53.0/sobjects/Attachment/{Id}/body

C# Example (tested with an image of 50Mb)

Client

public static async Task Main(string[] args) {
    const string sfDomain = "mysampleinstance.my.salesforce.com";
    const string connectionString = "Connection String of your Azure Storage Account";
    var token = GetSfAccessToken(sfDomain);
    const string contentVersionId = "0681t00000kVrGeAAK";
    var fileData = DownloadVersionData(sfDomain, token, contentVersionId);
    /*await*/ UploadFile(connectionString, fileData, "filename.jpg");
}

Main Logic

struct SfTokenResponse {
    public string? access_token { get; set; }
    public string? instance_url { get; set; }
    public string? id { get; set; }
    public string? token_type { get; set; }
    public string? issued_at { get; set; }
    public string? signature { get; set; }
}

public static string GetSfAccessToken(string domain) {
    var getTokenUrl = $"https://{domain}/services/oauth2/token";
    const string clientId = "clientId";
    const string clientSecret = "clientSecret";
    const string username = "username";
    const string password = "password";

    var client = new RestClient();
    var request = new RestRequest(getTokenUrl, Method.POST);
    request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
    request.AddHeader("Accept", "application/json");
    request.AddParameter("grant_type", "password");
    request.AddParameter("client_id", clientId);
    request.AddParameter("client_secret", clientSecret);
    request.AddParameter("username", username);
    request.AddParameter("password", password);

    var response = client.Execute(request);
    var body = (SfTokenResponse) JsonConvert.DeserializeObject(response.Content, typeof(SfTokenResponse))!;
    return body.access_token ?? throw new InvalidOperationException();
}
public static byte[] DownloadVersionData(
        string domain,
        string sfAccessToken,
        string contentVersionId
) {
    var downloadUrl = $"https://{domain}/services/data/v53.0/sobjects/ContentVersion/{contentVersionId}/VersionData";
    var request = new RestRequest(downloadUrl, Method.GET);
    request.AddHeader("Authorization", $"Bearer {sfAccessToken}");
    var client = new RestClient();
    var data = client.DownloadData(request);
    return data;
}
public static async Task UploadFile(
        string connectionString,
        byte[] data,
        string fileName
) {
    // Create a BlobServiceClient object which will be used to create a container client
    var serviceClient = new BlobServiceClient(connectionString);
    // Create a unique name for the container (lowercase only!)
    var containerName = $"sffiles{Guid.NewGuid()}";
    // Create the container and return a container client object
    var containerClient = await serviceClient.CreateBlobContainerAsync(containerName);
    // Get a reference to a blob
    var blobClient = containerClient.Value.GetBlobClient(fileName);
    // Upload File into a blob container
    await blobClient.UploadAsync(new MemoryStream(data));
}

Documenation [Quickstart: Azure Blob Storage client library v12 for .NET]

2
  • This solved my issue perfectly. Thank you so much for the clear explanation and sample code, Oleh! I learned something new today as well :)
    – Dee J.
    Commented Nov 26, 2021 at 15:04
  • @DeeJ. Glad to know that. You're welcome.) Commented Nov 26, 2021 at 15:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .