Your workflow is unique 👨‍💻 -  tell us how you use Dropbox here.

Forum Discussion

Omri1984's avatar
Omri1984
Explorer | Level 4
1 month ago

Using API and SDK together

Hello,

I need some help

until now i have been using >NET SDK Version 6.37.

and we are experiencing a problem downloading large files 30GB and higher.

so I tried to implement chunk download, but SDK does not support it .

so I did it using the API.

but my problem is that when we use the SDK we do not Need to refresh token.

and when we use api after some time I am getting 401 .

 

so I implemented a refresh token method, and refreshed the token , but still after sending the request again with the new token I got I am still getting the 401.

 

I tried to get the token from the Dropbox Client but it is not there

also regarding the refresh token , is this token always stays the same ?

can some one help?

adding some code snipped.

 

public async Task<string> RefreshAccessTokenAsync(string expiredAccessToken,BaseRequest baserRequest,bool isBusinessClient, CancellationToken token)

{

using (var http = new HttpClient())

{

var request = new HttpRequestMessage(HttpMethod.Post, "https://api.dropboxapi.com/oauth2/token");

 

var content = new FormUrlEncodedContent(new Dictionary<string, string>

{

{ "grant_type", "refresh_token" },

{ "refresh_token", baserRequest.RefreshToken },

{ "client_id", isBusinessClient ?ClientIdBusiness : ClientId },

{ "client_secret", isBusinessClient ? ClientSecretBusiness : ClientSecret }

});

 

request.Content = content;

 

using (var response = await http.SendAsync(request, token))

{

response.EnsureSuccessStatusCode();

 

var json = await response.Content.ReadAsStringAsync();

dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(json);

 

string newAccessToken = result.access_token;

if (string.IsNullOrEmpty(newAccessToken))

throw new Exception("Dropbox token refresh failed: access_token not returned.");

 

return newAccessToken;

}

}

}

 

private async Task DownloadFileInChunksHttpAsync(

string accessToken,

string dropboxPath,

string localPath,

long fileSize,

string asMember,

string rootNamespaceId,

CancellationToken token,

BaseRequest baseRequest,

bool isBusinessClient) // <-- tells refresh which client to use

{

DropboxTeamClient client;

const int bufferSize = 1024 * 1024; // 1MB

const long chunkSize = 50L * 1024 * 1024; // 50MB

 

using (var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None))

using (var http = new HttpClient())

{

string currentAccessToken = accessToken;

long offset = 0;

 

while (offset < fileSize)

{

long end = Math.Min(offset + chunkSize - 1, fileSize - 1);

bool retriedAfterRefresh = false;

 

RetryChunk:

 

var request = new HttpRequestMessage(HttpMethod.Post, "https://content.dropboxapi.com/2/files/download");

 

// Authorization

request.Headers.Authorization =

new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", currentAccessToken);

 

// Dropbox-API-Arg (JSON-encoded)

var arg = new { path = dropboxPath };

string jsonArg = Newtonsoft.Json.JsonConvert.SerializeObject(arg);

request.Headers.Add("Dropbox-API-Arg", jsonArg);

 

// Range header for chunking

request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, end);

 

// Act as team member (Business)

if (!string.IsNullOrEmpty(asMember))

request.Headers.Add("Dropbox-API-Select-User", asMember);

 

// Apply PathRoot (WithPathRoot) for Business only

if (!string.IsNullOrEmpty(rootNamespaceId))

{

var pathRoot = new Dictionary<string, object>

{

{ ".tag", "root" },

{ "root", rootNamespaceId }

};

 

string jsonPathRoot = Newtonsoft.Json.JsonConvert.SerializeObject(pathRoot);

request.Headers.Add("Dropbox-API-Path-Root", jsonPathRoot);

}

 

using (var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token))

{

// 🔐 Handle expired token (401)

if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)

{

 

if (retriedAfterRefresh)

{

var err = await response.Content.ReadAsStringAsync();

throw new HttpRequestException(

$"Dropbox download failed after token refresh (401). Body: {err}");

}

 

// Refresh token based on client type (Personal vs Business)

currentAccessToken = await RefreshAccessTokenAsync(currentAccessToken, baseRequest, isBusinessClient, token).ConfigureAwait(false);

 

retriedAfterRefresh = true;

goto RetryChunk;

}

 

// ❌ Other errors

if (!response.IsSuccessStatusCode)

{

var err = await response.Content.ReadAsStringAsync();

throw new HttpRequestException(

$"Dropbox download failed: {(int)response.StatusCode} {response.ReasonPhrase}. Body: {err}");

}

 

