You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C#将Azure Blob存储中Zoom录制视频直接迁移至SharePoint

问题描述

我需要在C#中实现直接将Azure Blob存储中的Zoom录制视频迁移至SharePoint的方案。当前实现流程是:获取Azure存储文件URI → 下载到本地设备 → 上传至SharePoint。希望跳过本地存储环节,直接完成迁移,请问该如何实现?

现有本地中转版代码

public async Task<Microsoft.SharePoint.Client.File> UploadFromUrl(string siteUrl, string root, string path, string filename, string originUrl)
{
    using ClientContext clientContext = authManager.GetContext(siteUrl);
    string text = _downloadDirectory + filename;
    Folder uploadDirectory = GetUploadDirectory(clientContext, root, path);
    //new WebClient().DownloadFile(new Uri(originUrl), text);
    var client = new HttpClient();
    GrantAccess(_downloadDirectory);
    await DownloadFileTaskAsync(client, new Uri(originUrl), text);

    long length = new FileInfo(text).Length;
    int num = 10485760;
    if (length <= num)
    {
        return UploadFromBuffer(clientContext, uploadDirectory, filename, text);
    }

    return new ChunkUploader(clientContext, num).Upload(uploadDirectory, text);
}

尝试Microsoft Graph方案遇到的错误

我尝试用Microsoft Graph实现无本地中转的上传,但遇到两个编译错误:

  1. 无法将Azure.Identity.ClientSecretCredential转换为Microsoft.Graph.IAuthenticationProvider
  2. 语句GraphClient = new GraphServiceClient(clientSecretCredential, scopes);中,无法将string[]转换为Microsoft.Graph.IHttpProvider

我的Graph尝试代码

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;

namespace ZoomRecordingHelper
{
    public class GraphHandler
    {
        public GraphServiceClient GraphClient { get; set; }

        public GraphHandler()
        {
            var clientId = "";
            var clientSecret = "";
            var tenantId = "";

            var options = new TokenCredentialOptions
            {
                AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
            };

            var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
            var scopes = new[] { "https://graph.microsoft.com/.default" };

            GraphClient = new GraphServiceClient(clientSecretCredential, scopes);
        }

