使用 Microsoft Graph 将 C# Asp.Net Core 中的文件上传到 Sharepoint/OneDrive,无需用户交互

问题描述 投票:0回答:4

当我尝试使用 Microsoft Graph API 通过守护程序应用程序将文件上传到我的 OneDrive 时,收到错误 400 错误请求。我使用 HttpClient,而不是 GraphServiceClient,因为后者假设交互并与 DeleatedAuthenticationProvider(?)一起使用。

  • 该应用程序已在AAD中注册并具有正确的应用程序权限(Microsoft Graph / File ReadWrite.All)
  • 注册适用于一个租户,没有重定向网址(按照说明)

主要方法

Upload
通过Helper
AuthenticationConfig
获取AccessToken,并使用Helper
ProtectedApiCallHelper
将文件放入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;
            }
        }
    }

编辑

请参阅下面的工作解决方案。

c# asp.net-core sharepoint microsoft-graph-api onedrive
4个回答
7
投票

您可以使用 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。


3
投票

根据文档:上传或替换 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

1
投票

这是使用 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 
    }
}

0
投票

我在尝试创建文件后也得到了 400,

自检后发现文件名中含有

:
,该文件名无效。

© www.soinside.com 2019 - 2024. All rights reserved.