从 C# 控制台应用程序将文件上传到受 2FA 保护的 Sharepoint 以及从中下载文件

问题描述 投票:0回答:1

我正在为这件事抓狂。似乎有 1000 条建议可以做我想做的事,但其中大多数已经过时,或者不起作用。微软文档比无用更糟糕,没有清楚地解释原理,引入了一堆复杂和晦涩的术语,然后在解释任何内容之前分支到其他页面的链接网络中,然后执行相同的操作。每当我认为我已经找到答案时,事实证明我正在寻找的方法已经或即将被弃用。

因此,我将在这里列出一些问题并列出我正在做的工作。首先,我的要求:

  • 该方法必须从 dotNet 控制台应用程序作为每日计划作业在无人值守的情况下运行
  • 仅在设置作业时需要进行一次基于 2FA Web 的身份验证(可能间隔很长,例如每年),这是可以接受的
  • 它必须能够将文件上传到受 2FA 保护的共享点站点或从该站点下载文件,该站点不受我的直接控制,但我可以与该站点的所有者进行通信并请求进行配置更改

我正在做什么

感谢 this 视频,我已成功从 Microsoft 365 电子邮件地址的控制台应用程序获得 2FA 身份验证。代码的身份验证部分如下所示:

    var scopes = new string[] { "https://graph.microsoft.com/.default" };

    var confidentialClient = ConfidentialClientApplicationBuilder
            .Create(spServiceProfile.ClientID)
            .WithClientSecret(spServiceProfile.ClientSecret)
            .WithAuthority(new Uri("https://login.microsoftonline.com/" + spServiceProfile.TenantID + "/"))
            .Build();

          var authResult = await confidentialClient
                .AcquireTokenForClient(scopes)
                .ExecuteAsync();

所以我们需要三个东西:TenantID、ClientID 和 ClientSecret。该视频展示了如何获取或配置它们。

要上传或下载文件,我需要选择用于访问 Sharepoint 的 API 或服务。似乎有两个明确的选择:CSOM 和 GraphServiceClient。

我找到了 GraphServiceClient 的代码(但尚未尝试过),如下所示:

      uploadedFile = (_graphServiceClient
      .Sites["root"]
      .Drives["{DriveId}"]
      .Items["{Id_of_Targetfolder}"]
      .ItemWithPath(fileToUpload.FileName)
      .Content.Request()
      .PutAsync<DriveItem>(ms)).Result;

乍一看,这似乎比 CSOM 方式灵活得多,我有工作代码,但在引入 2FA 时停止了身份验证,因为它只是使用用户名和密码登录。看起来像:

            var authMgr = new PnP.Framework.AuthenticationManager(spServiceProfile.ClientID, spServiceProfile.UserName, securePassword);
            using (var rootCtx = authMgr.GetContext(rootSiteUrl)) {
                Uri webUri = Web.WebUrlFromPageUrlDirect(rootCtx, new Uri(rootSiteUrl + pathUrl));

                using (var ctx = authMgr.GetContext(webUri.AbsoluteUri)) {
                    var list = ctx.Web.GetList(pathUrl);

                    var sharepointFolders = list.RootFolder.Folders;
                    ctx.Load(sharepointFolders);
                    ctx.ExecuteQuery();

                    Folder sharepointRootFolder = null;
                    foreach (var sharepointFolder in sharepointFolders) {
                        //s1 += "\r\n\\" + folder.Name;
                        if (sharepointFolder.Name.ToLower() == sharepointJob.SharepointRootFolder.ToLower()) { 
                            sharepointRootFolder = sharepointFolder;
                        }
                    }

                    if (fileOperation == FileOperationEnum.UploadFolder) {
                        var job = (SharepointTransferFolderJobClass)sharepointJob;

                        // cycle through all files in folder
                        var f_sarr = Directory.EnumerateFiles(job.LocalRootFolder);
                        foreach (var localFilePath in f_sarr) {
                            var localFileName = Path.GetFileName(localFilePath);
                            sharepointRootFolder.UploadFile(localFileName, job.LocalRootFolder, true);
                        }

                        sharepointRootFolder.Update();
                        ctx.Load(sharepointRootFolder);
                        ctx.ExecuteQueryRetry();
                    }

                    if (fileOperation == FileOperationEnum.DownloadFolder) {
                        var job = (SharepointTransferFolderJobClass)sharepointJob;

                        foreach (var remoteFile in sharepointRootFolder.Files) {
                            var backupFileName = remoteFile.Name;
                            var sharepointFile = sharepointRootFolder.GetFile(backupFileName);
                            ctx.Load(sharepointFile);
                            ctx.ExecuteQueryRetry();

                            ClientResult<Stream> stream = sharepointFile.OpenBinaryStream();
                            ctx.ExecuteQueryRetry();

                            var destFullFilePath = Path.Combine(job.LocalRootFolder, backupFileName);
                            using (Stream fileStream = new FileStream(destFullFilePath, FileMode.Create)) {
                                CopyStream(stream.Value, fileStream);
                            }
                        }
                    }
                }
            }

