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