// ✅ Success

using (var stream = await response.Content.ReadAsStreamAsync())

{

fileStream.Seek(offset, SeekOrigin.Begin);

await stream.CopyToAsync(fileStream, bufferSize, token);

}

}

 

offset = end + 1;

}

}

}

 

 

 

 

public async Task<DownloadFileResponse> DownloadFileAsync(DownloadFileRequest request)

{

DropboxTeamClient client;

var connectionId = GetDropboxTeamClient(request, out client);

////logsfodebug(request, "DownloadFileAsync");

 

var account = await client.AsMember(request.AsMember).Users.GetCurrentAccountAsync();

var spaceclient = client.AsMember(request.AsMember).WithPathRoot(new PathRoot.Root(account.RootInfo.RootNamespaceId));

bool newTeam = CheckIfUserIsUpdatedTeam(account);

 

string tempFilename;

 

if (request.ExportAs != null)

{

// ===== EXPORT: REVERTED TO OLD SDK CODE (AS REQUESTED) =====

tempFilename = await Retries.PerformWithRetriesAsync(

async () =>

{

var path = request.FilePath.Replace('\\', '/');

 

var fileInfo = await spaceclient.Files.GetMetadataAsync(path)

.ConfigureAwait(false);

 

if (fileInfo.IsDeleted)

throw new CannotDownloadFileException(string.Format("File was Deleted. - {0}", path));

 

long? size = request.Size ?? (long)fileInfo.AsFile.Size;

 

using (var safeTemporaryFileProvider =

new SafeTemporaryFileProvider(() => PathExtensions.GetTempFileName(request.ContextId)))

{

using (var tokenSource = new CancellationTokenSource(TimeoutHelper.FromSize(size)))

{

// OLD SDK EXPORT CODE (unchanged)

using (var downloadResponse = await spaceclient.Files

.ExportAsync(new ExportArg(request.FilePath, null))

.WithCancellation(tokenSource.Token).ConfigureAwait(false))

{

using (var fileStream = new FileStream(safeTemporaryFileProvider.TempFilename,

FileMode.Create))

{

var downloadStream =

await downloadResponse.GetContentAsStreamAsync()

.WithCancellation(tokenSource.Token)

.ConfigureAwait(false);

 

await downloadStream.CopyToAsync(fileStream, 4096, tokenSource.Token)

.WithCancellation(tokenSource.Token)

.ConfigureAwait(false);

 

return safeTemporaryFileProvider.TempFilename;

}

}

}

}

},

ShouldRetryWithDelay).ConfigureAwait(false);

}

else

{

if (newTeam)

{

// ===== BUSINESS / TEAM: NEW HTTP CHUNKED DOWNLOAD =====

tempFilename = await Retries.PerformWithRetriesAsync(

async () =>

{

var path = request.FilePath.Replace('\\', '/');

 

var fileInfo = await spaceclient.Files.GetMetadataAsync(path).ConfigureAwait(false);

if (fileInfo.IsDeleted)

throw new CannotDownloadFileException(string.Format("File was Deleted. - {0}", path));

 

long? size = request.Size ?? (long)fileInfo.AsFile.Size;

 

using (var safeTemporaryFileProvider = new SafeTemporaryFileProvider(() => PathExtensions.GetTempFileName(request.ContextId)))

{

using (var tokenSource = new CancellationTokenSource(TimeoutHelper.FromSize(size)))

{

await DownloadFileInChunksHttpAsync(

request.AccessToken,

path,

safeTemporaryFileProvider.TempFilename,

size ?? 0,

request.AsMember,

account.RootInfo.RootNamespaceId,

tokenSource.Token,

request,

true); // <-- Business client

 

return safeTemporaryFileProvider.TempFilename;

}

}

},

ShouldRetryWithDelay).ConfigureAwait(false);

}

else

{

// ===== PERSONAL: NEW HTTP CHUNKED DOWNLOAD =====

tempFilename = await Retries.PerformWithRetriesAsync(

async () =>

{

var path = request.FilePath.Replace('\\', '/');

 

var fileInfo = await client.AsMember(request.AsMember).Files.GetMetadataAsync(path).ConfigureAwait(false);

if (fileInfo.IsDeleted)

throw new CannotDownloadFileException(string.Format("File was Deleted. - {0}", path));

 

long? size = request.Size ?? (long)fileInfo.AsFile.Size;

 

using (var safeTemporaryFileProvider = new SafeTemporaryFileProvider(() => PathExtensions.GetTempFileName(request.ContextId)))

{

using (var tokenSource = new CancellationTokenSource(TimeoutHelper.FromSize(size)))

{

await DownloadFileInChunksHttpAsync(

request.AccessToken,

path,

safeTemporaryFileProvider.TempFilename,

size ?? 0,

request.AsMember,

null,

tokenSource.Token,

request,

false); // <-- Personal client

 

return safeTemporaryFileProvider.TempFilename;

}

}

},

ShouldRetryWithDelay).ConfigureAwait(false);

}

}

 

var response = new DownloadFileResponse

{

LocalPath = tempFilename

};

 

return UpdateTokens(response, request, client, connectionId);

}

 

 

