更新应用程序以创建和列出 SharePoint Embedded Containers

已完成

在本练习中,你将更新现有项目以创建和检索 SharePoint Embedded Containers。

将登录功能添加到前端 React SPA

首先,将用户登录支持添加到 React SPA 前端项目。 这包括为 Microsoft 身份验证库添加一些配置代码 (MSAL) ,并使用必要的登录详细信息配置 Microsoft Graph 工具包。

设置身份验证提供程序

找到并打开 ./src/index.tsx 文件。

在现有导入之后添加以下导入:

import { Providers } from "@microsoft/mgt-element";
import { Msal2Provider } from "@microsoft/mgt-msal2-provider";
import * as Constants from "./common/constants"
import * as Scopes from "./common/scopes";

最后,在 React 呈现代码之前添加以下代码,为 Microsoft Graph 工具包配置全局 MSAL 提供程序:

Providers.globalProvider = new Msal2Provider({
  clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
  authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
  scopes: [
    ...Scopes.GRAPH_OPENID_CONNECT_BASIC,
    Scopes.GRAPH_USER_READ_ALL,
    Scopes.GRAPH_FILES_READ_WRITE_ALL,
    Scopes.GRAPH_SITES_READ_ALL,
    Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED
  ]
});

更新应用主页以处理登录和注销过程

找到并打开 ./src/App.tsx 文件。 删除文件的导入 logo.svg ,然后添加并更新剩余的导入以反映以下代码:

import React, {
  useState, useEffect
} from "react";
import {
  Providers,
  ProviderState
} from "@microsoft/mgt-element";
import { Login } from "@microsoft/mgt-react";
import {
  FluentProvider,
  Text,
  webLightTheme
} from "@fluentui/react-components"
import './App.css';
import {
  InteractionRequiredAuthError,
  PublicClientApplication
} from "@azure/msal-browser";
import * as Scopes from "./common/scopes";
import * as Constants from "./common/constants";

在导入语句之后,添加以下自定义 React 挂钩以获取当前用户的登录状态:

function useIsSignedIn() {
  const [isSignedIn, setIsSignedIn] = useState(false);

  useEffect(() => {
    const updateState = async () => {
      const provider = Providers.globalProvider;
      setIsSignedIn(provider && provider.state === ProviderState.SignedIn);
    };

    Providers.onProviderUpdated(updateState);
    updateState();

    return () => {
      Providers.removeProviderUpdatedListener(updateState);
    }
  }, []);

  return isSignedIn;
}

现在,通过在 声明后添加以下代码来更新 App

const isSignedIn = useIsSignedIn();

const promptForContainerConsent = async (event: CustomEvent<undefined>): Promise<void> => {
  const containerScopes = {
    scopes: [Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED],
    redirectUri: `${window.location.protocol}://${window.location.hostname}${(window.location.port === '80' || window.location.port === '443') ? '' : ':' + window.location.port}`
  };

  const msalInstance = new PublicClientApplication({
    auth: {
      clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
      authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
    },
    cache: {
      cacheLocation: 'localStorage',
      storeAuthStateInCookie: false,
    },
  });

  msalInstance.acquireTokenSilent(containerScopes)
    .then(response => {
      console.log('tokenResponse', JSON.stringify(response));
    })
    .catch(async (error) => {
      if (error instanceof InteractionRequiredAuthError) {
        return msalInstance.acquireTokenPopup(containerScopes);
      }
    });
}

此代码将首先获取用户的当前登录状态,然后在用户选择呈现中的按钮后配置并获取访问令牌。

最后,通过将组件的 语句替换为以下内容来更新组件的 return() 呈现:

return (
  <FluentProvider theme={webLightTheme}>
    <div className="App">
      <Text size={900} weight='bold'>Sample SPA SharePoint Embedded App</Text>
      <Login loginCompleted={promptForContainerConsent} />
      <div>
      </div>
    </div>
  </FluentProvider>
);

测试 React 应用的身份验证

现在,让我们测试客户端 React 应用,以确保身份验证正常工作。

在项目的根文件夹中的命令行中运行以下命令:

npm run start

该脚本将生成服务器端 & 客户端项目,启动它们,然后启动客户端项目的浏览器:

React 应用的屏幕截图。

选择“ 登录 ”按钮,并使用具有 Microsoft 365 租户管理员访问权限 的工作和学校 帐户登录。

成功登录后,你将重定向回 React 应用,其中显示已登录用户的姓名和电子邮件:

