Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upload file (> 4MB) to OneDrive using Graph API

I am trying to upload files to OneDrive using Graph API. The below code works fine when I upload files with size lesser than 4MB but it shows an error when I try to upload files more than 4 MB. I went through this documentation but still, I am not sure how can I get this work.

Below is my working code for files less than 4MB.

using (var client = new HttpClient())
{
    var url = "https://graph.microsoft.com/v1.0" + $"/drives/{driveID}/items/{folderId}:/{originalFileName}:/content";
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + GetAccessToken());

    byte[] sContents = System.IO.File.ReadAllBytes(filePath);
    var content = new ByteArrayContent(sContents);

    var response = client.PutAsync(url, content).Result.Content.ReadAsStringAsync().Result;
}

Please help

like image 782
Mohamed Thaufeeq Avatar asked May 28 '19 05:05

Mohamed Thaufeeq


People also ask

How large of a file can I upload to OneDrive?

You can add files and folders to OneDrive automatically from your PC without having to go to the OneDrive website. Files you add to OneDrive this way can be up to 250GB in size. (If you sign in with a work or school account, the maximum file size is 15GB.)


2 Answers

We need to split the byte array by length (i.e. 4MB) and pass them to the OneDrive API. The working version is below:

using (var client = new HttpClient())
{
    var url = "https://graph.microsoft.com/v1.0" + $"/drives/{driveID}/items/{folderId}:/{originalFileName}:/createUploadSession";
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + GetAccessToken());

    var sessionResponse = client.PostAsync(apiUrl, null).Result.Content.ReadAsStringAsync().Result;

    byte[] sContents = System.IO.File.ReadAllBytes(filePath);
    var uploadSession = JsonConvert.DeserializeObject<UploadSessionResponse>(sessionResponse);
    string response = UploadFileBySession(uploadSession.uploadUrl, sContents);
}

UploadFileBySession is as follows:

private string UploadFileBySession(string url, byte[] file)
{
    int fragSize = 1024 * 1024 * 4;
    var arrayBatches = ByteArrayIntoBatches(file, fragSize);
    int start = 0;
    string response = "";

    foreach (var byteArray in arrayBatches)
    {
        int byteArrayLength = byteArray.Length;
        var contentRange = " bytes " + start + "-" + (start + (byteArrayLength - 1)) + "/" + file.Length;

        using (var client = new HttpClient())
        {
            var content = new ByteArrayContent(byteArray);
            content.Headers.Add("Content-Length", byteArrayLength.ToProperString());
            content.Headers.Add("Content-Range", contentRange);

            response = client.PutAsync(url, content).Result.Content.ReadAsStringAsync().Result;
        }

        start = start + byteArrayLength;
    }
    return response;
}

internal IEnumerable<byte[]> ByteArrayIntoBatches(byte[] bArray, int intBufforLengt)
{
    int bArrayLenght = bArray.Length;
    byte[] bReturn = null;

    int i = 0;
    for (; bArrayLenght > (i + 1) * intBufforLengt; i++)
    {
        bReturn = new byte[intBufforLengt];
        Array.Copy(bArray, i * intBufforLengt, bReturn, 0, intBufforLengt);
        yield return bReturn;
    }

    int intBufforLeft = bArrayLenght - i * intBufforLengt;
    if (intBufforLeft > 0)
    {
        bReturn = new byte[intBufforLeft];
        Array.Copy(bArray, i * intBufforLengt, bReturn, 0, intBufforLeft);
        yield return bReturn;
    }
}

UploadSessionResponse class file to deserialize JSON response when you create upload session

public class UploadSessionResponse
{
    public string odatacontext { get; set; }
    public DateTime expirationDateTime { get; set; }
    public string[] nextExpectedRanges { get; set; }
    public string uploadUrl { get; set; }
}
like image 122
Mohamed Thaufeeq Avatar answered Oct 05 '22 23:10

Mohamed Thaufeeq


For files bigger then 4MB you need to create an uploadSession that you POST to this URL:

https://graph.microsoft.com/v1.0/me/drive/root:/{item-path}:/createUploadSession

Pass an array of items,

{
  "item": {
    "@odata.type": "microsoft.graph.driveItemUploadableProperties",
    "@microsoft.graph.conflictBehavior": "rename",
    "name": "largefile.dat"
  }
}

I use "@microsoft.graph.conflictBehavior": "overwrite", instead of rename.

The response will provide an upload url to upload the file in batches

{
  "uploadUrl": "https://sn3302.up.1drv.com/up/fe6987415ace7X4e1eF866337",
  "expirationDateTime": "2015-01-29T09:21:55.523Z"
}

I don't have a c# example but here's a PHP one:

$url = $result['uploadUrl'];
$fragSize = 1024*1024*4;
$file = file_get_contents($source);
$fileSize = strlen($file);
$numFragments = ceil($fileSize / $fragSize);
$bytesRemaining = $fileSize;
$i = 0;
$response = null;

while ($i < $numFragments) {
    $chunkSize = $numBytes = $fragSize;
    $start = $i * $fragSize;
    $end = $i * $fragSize + $chunkSize - 1;
    $offset = $i * $fragSize;

    if ($bytesRemaining < $chunkSize) {
        $chunkSize = $numBytes = $bytesRemaining;
        $end = $fileSize - 1;
    }

    if ($stream = fopen($source, 'r')) {
        // get contents using offset
        $data = stream_get_contents($stream, $chunkSize, $offset);
        fclose($stream);
    }

    $contentRange = " bytes " . $start . "-" . $end . "/" . $fileSize;
    $headers = array(
        "Content-Length: $numBytes",
        "Content-Range: $contentRange"
    );

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $server_output = curl_exec($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);

    $bytesRemaining = $bytesRemaining - $chunkSize;
    $i++;
}
like image 42
Dave Avatar answered Oct 05 '22 23:10

Dave