7 Replies

  • DB-Des's avatar
    DB-Des
    Icon for Dropbox Community Moderator rankDropbox Community Moderator
    1 month ago

    Omri1984​ 

    Thanks for clarifying your request.

    I wanted to follow-up on your first enquiry. The .NET SDK does not currently support range retrieval requests, which relates directly to the issue you described with downloading large files (30GB and higher). That said, we've gone ahead and submitted a feature request for the .NET SDK to support Range Retrieval Requests.

    We also confirmed that your second request — the ability to retrieve the current access token after it's been refreshed by the SDK — isn't currently supported by the Dropbox .NET SDK either. However, we've submitted a separate feature request for that functionality as well.

    While we've passed both requests along to the appropriate teams, we can't make any guarantees on if or when either feature might be implemented.

  • Omri1984's avatar
    Omri1984
    Explorer | Level 4
    1 month ago

    but lets say a token is getting refreshed from the sdk , and now i need to get it for the download , how can i extract it from the dropbox  client sdk ?

  • Omri1984's avatar
    Omri1984
    Explorer | Level 4
    1 month ago

    I am fully know how to get the first access token. And my application is running for many years. 

    And recently we wanted to update the download method  with chunks that are not supported  in the sdk and  used the api.  when trying to refresh token all is good and we get a new access token , but when trying to retry the download request with the new access token we can 401. With empty response. No text inside

  • DB-Des's avatar
    DB-Des
    Icon for Dropbox Community Moderator rankDropbox Community Moderator
    1 month ago

    Omri1984​ 

    Yes, that’s correct: refresh tokens do not expire automatically (unless they’re revoked, for example if the user disconnects the app).

    Also keep in mind that access tokens do expire after a period of time, even if you obtain them via the SDK. When the access token expires, your app will need to use the refresh token to request a new access token, or the user will need to go through the authorization flow again.

    That said, when using the Dropbox NET SDK, you can retrieve the access token as such:

    ...
    var tokenResult = await DropboxOAuth2Helper.ProcessCodeFlowAsync(
    					code: code,
    					appKey: AppKey,
    					appSecret: AppSecret,
    					redirectUri: RedirectUri
    				);
    				
    Console.WriteLine($"Access token: {tokenResult.AccessToken}");
    ...
  • Omri1984's avatar
    Omri1984
    Explorer | Level 4
    1 month ago

    Hi,

    Thank you for the response. 

    The response of the 401 is empty. 

     

    And I will try to move to version 7 .

     

    So from what you are saying refreshments are stays the same always. Unless you revoked them . 

    Is there an option to extract the access token from the Dropbox client. Sence  I am using both sdk and api . Due to the linitation of download in chunks not available  in the sdk .

     

     

  • DB-Des's avatar
    DB-Des
    Icon for Dropbox Community Moderator rankDropbox Community Moderator
    1 month ago

    Hi Omri1984​ 

    Refresh tokens don't automatically expire. In general, a refresh token will remain valid unless it’s revoked (for example, if the user disconnects the app, the token is explicitly revoked, or the app's access is otherwise removed).

    For the 401 you mentioned, could you share the full error message/response body you're seeing (not just the status code)?

    Also, if you haven't already, please make sure you're using the latest version of the SDK, since updates can include important fixes and improvements related to auth handling.

About Dropbox API Support and Feedback

Node avatar for Dropbox API Support and Feedback
Get help with the Dropbox API from fellow developers and experts.

The Dropbox Community team is active from Monday to Friday. We try to respond to you as soon as we can, usually within 2 hours.

If you need more help you can view your support options (expected response time for an email or ticket is 24 hours), or contact us on X, Facebook or Instagram.

For more info on available support options for your Dropbox plan, see this article.

If you found the answer to your question in this Community thread, please 'like' the post to say thanks and to let us know it was useful!