包含已登录用户的 React 应用的屏幕截图。

在控制台中按 Ctrl + C 停止服务器。

添加列出并选择“容器”的功能

通过基本项目设置并配置为支持用户身份验证,现在让我们添加支持,以在租户分区中列出并选择“容器”。

容器管理是一种特权操作,需要必须获取服务器端的访问令牌。 首先,我们先创建服务器端 API 部件以支持 React 应用。

添加实用工具方法以检索 OBO 令牌以调用 Microsoft Graph

我们首先需要一个实用工具文件,以便使用现有凭据通过 OAuth2 代理流获取令牌。

创建一个新文件 ./server/auth.ts ,并向其添加以下代码:

import { ConfidentialClientApplication } from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import * as Scopes from './common/scopes';

export const getGraphToken = async (confidentialClient: ConfidentialClientApplication, token: string): Promise<[boolean, string | any]> => {
  try {
    const graphTokenRequest = {
      oboAssertion: token,
      scopes: [
        Scopes.GRAPH_SITES_READ_ALL,
        Scopes.SPEMBEDDED_FILESTORAGECONTAINER_SELECTED
      ]
    };
    const oboGraphToken = (await confidentialClient.acquireTokenOnBehalfOf(graphTokenRequest))!.accessToken;
    return [true, oboGraphToken];
  } catch (error: any) {
    const errorResult = {
      status: 500,
      body: JSON.stringify({
        message: `Unable to generate Microsoft Graph OBO token: ${error.message}`,
        providedToken: token
      })
    };
    return [false, errorResult];
  }
}

这将采用配置的 ConfidentialClientApplication 和用户的 ID 令牌,并使用 MSAL 库请求可用于调用 Microsoft Graph 的新令牌。

将容器管理添加到服务器端 API 项目

现在,让我们创建处理程序,以获取使用 Microsoft Graph 返回回 React 应用的容器列表。 创建一个新文件 ./server/listContainers.ts 并向其添加以下导入:

import {
  Request,
  Response
} from "restify";
import * as MSAL from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import { getGraphToken } from "./auth";

接下来,添加以下代码以创建 MSAL ConfidentialClientApplication 的实例,该实例将用于获取 OBO 访问令牌:

const msalConfig: MSAL.Configuration = {
  auth: {
    clientId: process.env['API_ENTRA_APP_CLIENT_ID']!,
    authority: process.env['API_ENTRA_APP_AUTHORITY']!,
    clientSecret: process.env['API_ENTRA_APP_CLIENT_SECRET']!
  },
  system: {
    loggerOptions: {
      loggerCallback(loglevel: any, message: any, containsPii: any) {
        console.log(message);
      },
      piiLoggingEnabled: false,
      logLevel: MSAL.LogLevel.Verbose,
    }
  }
};

const confidentialClient = new MSAL.ConfidentialClientApplication(msalConfig);

创建并导出将执行以下操作的新函数:

  • 验证请求包含具有 Authorization 访问令牌的标头。
  • 使用该令牌和 ConfidentialClientApplication 获取可用于调用 Microsoft Graph 的 OBO 令牌。
  • 使用 OBO 令牌创建用于调用 Microsoft Graph 的新 AuthenticationProvider 客户端。
export const listContainers = async (req: Request, res: Response) => {
  if (!req.headers.authorization) {
    res.send(401, { message: 'No access token provided.' });
    return;
  }

  const [bearer, token] = (req.headers.authorization || '').split(' ');

  const [graphSuccess, oboGraphToken] = await getGraphToken(confidentialClient, token);

  if (!graphSuccess) {
    res.send(200, oboGraphToken);
    return;
  }

  const authProvider = (callback: MSGraph.AuthProviderCallback) => {
    callback(null, oboGraphToken);
  };
}

最后一步是创建 Microsoft Graph 客户端并请求具有特定 ContainerTypeId 集的所有容器。 在函数的右括号前添加以下代码即时性:

try {
  const graphClient = MSGraph.Client.init({
    authProvider: authProvider,
    defaultVersion: 'beta'
  });

  const graphResponse = await graphClient.api(`storage/fileStorage/containers?$filter=containerTypeId eq ${process.env["CONTAINER_TYPE_ID"]}`).get();

  res.send(200, graphResponse);
  return;
} catch (error: any) {
  res.send(500, { message: `Unable to list containers: ${error.message}` });
  return;
}

