.NET 把文件上传到Sharepoint - Microsoft Graph API方式

1 - Microsoft Graph API 必备前置信息

  • SiteUrl
  • ClientId
  • ClientSecret
  • TargetFolder

SiteUrl 取值来源:

点击主页,地址栏的就是SiteUrl

ClientId 和 ClientSecret 取值来源:

bash 复制代码
1. 打开 https://portal.azure.com 并登录
2. 进入应用注册
- 搜索并点击 **"Azure Active Directory"** (或 **"Microsoft Entra ID"**)
- 左侧菜单选择 **"应用注册"** (App registrations)
3.创建新应用(如果还没有)
- 点击 **"新注册"** (New registration)
- 填写名称(如 "KBS-SharePoint-App")
- 选择支持的账户类型(通常选择"仅此组织目录中的账户")
- 点击 **"注册"**
4.获取 ClientId
- 在应用的 **"概述"** (Overview) 页面
- 复制 **"应用程序(客户端)ID"** (Application (client) ID)
- 这就是你的 `ClientId`
5.创建 ClientSecret
- 左侧菜单选择 **"证书和密码"** (Certificates & secrets)
- 点击 **"新客户端密码"** (New client secret)
- 添加描述(如 "KBS App Secret")
- 选择过期时间
- 点击 **"添加"**
- **⚠️ 立即复制"值"列的内容**,这就是 `ClientSecret`(离开页面后将无法再查看)

如下图:

TargetFolder 取值来源:

bash 复制代码
sharepoint进入目标文件夹,点击"复制链接" 如下:
https://域名/:f:/r/sites/站点/Shared%20Documents/AI_KnowledgeBase/ADO?csf=1&web=1&e=CfytE3

站点/ 后边  到 ADO 的 ? 号前边
即:/Shared%20Documents/AI_KnowledgeBase/ADO

规则就是:/Shared Docments/自定义目录
注:Shared%20Documents是Shared Docments 带空格转义后的字符串。

JSON配置实例:

bash 复制代码
{
  "SharePoint": {
    "SiteUrl": "https://域名/sites/站点名称",
    "ClientId": "f988xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
   "ClientSecret": "xxxxx~xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "TargetFolder": "/Shared Documents/AI_KnowledgeBase/ADO"
  }
}

2 - Microsoft Graph API 方式上传文件到SharePoint必须的API权限

问答:

配置好的样子:

参照步骤:

添加权限 -> Microsoft API -> Microsoft Graph

应用程序权限

在应用程序里边找到

✅ Sites.ReadWrite.All

✅ Files.ReadWrite.All

注:这样设置是完整访问,开发/测试环境 推荐。


生产环境推荐如下:

bash 复制代码
✅ Sites.Selected
使用 Sites.Selected 后,需要通过 PowerShell 或 Graph API 授予特定站点权限:

# 授予特定站点权限
Connect-MgGraph -Scopes "Sites.FullControl.All"

$appId = "f9885bb6-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$siteId = "<你的站点ID>"

# 授予写入权限
Invoke-MgGraphRequest -Method POST `
  -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" `
  -Body @{
    roles = @("write")
    grantedToIdentities = @(
      @{
        application = @{
          id = $appId
          displayName = "YourAppName"
        }
      }
    )
  }

3 - 实例代码

3.1 项目结构

3.1.1 appsettings.json

3.1.2 SharePointConfig.cs

3.1.3 Program.cs

csharp 复制代码
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Azure.Identity;
using Microsoft.Graph.Models;
using Microsoft.Kiota.Abstractions;

namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            // 检查是否运行诊断模式
            if (args.Length > 0 && args[0].Equals("--diagnose", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            try
            {
                // 读取配置文件
                var configuration = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .Build();

                var config = configuration.GetSection("SharePoint").Get<SharePointConfig>();
                if (config == null)
                {
                    Console.WriteLine("无法读取 SharePoint 配置");
                    return;
                }

                // 要上传的文件路径
                string localFilePath = @"D:\TempExcel\1.txt";
                
                if (!File.Exists(localFilePath))
                {
                    Console.WriteLine($"文件不存在: {localFilePath}");
                    return;
                }

                Console.WriteLine("开始上传文件到 SharePoint...");
                Console.WriteLine($"本地文件: {localFilePath}");
                Console.WriteLine($"目标站点: {config.SiteUrl}");
                Console.WriteLine($"目标文件夹: {config.TargetFolder}");

                // 上传文件
                await UploadFileToSharePoint(config, localFilePath);

                Console.WriteLine("文件上传成功!");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"错误: {ex.Message}");
                Console.WriteLine($"详细信息: {ex}");
                Console.WriteLine("\n提示: 运行 'dotnet run --diagnose' 来诊断配置问题");
            }
        }

        static async Task UploadFileToSharePoint(SharePointConfig config, string filePath)
        {
            // 从 SiteUrl 中提取租户、站点路径
            var uri = new Uri(config.SiteUrl);
            var tenantName = uri.Host.Split('.')[0]; // 例如:xxx
            var sitePath = uri.AbsolutePath; // 例如:/sites/xxx

            // 确定租户 ID
            string tenantId;
            if (!string.IsNullOrEmpty(config.TenantId))
            {
                tenantId = config.TenantId;
                Console.WriteLine($"使用配置的租户 ID: {tenantId}");
            }
            else
            {
                tenantId = $"{tenantName}.onmicrosoft.com";
                Console.WriteLine($"使用推断的租户 ID: {tenantId}");
            }

            // 创建 ClientSecretCredential
            Console.WriteLine($"客户端 ID: {config.ClientId}");
            var credential = new ClientSecretCredential(
                tenantId: tenantId,
                clientId: config.ClientId,
                clientSecret: config.ClientSecret
            );

            // 创建 Graph 客户端
            var graphClient = new GraphServiceClient(credential, new[] { "https://graph.microsoft.com/.default" });

            // 获取站点 ID
            Console.WriteLine("正在获取站点信息...");
            Console.WriteLine($"站点标识符: {tenantName}.sharepoint.com:{sitePath}");
            
            var site = await graphClient.Sites[$"{tenantName}.sharepoint.com:{sitePath}"].GetAsync();
            
            if (site == null || site.Id == null)
            {
                throw new Exception("无法获取站点信息");
            }

            Console.WriteLine($"站点 ID: {site.Id}");

            // 获取驱动器
            var drive = await graphClient.Sites[site.Id].Drive.GetAsync();
            if (drive == null || drive.Id == null)
            {
                throw new Exception("无法获取驱动器信息");
            }

            Console.WriteLine($"驱动器 ID: {drive.Id}");

            // 获取文件名
            string fileName = Path.GetFileName(filePath);

            // 构建目标路径(移除开头的斜杠)
            string targetPath = config.TargetFolder.TrimStart('/');
            string fullPath = $"{targetPath}/{fileName}";
            
            // 读取文件内容
            using var fileStream = File.OpenRead(filePath);
            
            // 上传文件
            Console.WriteLine($"正在上传文件: {fileName}...");
            
            // 对于小文件(< 4MB),使用简单上传
            var fileInfo = new FileInfo(filePath);
            if (fileInfo.Length < 4 * 1024 * 1024) // 4MB
            {
                // 使用 PUT 方法上传小文件
                await graphClient.Drives[drive.Id]
                    .Items["root"]
                    .ItemWithPath(fullPath)
                    .Content
                    .PutAsync(fileStream);

                Console.WriteLine($"文件已上传到: {fullPath}");
            }
            else
            {
                // 对于大文件,使用分块上传
                var uploadSessionRequestBody = new Microsoft.Graph.Drives.Item.Items.Item.CreateUploadSession.CreateUploadSessionPostRequestBody
                {
                    Item = new DriveItemUploadableProperties
                    {
                        AdditionalData = new Dictionary<string, object>
                        {
                            { "@microsoft.graph.conflictBehavior", "replace" }
                        }
                    }
                };

                var uploadSession = await graphClient.Drives[drive.Id]
                    .Items["root"]
                    .ItemWithPath(fullPath)
                    .CreateUploadSession
                    .PostAsync(uploadSessionRequestBody);

                if (uploadSession?.UploadUrl == null)
                {
                    throw new Exception("无法创建上传会话");
                }

                // 使用上传会话上传大文件
                var maxSliceSize = 320 * 1024; // 320 KB - 每次上传的块大小
                var fileUploadTask = new LargeFileUploadTask<DriveItem>(uploadSession, fileStream, maxSliceSize, graphClient.RequestAdapter);

                var uploadResult = await fileUploadTask.UploadAsync();
                
                if (uploadResult.UploadSucceeded)
                {
                    Console.WriteLine($"大文件已上传到: {fullPath}");
                }
                else
                {
                    throw new Exception("文件上传失败");
                }
            }
        }
    }
}

4-1 运行结果 - Success


相关推荐
彧翎Pro17 小时前
ASP.NET Core 外部依赖调用治理实战:HttpClientFactory、Polly 与幂等边界
microsoft·asp.net·php
唐青枫18 小时前
C#.NET Monitor 与 Mutex 深入解析:进程内同步、跨进程互斥与使用边界
c#·.net
会写代码的建筑师19 小时前
.NET 控制台后台程序实践细节总结
后端·.net
火山引擎开发者社区20 小时前
从监控盲区到业务洞察:深入解读 APMPlus 生产指标
大数据·人工智能·microsoft
好运的阿财20 小时前
OpenClaw四种角色详解
人工智能·python·程序人生·microsoft·开源·ai编程
阿捞220 小时前
在 .NET 中使用 Moonshot Kimi + AgentFramework:从 SDK 到 Agent 的完整实践
html·.net·xhtml
Access开发易登软件21 小时前
在 Access 中实现 Web 风格 To Do List
前端·数据结构·microsoft·list·vba·access·access开发
宝桥南山21 小时前
GitHub Copilot - 尝试使用一下GitHub Copilot SDK
microsoft·ai·微软·github·aigc·copilot
步步为营DotNet21 小时前
解锁.NET 11 中 Microsoft.Extensions.AI 在智能后端开发的深度应用
人工智能·microsoft·.net
无风听海21 小时前
.NET10之C# 中的is null深入理解
服务器·c#·.net