Search code examples
c#asp.net-coresharepointmicrosoft-graph-apionedrive

Upload a file in C# Asp.Net Core to Sharepoint/OneDrive using Microsoft Graph without user interaction


I get an error 400 Bad Request when I try to upload a file to my OneDrive with a daemon app, using the Microsoft Graph API. I use a HttpClient, not a GraphServiceClient as the latter assumes interaction and works with a DelegatedAuthenticationProvider(?).

  • The App is registered in AAD and has the right Application Permission (Microsoft Graph / File ReadWrite.All)
  • The registration is for One Tenant and has no redirect url (as per instructions)

The main Method Upload gets an AccessToken through a Helper AuthenticationConfig and puts a file to OneDrive/SharePoint with the Helper ProtectedApiCallHelper.

[HttpPost]
    public async Task<IActionResult> Upload(IFormFile file)
    {            
        var toegang = new AuthenticationConfig();
        var token = toegang.GetAccessTokenAsync().GetAwaiter().GetResult();

        var httpClient = new HttpClient();
        string bestandsnaam = file.FileName;
        var serviceEndPoint = "https://graph.microsoft.com/v1.0/drive/items/{Id_Of_Specific_Folder}/";            

        var wurl = serviceEndPoint + bestandsnaam + "/content";
// The variable wurl looks as follows: "https://graph.microsoft.com/v1.0/drive/items/{Id_Of_Specific_Folder}/proefdocument.txt/content"
        var apicaller = new ProtectedApiCallHelper(httpClient);
        apicaller.PostWebApi(wurl, token.AccessToken, file).GetAwaiter();

        return View();
    }

I get a proper Access Token using the following standard helper AuthenticationConfig.GetAccessToken()

public async Task<AuthenticationResult> GetAccessTokenAsync()
    {
        AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");            
        IConfidentialClientApplication app;

        app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
            .WithClientSecret(config.ClientSecret)
            .WithAuthority(new Uri(config.Authority))
            .Build();

        string[] scopes = new string[] { "https://graph.microsoft.com/.default" };

        AuthenticationResult result = null;
        try
        {
            result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
            return result;
        }
        catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
        {
            ...
            return result;
        }
    }

With the AccessToken, the Graph-Url and the File to be uploaded (as an IFormFile) the Helper ProtectedApiCallHelper.PostWebApi is called

public async Task PostWebApi(string webApiUrl, string accessToken, IFormFile fileToUpload)
    {
        Stream stream = fileToUpload.OpenReadStream();
        var x = stream.Length;
        HttpContent content = new StreamContent(stream);

        if (!string.IsNullOrEmpty(accessToken))
        {
            var defaultRequestHeaders = HttpClient.DefaultRequestHeaders;               
            HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));             
            defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);

// Here the 400 Bad Request happens
            HttpResponseMessage response = await HttpClient.PutAsync(webApiUrl, content);

            if (response.IsSuccessStatusCode)
            {
                return;
            }
            else
            {
                 //error handling                   
                return;
            }
        }
    }

EDIT

Please see the working solution below.


Solution

  • This the final working example using a GraphServiceClient

    public async Task<DriveItem> UploadSmallFile(IFormFile file, bool uploadToSharePoint)
        {
            IFormFile fileToUpload = file;
            Stream ms = new MemoryStream();
    
            using (ms = new MemoryStream()) //this keeps the stream open
            {
                await fileToUpload.CopyToAsync(ms);
                ms.Seek(0, SeekOrigin.Begin);
                var buf2 = new byte[ms.Length];
                ms.Read(buf2, 0, buf2.Length);
    
                ms.Position = 0; // Very important!! to set the position at the beginning of the stream
                GraphServiceClient _graphServiceClient = await AuthenticateViaAppIdAndSecret();
    
                DriveItem uploadedFile = null;
                if (uploadToSharePoint == true)
                {
                    uploadedFile = (_graphServiceClient
                    .Sites["root"]
                    .Drives["{DriveId}"]
                    .Items["{Id_of_Targetfolder}"]
                    .ItemWithPath(fileToUpload.FileName)
                    .Content.Request()
                    .PutAsync<DriveItem>(ms)).Result;
                }
                else
                {
                    // Upload to OneDrive (for Business)
                    uploadedFile = await _graphServiceClient
                    .Users["{Your_EmailAdress}"]
                    .Drive
                    .Root
                    .ItemWithPath(fileToUpload.FileName)
                    .Content.Request()
                    .PutAsync<DriveItem>(ms);
                }
    
                ms.Dispose(); //clears memory
                return uploadedFile; //returns a DriveItem. 
            }
        }
    

    You can use a HttpClient as well

    public async Task PostWebApi(string webApiUrl, string accessToken, IFormFile fileToUpload)
        {
    
            //Create a Stream and convert it to a required HttpContent-stream (StreamContent).
            // Important is the using{...}. This keeps the stream open until processed
            using (MemoryStream data = new MemoryStream())
            {
                await fileToUpload.CopyToAsync(data);
                data.Seek(0, SeekOrigin.Begin);
                var buf = new byte[data.Length];
                data.Read(buf, 0, buf.Length);
                data.Position = 0;
                HttpContent content = new StreamContent(data);
    
    
                if (!string.IsNullOrEmpty(accessToken))
                {
                    // NO Headers other than the AccessToken should be added. If you do
                    // an Error 406 is returned (cannot process). So, no Content-Types, no Conentent-Dispositions
    
                    var defaultRequestHeaders = HttpClient.DefaultRequestHeaders;                    
                    defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
    
                    HttpResponseMessage response = await HttpClient.PutAsync(webApiUrl, content);
    
                    if (response.IsSuccessStatusCode)
                    {
                        return;
                    }
                    else
                    {
                        // do something else
                        return;
                    }
                }
                content.Dispose();
                data.Dispose();
            } //einde using memorystream 
        }
    }