将此新终结点添加到 restify 服务器。 找到并打开 ./server/index.ts 文件,将单个导入语句添加到现有导入的末尾,并向终结点添加 HTTP GET 请求的 /api/listContainers 侦听器:

import { listContainers } from "./listContainers";
...

server.get('/api/listContainers', async (req, res, next) => {
  try {
    const response = await listContainers(req, res);
    res.send(200, response)
  } catch (error: any) {
    res.send(500, { message: `Error in API server: ${error.message}` });
  }
  next();
});

更新 React 项目以显示我们的容器

使用服务器端 API 设置,我们可以更新 React 项目,为用户提供选择现有容器或创建新的容器的界面。

首先创建一个新接口 ./src/common/IContainer.ts,其中包含以下内容来表示我们将从 Microsoft Graph 调用发送和接收的对象:

export interface IContainer {
  id: string;
  displayName: string;
  containerTypeId: string;
  createdDateTime: string;
}

创建一个新服务,该服务将用于调用我们的 API 终结点或从 React 应用直接调用 Microsoft Graph。 创建一个新文件 ./src/services/spembedded.ts ,并向其添加以下代码:

import { Providers, ProviderState } from '@microsoft/mgt-element';
import * as Msal from '@azure/msal-browser';
import * as Constants from './../common/constants';
import * as Scopes from './../common/scopes';
import { IContainer } from './../common/IContainer';

export default class SpEmbedded {
}

接下来,添加一个实用工具函数,以获取可用于调用 Microsoft Graph 的访问令牌。 这将创建一个 MSAL,我们将用于提交该 MSAL PublicClientApplication 来调用服务器端 API。 将以下函数添加到 SharePoint Embedded 类:

async getApiAccessToken() {
  const msalConfig: Msal.Configuration = {
    auth: {
      clientId: Constants.CLIENT_ENTRA_APP_CLIENT_ID,
      authority: Constants.CLIENT_ENTRA_APP_AUTHORITY,
    },
    cache: {
      cacheLocation: 'localStorage',
      storeAuthStateInCookie: false
    }
  };

  const scopes: Msal.SilentRequest = {
    scopes: [`api://${Constants.CLIENT_ENTRA_APP_CLIENT_ID}/${Scopes.SPEMBEDDED_CONTAINER_MANAGE}`],
    prompt: 'select_account',
    redirectUri: `${window.location.protocol}//${window.location.hostname}${(window.location.port === '80' || window.location.port === '443') ? '' : ':' + window.location.port}`
  };

  const publicClientApplication = new Msal.PublicClientApplication(msalConfig);
  await publicClientApplication.initialize();

  let tokenResponse;
  try {
    tokenResponse = await publicClientApplication.acquireTokenSilent(scopes);
    return tokenResponse.accessToken;
  } catch (error) {
    if (error instanceof Msal.InteractionRequiredAuthError) {
      tokenResponse = await publicClientApplication.acquireTokenPopup(scopes);
      return tokenResponse.accessToken;
    }
    console.log(error)
    return null;
  }
};

添加以下 listContainers() 方法,该方法将调用服务器端 API 以获取所有容器的列表。 这将获取实用工具方法返回的访问令牌,并将其包含在对服务器端 API 的调用中。 回想一下,此访问令牌用于创建 MSAL ConfidentialClientApplication 以获取用于调用 Microsoft Graph 的 OBO 令牌。 该 OBO 令牌具有授予的更多权限,我们只能从服务器端调用获取这些权限:

async listContainers(): Promise<IContainer[] | undefined> {
  const api_endpoint = `${Constants.API_SERVER_URL}/api/listContainers`;

  if (Providers.globalProvider.state === ProviderState.SignedIn) {
    const token = await this.getApiAccessToken();
    const containerRequestHeaders = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };
    const containerRequestOptions = {
      method: 'GET',
      headers: containerRequestHeaders
    };
    const response = await fetch(api_endpoint, containerRequestOptions);

    if (response.ok) {
      const containerResponse = await response.json();
      return (containerResponse.value)
        ? (containerResponse.value) as IContainer[]
        : undefined;
    } else {
      console.error(`Unable to list Containers: ${JSON.stringify(response)}`);
      return undefined;
    }
  }
};

现在,创建一个新的 React 组件,用于处理所有容器任务和 UI。 创建一个新文件 ./src/components/containers.tsx,并向其添加以下代码:

import React, { useEffect, useState } from 'react';
import {
  Button,
  Dialog, DialogActions, DialogContent, DialogSurface, DialogBody, DialogTitle, DialogTrigger,
  Dropdown, Option,
  Input, InputProps, InputOnChangeData,
  Label,
  Spinner,
  makeStyles, shorthands, useId
} from '@fluentui/react-components';
import type {
  OptionOnSelectData,
  SelectionEvents
} from '@fluentui/react-combobox'
import { IContainer } from "./../common/IContainer";
import SpEmbedded from '../services/spembedded';

const spe = new SpEmbedded();

const useStyles = makeStyles({
  root: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    ...shorthands.padding('25px'),
  },
  containerSelector: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    rowGap: '10px',
    ...shorthands.padding('25px'),
  },
  containerSelectorControls: {
    width: '400px',
  },
  dialogContent: {
    display: 'flex',
    flexDirection: 'column',
    rowGap: '10px',
    marginBottom: '25px'
  }
});

export const Containers = (props: any) => {
  // BOOKMARK 1 - constants & hooks

  // BOOKMARK 2 - handlers go here

  // BOOKMARK 3 - component rendering
  return (
  );
}

export default Containers;

注意

// BOOKMARK #请注意组件中的注释。 这些可确保在正确的位置添加代码。

第一步是获取容器列表。 首先,在 之前 // BOOKMARK 1添加以下代码。 这将设置几个状态值来保存从服务器端 API 检索到的容器:

const [containers, setContainers] = useState<IContainer[]>([]);
const [selectedContainer, setSelectedContainer] = useState<IContainer | undefined>(undefined);
const containerSelector = useId('containerSelector');

接下来,在 后面 // BOOKMARK 1 添加以下 React 挂钩,以在页面加载时获取所有容器,并设置将跟踪它们的状态对象:

useEffect(() => {
  (async () => {
    const containers = await spe.listContainers();
    if (containers) {
      setContainers(containers);
    }
  })();
}, []);

在刚刚添加的 React 挂钩实现之后创建一个事件处理程序,当用户从我们将添加到 UX 的下拉列表控件中选择容器时会触发该处理程序:

const onContainerDropdownChange = (event: SelectionEvents, data: OptionOnSelectData) => {
  const selected = containers.find((container) => container.id === data.optionValue);
  setSelectedContainer(selected);
};

通过在代码后面将以下内容添加到 return() 方法, // BOOKMARK 3 来更新呈现。 这将创建一个 DropDown 控件和一个占位符,我们将在其中添加所选容器中的内容列表:

<div className={styles.root}>
  <div className={styles.containerSelector}>
    <Dropdown
      id={containerSelector}
      placeholder="Select a Storage Container"
      className={styles.containerSelectorControls}
      onOptionSelect={onContainerDropdownChange}>
      {containers.map((option) => (
        <Option key={option.id} value={option.id}>{option.displayName}</Option>
      ))}
    </Dropdown>
  </div>
  {selectedContainer && (`[[TOOD]] container "${selectedContainer.displayName}" contents go here`)}
</div>

此代码使用最初创建组件时添加到组件的样式对象。 若要使用这些样式,请紧接在 语句前面 return() 添加以下代码并 // BOOKMARK 3 注释:

const styles = useStyles();

最后一步是将新 Containers组件添加到应用。 找到并打开文件, ./src/App.tsx 并在现有导入后添加以下导入:

import Containers from "./components/containers";

<div></div> 组件的 return()中找到标记。 将该标记替换为以下内容,以仅当用户登录时才添加我们的 Containers 组件:

<div>
  {isSignedIn && (<Containers />)}
</div>

测试列表并选择“容器”

现在,让我们测试客户端 React 应用,了解在 React 应用中列出和显示容器的更改的效果。

在项目的根文件夹中的命令行中运行以下命令:

npm run start

当浏览器加载时,请使用你一直使用的相同 工作和学校 帐户登录。

登录后,页面将重新加载,并且应显示容器列表(如果之前已创建任何容器)。

React 应用的屏幕截图,其中列出了所有容器。

选择容器时,请注意,条件逻辑将显示占位符,其中选择了容器的名称:

选择容器后 React 应用的屏幕截图。

在控制台中按 Ctrl + C 停止服务器。

添加创建新容器的功能

在本部分中,你将更新服务器端 API 和 React 应用,以从 Web 应用程序创建新的包含。

首先,我们先创建服务器端 API 部件以支持 React 应用。

