Actualizar aplicación para crear y enumerar contenedores incrustados de SharePoint

Completado

En este ejercicio, actualizará el proyecto existente para crear y recuperar contenedores incrustados de SharePoint.

Adición de la funcionalidad de inicio de sesión al spa de React de front-end

Comencemos agregando compatibilidad para el inicio de sesión de usuario en el proyecto front-end de React SPA. Esto implica agregar código de configuración para la Biblioteca de autenticación de Microsoft (MSAL) y configurar microsoft Graph Toolkit con los detalles de inicio de sesión necesarios.

Configuración del proveedor de autenticación

Busque y abra el archivo ./src/index.tsx .

Agregue las siguientes importaciones después de las importaciones existentes:

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";

Por último, agregue el código siguiente antes del código de representación de React para configurar un proveedor MSAL global para Microsoft Graph Toolkit:

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
  ]
});

Actualizar la página principal de la aplicación para controlar el proceso de inicio y cierre de sesión

Busque y abra el archivo ./src/App.tsx . Quite la importación del logo.svg archivo y, a continuación, agregue y actualice las importaciones restantes para reflejar el código siguiente:

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";

Después de las instrucciones import, agregue el siguiente enlace de React personalizado para obtener el estado de inicio de sesión del usuario actual:

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;
}

Ahora, actualice mediante la App adición del código siguiente después de la declaración:

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);
      }
    });
}

Este código obtendrá primero el estado de inicio de sesión actual del usuario y, a continuación, configurará y obtendrá un token de acceso una vez que el usuario seleccione un botón en la representación.

Por último, actualice la representación del componente reemplazando la instrucción del return() componente por lo siguiente:

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>
);

Prueba de la autenticación de la aplicación React

Ahora vamos a probar la aplicación React del lado cliente para asegurarse de que la autenticación funciona.

Desde la línea de comandos de la carpeta raíz del proyecto, ejecute el siguiente comando:

npm run start

El script compilará el lado servidor & proyectos del lado cliente, los iniciará e iniciará un explorador en el proyecto del lado cliente:

Captura de pantalla de la aplicación React.

Seleccione el botón Iniciar sesión e inicie sesión con la cuenta profesional y educativa con acceso de administrador a su inquilino de Microsoft 365.

Después de iniciar sesión correctamente, se le redirigirá a la aplicación react que muestra el nombre y el correo electrónico del usuario que inició sesión:

Captura de pantalla de la aplicación React con un usuario que ha iniciado sesión.

Detenga el servidor presionando CTRL + C en la consola.

Agregar la capacidad de enumerar y seleccionar contenedores

Con la configuración básica del proyecto y configurada para admitir la autenticación de usuario, ahora vamos a agregar compatibilidad a la lista y seleccionar Contenedores en la partición del inquilino.

La administración de contenedores es una operación con privilegios que requiere un token de acceso que se debe obtener en el lado servidor. Comencemos por crear primero los elementos de API del lado servidor para admitir la aplicación React.

Agregar método de utilidad para recuperar un token de OBO para llamar a Microsoft Graph

Primero necesitamos un archivo de utilidad para obtener un token mediante el flujo en nombre de OAuth2 mediante nuestra credencial existente.

Cree un archivo ./server/auth.ts y agréguele el código siguiente:

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];
  }
}

Esto tomará un confidentialClientApplication configurado y el token de identificador del usuario y usará la biblioteca MSAL para solicitar un nuevo token que podemos usar para llamar a Microsoft Graph.

Agregar administración de contenedores al proyecto de API del lado servidor

Ahora, vamos a crear el controlador para obtener una lista de los contenedores con Microsoft Graph que se devolverán a nuestra aplicación React. Cree un archivo ./server/listContainers.ts y agréguele las siguientes importaciones:

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";

A continuación, agregue el código siguiente para crear una instancia de un objeto ConfidentialClientApplication de MSAL que se usará para obtener el token de acceso de 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);

Cree y exporte una nueva función que haga lo siguiente:

  • Compruebe que la solicitud incluye un Authorization encabezado con un token de acceso.
  • Use ese token y para ConfidentialClientApplication obtener un token de OBO que podamos usar para llamar a Microsoft Graph.
  • Use el token de OBO para crear un nuevo AuthenticationProvider cliente que usaremos para llamar a Microsoft Graph.
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);
  };
}

El último paso es crear el cliente de Microsoft Graph y solicitar todos los contenedores que tienen un conjunto específico ContainerTypeId . Agregue la siguiente inmediatez del código antes del corchete de cierre de la función:

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;
}

