当我尝试使用 Microsoft Graph API 通过守护程序应用程序将文件上传到我的 OneDrive 时,收到错误 400 错误请求。我使用 HttpClient,而不是 GraphServiceClient,因为后者假设交互并与 DeleatedAuthenticationProvider(?)一起使用。
主要方法
Upload
通过HelperAuthenticationConfig
获取AccessToken,并使用HelperProtectedApiCallHelper
将文件放入OneDrive/SharePoint。
[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();
}
我使用以下标准助手获得了正确的访问令牌
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;
}
}
使用 AccessToken、Graph-Url 和要上传的文件(作为 IFormFile),调用 Helper
ProtectedApiCallHelper.PostWebApi
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;
}
}
}
编辑
请参阅下面的工作解决方案。
您可以使用 GraphServiceClient,无需使用客户端 ID 和客户端密钥进行用户交互。首先,创建一个名为 GraphAuthProvider 的类:
public class GraphAuthProvider
{
public async Task<GraphServiceClient> AuthenticateViaAppIdAndSecret(
string tenantId,
string clientId,
string clientSecret)
{
var scopes = new string[] { "https://graph.microsoft.com/.default" };
// Configure the MSAL client as a confidential client
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}/v2.0")
.WithClientSecret(clientSecret)
.Build();
// Build the Microsoft Graph client. As the authentication provider, set an async lambda
// which uses the MSAL client to obtain an app-only access token to Microsoft Graph,
// and inserts this access token in the Authorization header of each API request.
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
return graphServiceClient;
}
}
然后,您可以创建经过身份验证的 GraphServiceClient 并使用它们上传文件,例如上传到 SharePoint:
GraphServiceClient _graphServiceClient = await _graphAuthProvider.AuthenticateViaAppIdAndSecret(
tenantId,
clientId,
appSecret);
using (Stream fileStream = new FileStream(
fileLocation,
FileMode.Open,
FileAccess.Read))
{
resultDriveItem = await _graphServiceClient.Sites[sites[0]]
.Drives[driveId].Root.ItemWithPath(fileName).Content.Request().PutAsync<DriveItem>(fileStream);
}
关于权限:您可能需要的权限不仅仅是Files.ReadWrite.All。据我所知,应用程序需要应用程序权限Sites.ReadWrite.All才能将文档上传到SharePoint。
根据文档:上传或替换 DriveItem 的内容
如果使用客户端凭证流程(无用户的 M2M 流程),您应该使用以下请求:
PUT /drives/{drive-id}/items/{parent-id}:/{filename}:/content
而不是:
https://graph.microsoft.com/v1.0/drive/items/{Id_Of_Specific_Folder}/proefdocument.txt/content
这是使用 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.
}
}
您也可以使用 HttpClient
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
}
}
我在尝试创建文件后也得到了 400,
自检后发现文件名中含有
:
,该文件名无效。