向服务器端 API 项目添加创建容器的支持

现在,让我们创建处理程序,以使用 Microsoft Graph 创建容器,以返回到我们的 React 应用。 创建一个新文件 ./server/createContainer.ts ,并向其添加以下导入:

import {
  Request,
  Response
} from "restify";
import * as MSAL from "@azure/msal-node";
require('isomorphic-fetch');
import * as MSGraph from '@microsoft/microsoft-graph-client';
import { getGraphToken } from "./auth";

接下来,添加以下代码以创建 MSAL ConfidentialClientApplication 的实例,该实例将用于获取 OBO 访问令牌:

const msalConfig: MSAL.Configuration = {
  auth: {
    clientId: process.env['API_ENTRA_APP_CLIENT_ID']!,
    authority: process.env['API_ENTRA_APP_AUTHORITY']!,
    clientSecret: process.env['API_ENTRA_APP_CLIENT_SECRET']!
  },
  system: {
    loggerOptions: {
      loggerCallback(loglevel: any, message: any, containsPii: any) {
        console.log(message);
      },
      piiLoggingEnabled: false,
      logLevel: MSAL.LogLevel.Verbose,
    }
  }
};

const confidentialClient = new MSAL.ConfidentialClientApplication(msalConfig);

创建并导出将执行以下操作的新函数:

  • 验证请求包含具有 Authorization 访问令牌的标头。
  • 使用该令牌和 ConfidentialClientApplication 获取可用于调用 Microsoft Graph 的 OBO 令牌。
  • 使用 OBO 令牌创建用于调用 Microsoft Graph 的新 AuthenticationProvider 客户端。
export const createContainer = async (req: Request, res: Response) => {
  if (!req.headers.authorization) {
    res.send(401, { message: 'No access token provided.' });
    return;
  }

  const [bearer, token] = (req.headers.authorization || '').split(' ');

  const [graphSuccess, graphTokenRequest] = await getGraphToken(confidentialClient, token);

  if (!graphSuccess) {
    res.send(200, graphTokenRequest);
    return;
  }

  const authProvider = (callback: MSGraph.AuthProviderCallback) => {
    callback(null, graphTokenRequest);
  };
}

最后一步是创建 Microsoft Graph 客户端并提交请求,以创建一个设置为特定 ContainerTypeId的新容器集。 在函数的右括号前添加以下代码即时性:

try {
  const graphClient = MSGraph.Client.init({
    authProvider: authProvider,
    defaultVersion: 'beta'
  });

  const containerRequestData = {
    displayName: req.body!.displayName,
    description: (req.body?.description) ? req.body.description : '',
    containerTypeId: process.env["CONTAINER_TYPE_ID"]
  };

  const graphResponse = await graphClient.api(`storage/fileStorage/containers`).post(containerRequestData);

  res.send(200, graphResponse);
  return;
} catch (error: any) {
  res.send(500, { message: `Failed to create container: ${error.message}` });
  return;
}

将此新终结点添加到 restify 服务器。 找到并打开 ./server/index.ts 文件,将单个 import 语句添加到现有导入的末尾,并将 HTTP POST 请求的侦听器添加到 /api/createContainers 终结点:

import { createContainer } from "./createContainer";

...

server.post('/api/createContainer', async (req, res, next) => {
  try {
    const response = await createContainer(req, res);
    res.send(200, response)
  } catch (error: any) {
    res.send(500, { message: `Error in API server: ${error.message}` });
  }
  next();
});

更新 React 项目以创建新的容器

找到并打开文件 ./src/services/spembedded.ts 并将以下代码添加到 类:

async createContainer(containerName: string, containerDescription: string = ''): Promise<IContainer | undefined> {
  const api_endpoint = `${Constants.API_SERVER_URL}/api/createContainer`;

  if (Providers.globalProvider.state === ProviderState.SignedIn) {
    const token = await this.getApiAccessToken();
    const containerRequestHeaders = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };

    const containerRequestData = {
      displayName: containerName,
      description: containerDescription
    };
    const containerRequestOptions = {
      method: 'POST',
      headers: containerRequestHeaders,
      body: JSON.stringify(containerRequestData)
    };

    const response = await fetch(api_endpoint, containerRequestOptions);

    if (response.ok) {
      const containerResponse = await response.json();
      return containerResponse as IContainer;
    } else {
      console.error(`Unable to create container: ${JSON.stringify(response)}`);
      return undefined;
    }
  }
};