        public async Task UploadFileToSharePoint(string siteId, string driveId, string fileName, Stream fileStream)
        {
            try
            {
                var uploadUrl = $"https://graph.microsoft.com/v1.0/sites/{siteId}/drives/{driveId}/items/root:/{fileName}:/content";

                using (var httpClient = new HttpClient())
                {
                    var accessToken = await GetAccessTokenAsync();
                    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                    using (var httpRequest = new HttpRequestMessage(HttpMethod.Put, uploadUrl))
                    {
                        httpRequest.Content = new StreamContent(fileStream);
                        httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                        using (var response = await httpClient.SendAsync(httpRequest))
                        {
                            response.EnsureSuccessStatusCode();
                            Console.WriteLine("File uploaded successfully.");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error uploading file: {ex.Message}");
            }
        }

        private async Task<string> GetAccessTokenAsync()
        {
            var clientId = "";
            var clientSecret = "";
            var tenantId = "";

            var options = new TokenCredentialOptions
            {
                AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
            };

            var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
            var tokenRequestContext = new TokenRequestContext(new string[] { "https://graph.microsoft.com/.default" });

            var accessTokenResult = await clientSecretCredential.GetTokenAsync(tokenRequestContext);

            return accessTokenResult.Token;
        }
    }
}

项目依赖文件(.csproj)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="AutoMapper" Version="10.1.1" />
    <PackageReference Include="Azure.Storage.Files.Shares" Version="12.18.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.6.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventHubs" Version="5.4.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.WebPubSub" Version="1.1.0" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.2.0" />
    <PackageReference Include="Microsoft.SharePoint.Client" Version="14.0.4762.1000" />
    <PackageReference Include="Microsoft.SharePointOnline.CSOM" Version="16.1.25012.12000" />
    <PackageReference Include="Nexus.Base.EventGridExtensions" Version="2020.3.13.1" />
    <PackageReference Include="Nexus.Base.EventHubExtensions" Version="2020.3.13.2" />
    <PackageReference Include="PnP.Framework" Version="1.16.0" />
    <PackageReference Include="System.ServiceModel.Duplex" Version="6.0.*" />
    <PackageReference Include="System.ServiceModel.Federation" Version="6.0.*" />
    <PackageReference Include="System.ServiceModel.Http" Version="6.0.*" />
    <PackageReference Include="System.ServiceModel.NetTcp" Version="6.0.*" />
    <PackageReference Include="System.ServiceModel.Security" Version="6.0.*" />
    <PackageReference Include="Zoom.API.Client.Library" Version="2.0.4" />
  </ItemGroup>
</Project>

解决方案

1. 修复Microsoft Graph依赖问题

你遇到的编译错误是因为缺少适配Azure.Identity的Graph SDK扩展包,且项目未引入正确的Microsoft Graph NuGet包。在.csproj中添加以下依赖:

<PackageReference Include="Microsoft.Graph" Version="5.*" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.*" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.*" />

注:Microsoft Graph SDK v5+ 才支持直接传入ClientSecretCredential,旧版本需要用AuthenticationProvider包装。

2. 修正后的Graph实现(无本地中转)

以下代码直接从Azure Blob获取文件流,上传至SharePoint,完全跳过本地存储:

using System;
using System.IO;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Graph;
using Microsoft.Graph.Models;

namespace ZoomRecordingHelper
{
    public class BlobToSharepointMigrator
    {
        private readonly GraphServiceClient _graphClient;

        public BlobToSharepointMigrator(string tenantId, string clientId, string clientSecret)
        {
            var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
            _graphClient = new GraphServiceClient(credential, new[] { "https://graph.microsoft.com/.default" });
        }

        public async Task MigrateBlobToSharepoint(string blobContainerUrl, string blobName, string siteId, string driveId, string sharepointFilePath)
        {
            // 获取Azure Blob的文件流
            var blobClient = new BlobClient(new Uri($"{blobContainerUrl.TrimEnd('/')}/{blobName}"), new DefaultAzureCredential());
            using var blobStream = await blobClient.OpenReadAsync();

            // 直接通过Graph上传流到SharePoint
            var uploadResult = await _graphClient.Sites[siteId]
                .Drives[driveId]
                .Items["root:/"+sharepointFilePath+":/content"]
                .PutAsync<DriveItem>(blobStream);

            Console.WriteLine($"文件已成功迁移至SharePoint,ID:{uploadResult.Id}");
        }
    }
}

3. 基于CSOM的无本地中转方案(无需Graph)

如果你不想切换到Graph,可修改现有CSOM代码,直接使用流上传:

public async Task<Microsoft.SharePoint.Client.File> UploadFromStreamDirectly(string siteUrl, string root, string path, string filename, string originUrl)
{
    using ClientContext clientContext = authManager.GetContext(siteUrl);
    Folder uploadDirectory = GetUploadDirectory(clientContext, root, path);
    
    // 直接从Azure Blob获取流
    using var httpClient = new HttpClient();
    using var blobStream = await httpClient.GetStreamAsync(originUrl);

    // 上传流到SharePoint
    var fileCreationInfo = new FileCreationInformation
    {
        ContentStream = blobStream,
        Url = filename,
        Overwrite = true
    };

    var uploadFile = uploadDirectory.Files.Add(fileCreationInfo);
    clientContext.Load(uploadFile);
    await clientContext.ExecuteQueryAsync();

    return uploadFile;
}

注:如果文件超过10MB,需改用CSOM分块上传逻辑,直接传入流即可,无需本地文件。

关键注意事项

  • 权限配置:确保服务主体拥有Azure Blob的读取权限,以及SharePoint站点/文档库的写入权限。
  • 大文件处理:Graph自动处理4MB以上文件的分块上传;CSOM需手动实现分块逻辑,直接传入流即可。
  • 依赖冲突:移除旧版Microsoft.SharePoint.Client(v14),保留Microsoft.SharePointOnline.CSOM(v16)即可。

内容的提问来源于stack exchange,提问作者ngel_lala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 06:37:05