.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


相关推荐
薛勇4 小时前
.net中如何选择async/await 和Task.Run?
c#·.net
持梦远方5 小时前
持梦行文本编辑器(cmyfEdit):架构设计与十大核心功能实现详解
开发语言·数据结构·c++·算法·microsoft·visual studio
QT 小鲜肉5 小时前
【Linux命令大全】001.文件管理之paste命令(实操篇)
linux·运维·服务器·笔记·microsoft
温暖的苹果6 小时前
【.Net runtime】coreclr(.Net应用启动过程)
c#·.net·.netcore
天空属于哈夫克37 小时前
企业微信 API 开发:外部群自动化推送的技术实现
数据库·microsoft
我是唐青枫7 小时前
深入理解 C#.NET IEnumerable<T>:一切集合的起点
c#·.net
mudtools8 小时前
当传统工单遇见飞书:.NET系统的协作升级之旅
c#·自动化·.net·飞书
唐青枫8 小时前
深入理解 C#.NET Interlocked.Increment:原子操作的核心
c#·.net
ejjdhdjdjdjdjjsl17 小时前
JSON序列化与反序列化实战指南
数据库·microsoft·c#