此新方法类似于现有 listContainers() 方法,只不过它会创建一个新对象并将其作为 POST 提交到服务器端 API。

最后一步是更新组件 Containers 以更新 UI 以支持创建容器。 找到并打开 ./src/components/containers.tsx 文件。

对于此步骤,我们将使用 Fluent UI React Dialog 组件。 首先,在注释前面 // BOOKMARK 1 添加以下状态对象和 UI 组件 ID 对象:

const [dialogOpen, setDialogOpen] = useState(false);
const containerName = useId('containerName');
const [name, setName] = useState('');
const containerDescription = useId('containerDescription');
const [description, setDescription] = useState('');
const [creatingContainer, setCreatingContainer] = useState(false);

接下来,紧接在注释前面 // BOOKMARK 2 添加以下代码。 这些处理程序用于从 Input 将位于 中的 Dialog组件更新新容器的名称和说明属性。 它们还用于处理用户选择创建容器的按钮:

const handleNameChange: InputProps["onChange"] = (event: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData): void => {
  setName(data?.value);
};

const handleDescriptionChange: InputProps["onChange"] = (event: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData): void => {
  setDescription(data?.value);
};

const onContainerCreateClick = async (event: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
  setCreatingContainer(true);
  const newContainer = await spe.createContainer(name, description);

  if (newContainer) {
    setName('');
    setDescription('');
    setContainers(current => [...current, newContainer]);
    setSelectedContainer(newContainer);
    setDialogOpen(false);
  } else {
    setName('');
    setDescription('');
  }
  setCreatingContainer(false);
}

最后,紧接在结束 </Dropdown> 元素之后添加以下 React 代码。 这将创建从按钮触发的 Fluent UI React Dialog 组件:

<Dialog open={dialogOpen} onOpenChange={(event, data) => setDialogOpen(data.open)}>

  <DialogTrigger disableButtonEnhancement>
    <Button className={styles.containerSelectorControls} appearance='primary'>Create a new storage Container</Button>
  </DialogTrigger>

  <DialogSurface>
    <DialogBody>
      <DialogTitle>Create a new storage Container</DialogTitle>

      <DialogContent className={styles.dialogContent}>
        <Label htmlFor={containerName}>Container name:</Label>
        <Input id={containerName} className={styles.containerSelectorControls} autoFocus required
          value={name} onChange={handleNameChange}></Input>
        <Label htmlFor={containerDescription}>Container description:</Label>
        <Input id={containerDescription} className={styles.containerSelectorControls} autoFocus required
          value={description} onChange={handleDescriptionChange}></Input>
        {creatingContainer &&
          <Spinner size='medium' label='Creating storage Container...' labelPosition='after' />
        }
      </DialogContent>

      <DialogActions>
        <DialogTrigger disableButtonEnhancement>
          <Button appearance="secondary" disabled={creatingContainer}>Cancel</Button>
        </DialogTrigger>
        <Button appearance="primary"
          value={name}
          onClick={onContainerCreateClick}
          disabled={creatingContainer || (name === '')}>Create storage Container</Button>
      </DialogActions>
    </DialogBody>
  </DialogSurface>

</Dialog>

测试创建新容器

现在,让我们测试客户端 React 应用,了解在 React 应用中创建容器的更改的效果。

在项目的根文件夹中的命令行中运行以下命令:

npm run start

当浏览器加载时,请使用你一直使用的相同 工作和学校 帐户登录。

登录后,页面将重新加载,现在应包含用于启动对话框的按钮:

React 应用的屏幕截图,其中包含用于启动对话框的按钮。

选择“ 创建新存储容器 ”按钮以打开对话框。 请注意,在输入名称之前如何禁用该按钮:

用于创建新容器的对话框的屏幕截图。

输入容器的名称和说明,然后选择 “创建存储容器”。 创建容器时,按钮处于禁用状态,控件 Spinner 显示用户正在运行:

创建容器的对话框的屏幕截图。

对话框消失后,你将看到新的选择器显示我们的新容器!

显示更新后选择器的屏幕截图,其中包含新的容器。

在控制台中按 Ctrl + C 停止服务器。

摘要

在本练习中,你更新了现有项目以创建和检索 SharePoint Embedded Containers。

知识检查

1.

服务器端 API 中的 ConfidentialClientApplication 用途是什么?

2.

哪个操作不能由 React 应用程序直接执行,并且需要服务器端 API?