好的,我们终于回答问题了:

  1. CSOM 即将被弃用吗?

是使用 CSOM 的指南,但它在顶部有一个显着的警告:“SharePoint 加载项模型目前仍受完全支持,但很快将在 SharePoint Online 中正式弃用(到 2023 年底)。考虑使用替代的可扩展性选项,以便在未来更好地证明您的可扩展性。'

我不知道我是否正在使用 SharePoint 加载项模型,或者这是否完整地引用了 CSOM,即使我使用的是 Sharepoint Online(我想,而不是本地部署,但它实际上是在本地) -现在任何人都可以使用prem,或者这只是一个许可制品?)。

那么我的用例是否会被弃用,总体而言,从长远来看,CSOM 是否会被弃用?或者它是长期生态系统的一部分?

  1. 如果不打算弃用,如何让 CSOM 使用我的身份验证方法?

我无法找到任何将两者结合起来的东西,并且智能感知没有产生任何有用的东西。

  1. 总体来说 GraphServiceClient 是更好的选择吗?

Graph 似乎正处于上升阶段,因此从长远来看,它可能是一个更好的选择。但我对它很陌生,不确定它的用途是什么(GraphQL 取代了 REST,对吧?)。我是否使用了它的一个侧面功能,该功能可能在将来的某个时候被删除,或者这正是它的目的?
我需要能够执行诸如枚举共享点文件夹和文件以及获取上次修改日期之类的操作。这个可以吗

基本上我需要知道 CSOM 和 GraphServiceClient 在 Sharepoint 生态系统中的位置。它们是否是用于不同目的的不同工具,并且都会长期存在? GraphServiceClient 会取代 CSOM 吗?

感谢您的回答!过去就像拥有用户名和密码一样简单。我毫不怀疑,任何理解这一切复杂性的人都可以在这一点上建立整个职业生涯。

c# sharepoint console-application file-transfer unattended-processing
1个回答
0
投票
  1. 目前还没有。 SharePoint 加载项模型与 CSOM 不直接相关。尽管如此,CSOM 可用于构建 SharePoint 加载项。

  2. 您似乎正在使用 PnP 框架。它应该支持开箱即用的交互式登录(即 2FA 登录),例如使用

    CreateWithInteractiveLogin
    方法(还有其他选项,例如证书或设备代码):

var authMgr = PnP.Framework.AuthenticationManager
    .CreateWithInteractiveLogin(spServiceProfile.ClientID);

using (var rootCtx = authMgr.GetContext(rootSiteUrl)) {
....

您仍然需要在 Azure AD 中注册您的应用程序,并为其授予适当的 SharePoint 权限,即从授予权限的选项列表中,您需要选择 SharePoint 而不是 Graph。

  1. Graph SharePoint API 仍然缺少 SharePoint REST API(即 CSOM)提供的许多功能。但如果你不需要这个高级功能,你也可以使用它。
© www.soinside.com 2019 - 2024. All rights reserved.