Agregue este nuevo punto de conexión a nuestro servidor restify. Busque y abra el archivo ./server/index.ts , agregue una sola instrucción import al final de las importaciones existentes y agregue un agente de escucha para las solicitudes HTTP GET al /api/listContainers punto de conexión:

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();
});

Actualización del proyecto de React para mostrar los contenedores

Con la configuración de la API del lado servidor, podemos actualizar nuestro proyecto de React para proporcionar a los usuarios una interfaz para seleccionar un contenedor existente o crear un nuevo contenedor.

Empiece por crear una nueva interfaz, ./src/common/IContainer.ts, con el siguiente contenido para representar el objeto que enviaremos y recibiremos de las llamadas de Microsoft Graph:

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

Cree un nuevo servicio que se usará para llamar a nuestro punto de conexión de API o realizar llamadas directas desde la aplicación React a Microsoft Graph. Cree un archivo ./src/services/spembedded.ts y agréguele el código siguiente:

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 {
}

A continuación, agregue una función de utilidad para obtener un token de acceso que podamos usar para llamar a Microsoft Graph. Esto creará un MSAL PublicClientApplication que usaremos para enviar para llamar a nuestra API del lado servidor. Agregue la siguiente función a la clase De 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;
  }
};

Agregue el siguiente listContainers() método que llamará a nuestra API del lado servidor para obtener una lista de todos nuestros contenedores. Esto obtendrá el token de acceso que devuelve nuestro método de utilidad y lo incluirá en las llamadas a la API del lado servidor. Recuerde que este token de acceso se usa para crear un MSAL ConfidentialClientApplication para obtener un token de OBO para llamar a Microsoft Graph. Ese token de OBO tiene más permisos concedidos que solo podemos obtener de una llamada del lado servidor:

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;
    }
  }
};

Ahora, cree un nuevo componente de React que controlará todas nuestras tareas de contenedor y la interfaz de usuario. Cree un nuevo archivo, ./src/components/containers.tsx, y agréguele el código siguiente:

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;

Nota:

Observe los // BOOKMARK # comentarios del componente. Esto para asegurarse de que va a agregar código en los lugares correctos.

El primer paso es obtener una lista de los contenedores. Para empezar, agregue el código siguiente antes de // BOOKMARK 1. Esto establecerá algunos valores de estado para contener los contenedores recuperados de nuestra API del lado servidor:

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

A continuación, agregue el siguiente enlace de React después // BOOKMARK 1 de para obtener todos los contenedores cuando se cargue la página y establezca el objeto de estado que realizará un seguimiento de ellos:

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

Cree un controlador de eventos después de la implementación del enlace de React que acaba de agregar y que se activará cuando un usuario seleccione un contenedor en el control desplegable que agregaremos a nuestra experiencia de usuario:

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

Actualice la representación agregando lo siguiente al return() método después del // BOOKMARK 3 código. Esto creará un DropDown control y un marcador de posición donde agregaremos una lista del contenido en el contenedor seleccionado:

<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>

Este código usa un objeto de estilo que agregamos al componente cuando lo creamos inicialmente. Para usar los estilos, agregue el código siguiente inmediatamente antes de la instrucción y // BOOKMARK 3 el return() comentario:

const styles = useStyles();

El último paso es agregar nuestro nuevo Containerscomponente a la aplicación. Busque y abra el ./src/App.tsx archivo y agregue la siguiente importación después de las importaciones existentes:

import Containers from "./components/containers";

Busque el <div></div> marcado en el componente return(). Reemplace ese marcado por lo siguiente para agregar nuestro Containers componente solo si el usuario ha iniciado sesión:

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

Lista de pruebas y selección de contenedores

Ahora vamos a probar la aplicación React del lado cliente para ver los efectos de los cambios para mostrar contenedores en la aplicación React.

Desde la línea de comandos de la carpeta raíz del proyecto, ejecute el siguiente comando:

npm run start

Cuando se cargue el explorador, inicie sesión con la misma cuenta profesional y educativa que ha estado usando.

Después del inicio de sesión, la página se volverá a cargar y debe mostrar una lista de contenedores si ha creado anteriormente alguno.

Captura de pantalla de la aplicación React que enumera todos nuestros contenedores.

Al seleccionar un contenedor, observe que la lógica condicional mostrará el marcador de posición con el nombre del contenedor seleccionado:

Captura de pantalla de la aplicación React después de seleccionar un contenedor.

Detenga el servidor presionando CTRL + C en la consola.

Agregar la capacidad de crear nuevos contenedores

