文件选取器
使用文件选取器 v8 将使你可以使用解决方案中 M365 服务中使用的相同功能。 这意味着随着我们对服务的迭代和改进,这些新功能会为用户而出现!
这个新的“控件”是通过发布消息与之交互的托管在 Microsoft 服务中的页面。 页面可以嵌入在 iframe 中或作为弹出窗口托管。
可在此处找到 7.2 选取器的文档。
所需设置
若要运行示例或在解决方案中使用控件,需要创建 AAD 应用程序。 可以遵循这些步骤:
- 创建新的 AAD 应用注册,注意应用程序的 ID
- 在身份验证下,创建新的单页应用程序注册表
- 将重定向 URI 设置为
https://localhost
(用于测试示例) - 请确保访问令牌和 ID 令牌都得到检查
- 可以选择将此应用程序配置为多租户,但这已超出了本文的讨论范围
- 将重定向 URI 设置为
- 在 API 权限下
- 添加
Files.Read.All
、Sites.Read.All
、离开User.Read
图委派权限 - 为 SharePoint 委派权限添加
AllSites.Read
、MyFiles.Read
- 添加
如果在 SharePoint 框架 中进行开发,则可以在应用程序清单中使用资源“SharePoint”和“Microsoft Graph”请求这些权限。
若要允许用户上传文件并在选取器体验中创建文件夹,需要请求对
Files.ReadWrite.All
、Sites.ReadWrite.All
、AllSites.Write
和MyFiles.Write
的访问权限。
权限
文件选取器始终使用委托的权限进行操作,因此只能访问当前用户已有权访问的文件和文件夹。
至少必须请求 SharePoint MyFiles.Read 权限才能从用户的 OneDrive 和 SharePoint 网站读取文件。
请查看下表,了解根据要执行的操作需要哪些权限。 此表中的所有权限都指委托的权限。
读取 | 写入 | |
---|---|---|
OneDrive | SharePoint.MyFiles.Read 或 Graph.Files.Read |
SharePoint.MyFiles.Write 或 Graph.Files.ReadWrite |
SharePoint 网站 | SharePoint.MyFiles.Read 或 Graph.Files.Read 或 SharePoint.AllSites.Read |
SharePoint.MyFiles.Write 或 Graph.Files.ReadWrite 或 SharePoint.AllSites.Write |
Teams 频道 | Graph.ChannelSettings.Read.All 和 SharePoint.AllSites.Read | Graph.ChannelSettings.Read.All 和 SharePoint.AllSites.Write |
运作方式
若要使用控件,必须:
- 向托管在 /_layouts/15/FilePicker.aspx 的“控件”页发出 POST 请求。 使用此请求需要提供一些参数,关键参数是 选取器配置。
- 使用 postMessage 和 消息端口 在主机应用程序与控件之间设置消息传递。
- 建立通信通道后,必须响应各种“命令”,第一个命令是提供身份验证令牌。
- 最后,需要响应其他命令消息,以提供新的/不同的身份验证令牌、处理选取的文件或关闭弹出窗口。
以下各部分对每一步骤进行了解释。
我们还提供显示了与控件集成的不同方法的 各种示例。
启动选取器
- 查看 文件选取器配置架构。
若要启动选取器,需要创建一个可以是 iframe 或弹出窗口的“窗口”。 创建窗口后,应构建窗体并使用定义的查询字符串参数将窗体 POST 到 URL {baseUrl}/_layouts/15/FilePicker.aspx
。
上述 {baseUrl}
值为目标 Web 的 SharePoint Web URL 或用户的 OneDrive。 一些示例包括:“https://tenant.sharepoint.com/sites/dev"或 “https://tenant-my.sharepoint.com"。
OneDrive 使用者配置
name | 描述 |
---|---|
柄 | https://login.microsoftonline.com/consumers |
范围 | OneDrive.ReadWrite 或 OneDrive.ReadOnly |
baseUrl | https://onedrive.live.com/picker |
请求令牌时,
OneDrive.ReadOnly
将使用 或OneDrive.ReadWrite
请求令牌时。 请求应用程序的权限时,将选择Files.Read
Files.ReadWrite
或 (或另一个 Files.X 范围) 。
// create a new window. The Picker's recommended maximum size is 1080x680, but it can scale down to
// a minimum size of 250x230 for very small screens or very large zoom.
const win = window.open("", "Picker", "width=1080,height=680");
// we need to get an authentication token to use in the form below (more information in auth section)
const authToken = await getToken({
resource: baseUrl,
command: "authenticate",
type: "SharePoint",
});
// to use an iframe you can use code like:
// const frame = document.getElementById("iframe-id");
// const win = frame.contentWindow;
// now we need to construct our query string
// options: These are the picker configuration, see the schema link for a full explaination of the available options
const queryString = new URLSearchParams({
filePicker: JSON.stringify(options),
locale: 'en-us'
});
// Use MSAL to get a token for your app, specifying the resource as {baseUrl}.
const accessToken = await getToken(baseUrl);
// we create the absolute url by combining the base url, appending the _layouts path, and including the query string
const url = baseUrl + `/_layouts/15/FilePicker.aspx?${queryString}`);
// create a form
const form = win.document.createElement("form");
// set the action of the form to the url defined above
// This will include the query string options for the picker.
form.setAttribute("action", url);
// must be a post request
form.setAttribute("method", "POST");
// Create a hidden input element to send the OAuth token to the Picker.
// This optional when using a popup window but required when using an iframe.
const tokenInput = win.document.createElement("input");
tokenInput.setAttribute("type", "hidden");
tokenInput.setAttribute("name", "access_token");
tokenInput.setAttribute("value", accessToken);
form.appendChild(tokenInput);
// append the form to the body
win.document.body.append(form);
// submit the form, this will load the picker page
form.submit();
选取器配置
通过序列化包含所需设置的 json 对象,并将其追加到查询字符串值 (如 启动选取器 部分中所示) 来配置选取器。 还可以查看 完整架构。 至少必须提供身份验证、项和消息传递设置。
下面显示了一个最小设置对象示例。 这会在频道 27 上设置消息传递,让选取器知道我们可以提供令牌,并且我们希望“我的文件”选项卡代表用户的 OneDrive 文件。 此配置将使用“https://{tenant}-my.sharepoint.com”形式的 baseUrl;
const channelId = uuid(); // Always use a unique id for the channel when hosting the picker.
const options = {
sdk: "8.0",
entry: {
oneDrive: {}
},
// Applications must pass this empty `authentication` option in order to obtain details item data
// from the picker, or when embedding the picker in an iframe.
authentication: {},
messaging: {
origin: "http://localhost:3000",
channelId: channelId
},
}
选取器设计为在给定实例中与 OneDrive 或 SharePoint 配合使用,只应包含其中一个条目部分。
本地化
文件选取器接口支持与 SharePoint 设置的语言相同的本地化。
若要设置文件选取器的语言,请使用 locale
查询字符串参数,设置为上面列表中的 LCID 值之一。
建立消息传递
创建窗口并提交窗体后,需要建立消息传递通道。 这用于从选取器接收命令并进行响应。
let port: MessagePort;
function initializeMessageListener(event: MessageEvent): void {
// we validate the message is for us, win here is the same variable as above
if (event.source && event.source === win) {
const message = event.data;
// the channelId is part of the configuration options, but we could have multiple pickers so that is supported via channels
// On initial load and if it ever refreshes in its window, the Picker will send an 'initialize' message.
// Communication with the picker should subsequently take place using a `MessageChannel`.
if (message.type === "initialize" && message.channelId === options.messaging.channelId) {
// grab the port from the event
port = event.ports[0];
// add an event listener to the port (example implementation is in the next section)
port.addEventListener("message", channelMessageListener);
// start ("open") the port
port.start();
// tell the picker to activate
port.postMessage({
type: "activate",
});
}
}
};
// this adds a listener to the current (host) window, which the popup or embed will message when ready
window.addEventListener("message", messageEvent);
消息侦听器实现
解决方案必须处理来自选取器的各种消息,将其分类为通知或命令。 通知不需要响应,可以视为日志信息。 一个例外情况是下面突出显示的 page-loaded
通知,它将告诉你选取器已准备就绪。
命令要求你确认,并根据命令进行响应。 本部分演示作为事件侦听器添加到端口的 channelMessageListener
函数的示例实现。 接下来的章节将详细介绍通知和命令。
async function channelMessageListener(message: MessageEvent): Promise<void> {
const payload = message.data;
switch (payload.type) {
case "notification":
const notification = payload.data;
if (notification.notification === "page-loaded") {
// here we know that the picker page is loaded and ready for user interaction
}
console.log(message.data);
break;
case "command":
// all commands must be acknowledged
port.postMessage({
type: "acknowledge",
id: message.data.id,
});
// this is the actual command specific data from the message
const command = payload.data;
// command.command is the string name of the command
switch (command.command) {
case "authenticate":
// the first command to handle is authenticate. This command will be issued any time the picker requires a token
// 'getToken' represents a method that can take a command and return a valid auth token for the requested resource
try {
const token = await getToken(command);
if (!token) {
throw new Error("Unable to obtain a token.");
}
// we report a result for the authentication via the previously established port
port.postMessage({
type: "result",
id: message.data.id,
data: {
result: "token",
token: token,
}
});
} catch (error) {
port.postMessage({
type: "result",
id: message.data.id,
data: {
result: "error",
error: {
code: "unableToObtainToken",
message: error.message
}
}
});
}
break;
case "close":
// in the base of popup this is triggered by a user request to close the window
await close(command);
break;
case "pick":
try {
await pick(command);
// let the picker know that the pick command was handled (required)
port.postMessage({
type: "result",
id: message.data.id,
data: {
result: "success"
}
});
} catch (error) {
port.postMessage({
type: "result",
id: message.data.id,
data: {
result: "error",
error: {
code: "unusableItem",
message: error.message
}
}
});
}
break;
default:
// Always send a reply, if if that reply is that the command is not supported.
port.postMessage({
type: "result",
id: message.data.id,
data: {
result: "error",
error: {
code: "unsupportedCommand",
message: command.command
}
}
});
break;
}
break;
}
}
获取令牌
控件要求我们能够根据发送的命令为其提供身份验证令牌。 为此,我们创建一个方法,该方法接受命令并返回令牌,如下所示。 我们使用 包 @azure/msal-browser
来处理身份验证工作。
控件目前依赖于 SharePoint 令牌而不是 Graph,因此需要确保资源正确,并且不能将令牌用于 Graph 调用。
import { PublicClientApplication, Configuration, SilentRequest } from "@azure/msal-browser";
import { combine } from "@pnp/core";
import { IAuthenticateCommand } from "./types";
const app = new PublicClientApplication(msalParams);
async function getToken(command: IAuthenticateCommand): Promise<string> {
let accessToken = "";
const authParams = { scopes: [`${combine(command.resource, ".default")}`] };
try {
// see if we have already the idtoken saved
const resp = await app.acquireTokenSilent(authParams!);
accessToken = resp.accessToken;
} catch (e) {
// per examples we fall back to popup
const resp = await app.loginPopup(authParams!);
app.setActiveAccount(resp.account);
if (resp.idToken) {
const resp2 = await app.acquireTokenSilent(authParams!);
accessToken = resp2.accessToken;
} else {
// throw the error that brought us here
throw e;
}
}
return accessToken;
}
选取的项目结果
选择项目后,选取器将通过消息传递通道返回所选项的数组。 虽然有一组可能返回的信息,但始终保证包括以下内容:
{
"id": string,
"parentReference": {
"driveId": string
},
"@sharePoint.endpoint": string
}
使用此 URL 可以构造一个 URL 来发出 GET 请求,以获取有关所选文件所需的任何信息。 它通常采用以下形式:
@sharePoint.endpoint + /drives/ + parentReference.driveId + /items/ + id
需要包含具有适当权限的有效令牌才能读取请求中的文件。
上传文件
如果向用于选取器令牌的应用程序授予 Files.ReadWrite.All
权限,顶部菜单中将显示一个小组件,允许将文件和文件夹上传到 OneDrive 或 SharePoint 文档库。 无需进行其他配置更改,此行为由应用程序 + 用户权限控制。 请注意,如果用户无权访问要上传的位置,则选取器不会显示 选项。
品牌打造指南
与 Microsoft OneDrive 文件选取器集成的应用程序也可以选择将其与 OneDrive 的集成推广给客户。 由于 OneDrive 具有使用者和商业产品/服务,因此可在第三方应用程序接口中显示以下选项:
- Microsoft OneDrive (个人)
- 也可以显示为 Microsoft OneDrive 个人版
- Microsoft OneDrive (工作/学校)
- 也可能显示为 Microsoft OneDrive for work 或 school