如何用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实现无本地中转的上传,但遇到两个编译错误:
- 无法将
Azure.Identity.ClientSecretCredential转换为Microsoft.Graph.IAuthenticationProvider - 语句
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
相关产品推荐
相关产品推荐

