【问题标题】:Upload a file in C# Asp.Net Core to Sharepoint/OneDrive using Microsoft Graph without user interaction使用 Microsoft Graph 将 C# Asp.Net Core 中的文件上传到 Sharepoint/OneDrive,无需用户交互
【发布时间】:2019-12-13 17:43:33
【问题描述】:

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

  • 该应用程序已在 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


    【解决方案1】:

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

    【讨论】:

    • @QuestionPS1991 感谢您的明确指示。 resultDriveItem 是一个什么样的变量?那应该是 HttpResponseMessage 吗?
    • @QuestionPS1991 您的解决方案也很有效!非常感谢你的帮助。我现在面临让 Stream 工作的另一个挑战。直到现在(在两种解决方案中),Memorystream 仍然是空的(0 字节)。因为我没有收到任何错误,所以一切都“正常”但没有结果。
    • 结果的类型是DriveItem。
    • 流,IFormFile.OpenReadStream 只是打开流,还需要读取文件。这应该可以帮助您入门:link
    • 我已经解决了。我将在此讨论中添加解决方案。非常感谢
    【解决方案2】:

    根据文档:Upload or replace the contents of a 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
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-16
        • 1970-01-01
        • 2010-11-30
        • 1970-01-01
        • 2020-07-30
        • 1970-01-01
        相关资源
        最近更新 更多