En esta sección, actualizará la API del lado servidor y la aplicación React para crear nuevos contenidos a partir de nuestra aplicación web.

Comencemos por crear primero los elementos de API del lado servidor para admitir la aplicación React.

Adición de compatibilidad para crear contenedores en el proyecto de API del lado servidor

Ahora vamos a crear el controlador para crear un contenedor con Microsoft Graph para devolverlo a nuestra aplicación React. Cree un archivo ./server/createContainer.ts y agréguele las siguientes importaciones:

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";

A continuación, agregue el código siguiente para crear una instancia de MSAL ConfidentialClientApplication que se usará para obtener el token de acceso de 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);

Cree y exporte una nueva función que haga lo siguiente:

  • Compruebe que la solicitud incluye un Authorization encabezado con un token de acceso.
  • Use ese token y para ConfidentialClientApplication obtener un token de OBO que podamos usar para llamar a Microsoft Graph.
  • Use el token de OBO para crear un nuevo AuthenticationProvider cliente que usaremos para llamar a Microsoft Graph.
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);
  };
}

El último paso consiste en crear el cliente de Microsoft Graph y enviar la solicitud para crear un nuevo conjunto de contenedores en un determinado ContainerTypeId. Agregue la siguiente inmediatez del código antes del corchete de cierre de la función:

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;
}

Agregue este nuevo punto de conexión a nuestro servidor restify. Busque y abra el archivo ./server/index.ts , agregue una sola instrucción import al final de las importaciones existentes y agregue un agente de escucha para las solicitudes HTTP POST al /api/createContainers punto de conexión:

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();
});

Actualizar el proyecto de React para crear un nuevo contenedor

Busque y abra el archivo ./src/services/spembedded.ts y agregue el código siguiente a la clase :

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;
    }
  }
};

Este nuevo método es similar al método existente listContainers() , excepto que crea un nuevo objeto y lo envía como POST a nuestra API del lado servidor.

El último paso es actualizar nuestro Containers componente para actualizar la interfaz de usuario para admitir la creación de un contenedor. Busque y abra el archivo ./src/components/containers.tsx .

En este paso, usaremos un componente Fluent UI React Dialog . Para empezar, agregue los siguientes objetos de estado y objetos de identificador de componente de interfaz de usuario inmediatamente antes del // BOOKMARK 1 comentario:

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);

A continuación, agregue el código siguiente inmediatamente antes del // BOOKMARK 2 comentario. Estos controladores se usan para actualizar las propiedades de nombre y descripción del nuevo contenedor de los Input componentes que estarán en Dialog. También se usan para controlar con el usuario selecciona un botón para crear el contenedor:

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);
}

Por último, agregue el siguiente código de React inmediatamente después del elemento de cierre </Dropdown> . Esto creará un componente de Fluent UI React Dialog que se desencadena a partir de un botón:

<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>

Prueba de la creación de nuevos contenedores

Ahora vamos a probar la aplicación React del lado cliente para ver los efectos de los cambios para crear contenedores en la aplicación React.

Desde la línea de comandos de la carpeta raíz del proyecto, ejecute el siguiente comando:

npm run start

Cuando se cargue el explorador, inicie sesión con la misma cuenta profesional y educativa que ha estado usando.

Después de iniciar sesión, la página se volverá a cargar y ahora debe incluir un botón para iniciar el cuadro de diálogo:

Captura de pantalla de la aplicación React con un botón para iniciar el cuadro de diálogo.

Seleccione el botón Crear un nuevo contenedor de almacenamiento para abrir el cuadro de diálogo. Observe cómo se deshabilita el botón hasta que escriba un nombre:

Captura de pantalla del cuadro de diálogo para crear un nuevo contenedor.

Escriba un nombre y una descripción para el contenedor y seleccione Crear contenedor de almacenamiento. Al crear el contenedor, los botones están deshabilitados y un Spinner control muestra al usuario que está trabajando:

Captura de pantalla del cuadro de diálogo que crea el contenedor.

Una vez que el cuadro de diálogo desaparece, verá que el selector nuevo muestra nuestro nuevo contenedor.

Captura de pantalla que muestra el selector actualizado con nuestro nuevo contenedor.

Detenga el servidor presionando CTRL + C en la consola.

Resumen

En este ejercicio, ha actualizado el proyecto existente para crear y recuperar contenedores incrustados de SharePoint.

Compruebe sus conocimientos

1.

¿Cuál es el propósito de ConfidentialClientApplication en la API del lado servidor?

2.

¿Qué operación no puede realizar directamente la aplicación React y requiere la API del lado servidor?