快速入门:使用客户端 JavaScript 和 Visual Studio Code 的 Web API

本快速入门介绍如何使用以下技术在 Visual Studio Code 中对 JavaScript 单页应用程序(SPA)进行身份验证和连接 Dataverse Web API。 生成一个应用来登录用户、调用 WhoAmI 函数并显示用户的 ID。

科技 Description
JavaScript Web 开发的编程语言,支持交互式内容。 它在浏览器中运行用于客户端脚本,并可用于服务器端 Node.js。
Visual Studio Code 一个轻型开源代码编辑器,其中包含调试、语法突出显示和插件支持。
单页应用程序(SPA) 当用户与应用交互时,加载单个 HTML 页面并动态更新内容的 Web 应用程序。 此方法通过减少页面重载并提高性能来提供更流畅、更快的用户体验。
适用于 JavaScript 的 Microsoft 身份验证库(MSAL.js) 使用Microsoft标识平台为 Web 应用程序启用身份验证和授权的库。 它简化了集成安全登录和令牌获取以访问受保护资源。
跨域资源共享 (CORS) SPA 应用程序可以将客户端 JavaScript 与 Dataverse Web API 配合使用,因为已启用 CORS。 CORS 是 Web 浏览器中的一项安全功能,允许从不同的源对 Web 服务器上的资源进行受控访问。 它使 Web 应用程序能够绕过 同源策略,促进跨不同域的安全可靠的数据共享。

目标

本快速入门重点介绍如何使用 SPA 客户端应用程序和最少的步骤连接到 Dataverse Web API 和 JavaScript。 完成本快速入门后,可以:

  • 登录并连接到 Dataverse。
  • 调用 WhoAmI 函数 并显示值 UserID

已完成的 Dataverse Web API 快速入门的屏幕截图,其中显示了已登录用户的 ID。

通过完成本快速入门,可以尝试 Web API 数据操作示例(客户端 JavaScript),该示例 演示了更高级的功能。

注释

本快速入门不适用于以下客户端 JavaScript 方案:

情景 了解详细信息
模型驱动的应用程序脚本 - 使用 JavaScript 在模型驱动应用中使用客户端脚本应用业务逻辑
- Xrm.WebApi (客户端 API 参考)
Power Apps 组件框架 - 代码组件 WebAPI
- 实现 Web API 组件
Power Pages门户 Power Pages门户 Web API

在这些方案中,相应的应用程序类型提供发送请求而不是直接使用 JavaScript 本机 提取 API 的功能,如本快速入门中所示。 模型驱动应用中的客户端脚本在经过身份验证的应用程序上下文中运行,因此每个请求都不需要访问令牌。

先决条件

下表介绍了完成本快速入门和 Web API 数据操作示例(客户端 JavaScript)所需的先决条件。

先决条件 Description
用于创建 Entra 应用注册的权限 需要能够创建Microsoft Entra应用注册才能完成本快速入门。

如果不确定是否具有此功能,请尝试注册 SPA 应用程序 并找出第一步。
Visual Studio Code 如果计算机上未安装Visual Studio Code,请下载并安装Visual Studio Code以运行本快速入门。
Node.js Node.js 是一种运行时环境,可用于在服务器端运行 JavaScript。 本快速入门创建一个 SPA 应用程序,该应用程序在浏览器而不是 Node.js 运行时的客户端上运行 JavaScript。 但 Node 程序包管理器 (npm) 随 Node.js一起安装,需要 npm 才能安装 Parcel 和 MSAL.js 库。
包裹 新式 Web 应用程序通常依赖于使用 npm 和脚本分发的开放源代码库,这些库需要在生成过程中进行管理和优化。 这些工具称为捆绑程序。 最常见的是 webpackVITE 也很受欢迎。 本快速入门使用 Parcel ,因为它提供了简化的体验。

有关使用不同框架和捆绑程序显示 SPA 应用程序的快速入门和示例,请参阅Microsoft Entra单页应用程序示例。 可以调整这些示例,以便将 Dataverse Web API 与本快速入门中所示的信息一起使用。
Web 技术 了解 HTML、JavaScript 和 CSS 需要了解本快速入门的工作原理。 了解如何 使用 JavaScript 发出网络请求 至关重要。

注册 SPA 应用程序

此步骤首先是因为如果无法注册应用,则无法完成本快速入门。

以下任何特权Microsoft Entra角色包括所需的权限:

配置应用程序时,需要一个应用程序(客户端)ID 和Microsoft Entra租户的 ID。 为应用程序选择描述性名称,以便人们知道为应用程序创建的内容。

注册应用

可以使用以下任一选项注册应用程序:

使用Microsoft Entra 管理中心创建单租户 SPA 应用注册、配置其重定向 URI、复制应用程序和租户 ID 以及添加 Dataverse user_impersonation 权限。

创建应用程序注册

  1. 登录到 Microsoft Entra 管理中心

  2. 如果有权访问多个租户,请使用顶部菜单中的 “设置” 图标切换到要从 “目录 + 订阅 ”菜单注册应用程序的租户。

  3. 浏览到 应用注册 并选择新注册

  4. 输入应用程序的名称,例如 Dataverse Web API Quickstart SPA

  5. 对于 支持的帐户类型,请在“ 选择可以使用此应用程序或访问此 API 的帐户类型”下, 仅选择单租户 - <租户名称>

  6. 对于 重定向 URI(可选)

    1. 对于“选择平台”,请选择“单页应用程序”(SPA)。
    2. 输入 http://localhost:1234/redirect.html 为值。
  7. 选择 “注册 ”以保存更改。

  8. 在创建的应用注册的窗口中,在 “概述 ”选项卡的 “概要”下,可以找到以下值:

    • 应用程序(客户端)ID
    • 目录(租户)ID
  9. 复制这些值,因为在 创建 .env 文件时 需要使用环境变量。

添加 Dataverse user_impersonation 权限

  1. “管理 ”区域中,选择 API 权限
  2. 选择“添加权限”。
  3. “请求 API 权限 ”浮出控件中,选择 组织使用的 API 选项卡。
  4. 键入“Dataverse”以查找应用程序(客户端)ID 00000007-0000-0000-c000-000000000000
  5. 选择 Dataverse 应用程序。
  6. “选择权限”中,选择 user_impersonation
  7. 选择添加权限

注释

如果没有为公司创建应用注册的权限,请通过Power Apps开发人员计划获取自己的租户。

安装 Node.js

  1. 转到 “下载 Node.js

  2. 为操作系统(Windows、macOS 或 Linux)选择适当的安装程序并下载它。

  3. 运行安装程序。 请确保接受默认选项: 安装 npm,这是建议用于 Node.js的包管理器。

  4. 通过打开终端或命令提示符、键入这些命令并按 Enter 来验证安装。

    • node -v
    • npm -v

    你会看到如下所示的输出:

    PS C:\Users\you> node -v
    v24.19.0
    PS C:\Users\you> npm -v
    11.17.0
    PS C:\Users\you>
    

创建项目

注释

若要跳过这些步骤,请克隆或下载 PowerApps-Samples 存储库。 这些步骤的完整应用程序在 /dataverse/webapi/JS/quickspa 上提供。 按照自述文件中的说明操作。

本部分将指导你从 npm 安装依赖项、创建文件夹结构以及打开Visual Studio Code。

  1. 将终端窗口打开到要在其中创建项目的位置。 有关这些说明,请使用 C:\projects

  2. 键入以下命令,然后按 Enter 运行每个命令:

    命令 Action
    mkdir quickspa 创建名为 quickspa 的新文件夹。
    cd quickspa 移动到新 quickspa 文件夹中。
    npm install --save-dev parcel 安装地块并初始化项目。
    npm install @azure/msal-browser 安装 MSAL.js 库。
    npm install dotenv 安装 dotenv 以访问存储可能敏感的配置数据的环境变量。
    mkdir src 在以下步骤中为应用添加 HTML、JS 和 CSS 文件时,请创建一个 src 文件夹。
    code . 在文件夹的quickspa上下文中打开Visual Studio Code。

项目在Visual Studio Code资源管理器中应如下所示:

在添加文件之前,Visual Studio Code中新的 quickspa 项目的屏幕截图。

注释

Visual Studio Code可能会显示提示:受限模式适用于安全代码浏览。信任此文件夹以启用所有功能。选择“管理”并选择信任该文件夹。 详细了解工作区信任

创建 .env 文件

将配置数据存储在独立于代码的环境中是一种安全最佳做法。

  1. 在文件夹的quickspa根目录中创建一.env个名为的新文件。

  2. 粘贴“ 注册应用 ”中的值,以替换 CLIENT_ID 以下代码中的值和 TENANT_ID 值:

    # The environment this application will connect to.
    BASE_URL=https://<yourorg>.api.crm.dynamics.com
    # The registered Entra application id
    CLIENT_ID=11112222-bbbb-3333-cccc-4444dddd5555
    # The Entra tenant id
    TENANT_ID=aaaabbbb-0000-cccc-1111-dddd2222eeee
    # The SPA redirect URI included in the Entra application registration
    REDIRECT_URI=http://localhost:1234/redirect.html
    
  3. BASE_URL 值设置为要连接到的环境的 Web API URL 的 URL

注释

不要签入 .env 文件。 在 “创建 .gitignore ”文件中,将排除该文件。 但你可能想要使用占位符值创建 .env.example 文件,以便人们知道它应包含哪些数据。

创建 HTML 页面

本节中的说明介绍如何创建为 SPA 应用程序提供用户界面的 HTML 文件。

  1. 在名为 <a0/&a0> 的文件夹中创建一个新文件。

  2. 将此内容复制并粘贴到 index.html 页面:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Dataverse Web API JavaScript Quick Start</title>
        <link rel="stylesheet" href="styles/style.css" />
      </head>
      <body>
        <header>
          <h1>Dataverse Web API JavaScript Quick Start</h1>
          <button id="loginButton">Login</button>
          <button id="logoutButton" class="hidden">Logout</button>
        </header>
        <nav id="buttonContainer" class="disabled">
          <button id="whoAmIButton">WhoAmI</button>
        </nav>
        <main id="container"></main>
        <script type="module" src="scripts/index.js"></script>
      </body>
    </html>
    

此 HTML 提供以下元素:

元素 ID 元素类型 Description
loginButton 按钮 打开登录对话框。
logoutButton 按钮 打开注销对话框。 默认情况下隐藏。
buttonContainer nav 包含要求用户登录才能使用的按钮。 默认已禁用。
whoAmIButton 按钮 执行 WhoAmI 函数 以显示用户的 ID。
container main 可在其中向用户显示信息的区域。
脚本 index.js加载页面其余元素后加载文件。

创建重定向 HTML 页

MSAL Browser v5 引入了对跨源 -Opener-Policy (COOP) 标头提供的应用程序的支持,并且该更改需要重定向网桥页安全地将身份验证响应传递回主 SPA 窗口。 了解如何在 MSAL 浏览器中设置重定向桥页

  1. 在名为 <a0/&a0> 的文件夹中创建一个新文件。

  2. 将此内容复制并粘贴到 redirect.html 页面:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Signing in</title>
      </head>
      <body>
        <p>Processing authentication...</p>
        <script type="module">
          import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";
    
          broadcastResponseToMainFrame().catch((error) => {
            console.error("Error broadcasting authentication response:", error);
          });
        </script>
      </body>
    </html>
    

创建 JavaScript 脚本

此文件包含使页面动态 index.html 的所有逻辑。

  1. 在名为 <a0/&a0> 的文件夹中创建一个新文件夹。

  2. 在名为 <a0/&a0> 的文件夹中创建一个新文件。

  3. 将此内容复制并粘贴到 index.js 文件中:

    import { PublicClientApplication } from "@azure/msal-browser";
    import 'dotenv/config'
    
    // Load the environment variables from the .env file
    const config = {
       baseUrl: process.env.BASE_URL,
       clientId: process.env.CLIENT_ID,
       tenantId: process.env.TENANT_ID,
       redirectUri: process.env.REDIRECT_URI,
    };
    
    // Microsoft Authentication Library (MSAL) configuration
    const msalConfig = {
      auth: {
        clientId: config.clientId,
        authority: "https://login.microsoftonline.com/" + config.tenantId,
        redirectUri: config.redirectUri,
        postLogoutRedirectUri: config.redirectUri,
      },
      cache: {
        cacheLocation: "sessionStorage", // This configures where your cache will be stored
        storeAuthStateInCookie: true,
      },
    };
    
    // Create an instance of MSAL
    const msalInstance = new PublicClientApplication(msalConfig);
    
    // body/main element where messages are displayed
    const container = document.getElementById("container");
    
    // Event handler for login button
    async function logIn() {
      await msalInstance.initialize();
    
      if (!msalInstance.getActiveAccount()) {
        const request = {
          scopes: ["User.Read", config.baseUrl + "/user_impersonation"],
        };
        try {
          const response = await msalInstance.loginPopup(request);
          msalInstance.setActiveAccount(response.account);
    
          // Hide the loginButton so it won't get pressed twice
          document.getElementById("loginButton").style.display = "none";
    
          // Show the logoutButton
          const logoutButton = document.getElementById("logoutButton");
          logoutButton.innerHTML = "Logout " + response.account.name;
          logoutButton.style.display = "block";
          // Enable any buttons in the nav element
          document.getElementsByTagName("nav")[0].classList.remove("disabled");
        } catch (error) {
          let p = document.createElement("p");
          p.textContent = "Error logging in: " + error;
          p.className = "error";
          container.append(p);
        }
      } else {
        // Clear the active account and try again
        msalInstance.setActiveAccount(null);
        this.click();
      }
    }
    
    // Event handler for logout button
    async function logOut() {
      const activeAccount = await msalInstance.getActiveAccount();
      const logoutRequest = {
        account: activeAccount,
        mainWindowRedirectUri: config.redirectUri,
      };
    
      try {
        await msalInstance.logoutPopup(logoutRequest);
    
        document.getElementById("loginButton").style.display = "block";
    
        this.innerHTML = "Logout ";
        this.style.display = "none";
        document.getElementsByTagName("nav")[0].classList.remove("disabled");
      } catch (error) {
        console.error("Error logging out: ", error);
      }
    }
    
    /**
     * Retrieves an access token using MSAL (Microsoft Authentication Library).
     * Set as the getToken function for the DataverseWebAPI client in the login function.
     *
     * @async
     * @function getToken
     * @returns {Promise<string>} The access token.
     * @throws {Error} If token acquisition fails and is not an interaction required error.
     */
    async function getToken() {
      const request = {
        scopes: [config.baseUrl + "/.default"],
      };
    
      try {
        const response = await msalInstance.acquireTokenSilent(request);
        return response.accessToken;
      } catch (error) {
        if (error instanceof msal.InteractionRequiredAuthError) {
          const response = await msalInstance.acquireTokenPopup(request);
          return response.accessToken;
        } else {
          console.error(error);
          throw error;
        }
      }
    }
    
    // Add event listener to the login button
    document.getElementById("loginButton").onclick = logIn;
    
    // Add event listener to the logout button
    document.getElementById("logoutButton").onclick = logOut;
    
    /// Function to get the current user's information
    /// using the WhoAmI function of the Dataverse Web API.
    async function whoAmI() {
      const token = await getToken();
      const request = new Request(config.baseUrl + "/api/data/v9.2/WhoAmI", {
        method: "GET",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
          "OData-Version": "4.0",
          "OData-MaxVersion": "4.0",
        },
      });
      // Send the request to the API
      const response = await fetch(request);
      // Handle the response
      if (!response.ok) {
        throw new Error("Network response was not ok: " + response.statusText);
      }
      // Successfully received response
      return await response.json();
    }
    
    // Add event listener to the whoAmI button
    document.getElementById("whoAmIButton").onclick = async function () {
      // Clear any previous messages
      container.replaceChildren();
      try {
        const response = await whoAmI();
        let p1 = document.createElement("p");
        p1.textContent =
          "Congratulations! You connected to Dataverse using the Web API.";
        container.append(p1);
        let p2 = document.createElement("p");
        p2.textContent = "User ID: " + response.UserId;
        container.append(p2);
      } catch (error) {
        let p = document.createElement("p");
        p.textContent = "Error fetching user info: " + error;
        p.className = "error";
        container.append(p);
      }
    };
    

index.js 脚本包含以下常量和函数:

Item Description
config 包含Microsoft 身份验证库(MSAL)配置使用的数据。
msalConfig Microsoft 身份验证库 (MSAL) 配置。
msalInstance MSAL PublicClientApplication 实例。
container 显示消息的元素。
getToken 使用 MSAL 检索访问令牌。
logIn 登录按钮的事件侦听器函数。 打开“选择帐户”对话框。
logOut 注销按钮的事件侦听器函数。 打开“选择帐户”对话框。
whoAmI 调用 WhoAmI 函数 以从 Dataverse 检索数据的异步函数。
whoAmIButton 事件侦听器 调用 whoAmI 函数并管理 UI 更改以显示消息的函数。

创建 CSS 页

级联样式表(CSS)文件使 HTML 页面更具吸引力,当控件被禁用或隐藏时,控件将更具吸引力。

  1. 在文件夹中创建一stylessrc个名为的新文件夹。

  2. style.css 文件夹中,创建名为 styles 的新文件。

  3. 将此文本复制并粘贴到 style.css 文件中:

    .disabled {
       pointer-events: none;
       opacity: 0.5;
       /* Optional: to visually indicate the element is disabled */
    }
    
    .hidden {
       display: none;
    }
    
    .error {
       color: red;
    }
    
    .expectedError {
       color: green;
    }
    
    body {
       font-family: 'Roboto', sans-serif;
       font-size: 16px;
       line-height: 1.6;
       color: #333;
       background-color: #f9f9f9;
    }
    
    h1,
    h2,
    h3 {
       color: #2c3e50;
    }
    
    button {
       background-color: #3498db;
       color: #fff;
       border: none;
       padding: 10px 20px;
       border-radius: 5px;
       box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
       transition: background-color 0.3s ease;
       margin: 5px;
       /* Adjust the value as needed */
    }
    
    button:hover {
       background-color: #2980b9;
    }
    
    header {
       padding-bottom: 10px;
       /* Adjust the value as needed */
    }
    

创建 .gitignore 文件

使用源代码管理签入应用时,添加文件 .gitignore 可防止签入指定的文件和文件夹。

  1. 创建名为 的文件 .gitignore

  2. 添加以下内容:

    .parcel-cache
    dist
    node_modules
    .env
    

.parcel-cache首次运行应用时,将显示和dist文件夹。

不签入 .env 文件是一种安全最佳做法。 可能需要使用占位符值签入占位符 .env.sample 文件。

项目在Visual Studio Code资源管理器中应如下所示:

添加文件后Visual Studio Code quickspa 项目的屏幕截图。

配置 package.json 文件

文件 package.json 应类似于以下示例:

{
  "devDependencies": {
    "parcel": "^2.14.1",
  },
  "dependencies": {
    "@azure/msal-browser": "^5.17.3",
    "dotenv": "^17.4.2"
  }
}

scripts以下项后面dependencies添加和@parcel/resolver-default项:

  "dependencies": {
    "@azure/msal-browser": "^5.17.3",
    "dotenv": "^17.4.2"
  },
  "scripts": {
    "start": "parcel src/index.html src/redirect.html"
  },
  "@parcel/resolver-default": {
    "packageExports": true
  }

@parcel/resolver-default 配置启用地块的包导出解析行为。 必须正确解析此配置,该配置 @azure/msal-browser使用包元数据中的导出字段。 根据使用的包裹版本,此设置可能不是必需的,但保留此设置以确保与示例的 MSAL 依赖项兼容。

此配置使你能够在下一步中使用 npm start 来启动应用程序。

试用

  1. 在Visual Studio Code中,打开终端窗口。

  2. 键入 npm start 并按 Enter

    注释

    当项目首次初始化时,你可能会看到写入终端的一些输出。 此输出是包裹安装一些更多 Node 模块,以防止使用 dotenv 时出现问题。 查看并 package.json 看到添加到其中的 devDependencies一些新项。

    你将看到输出到如下所示的终端:

    Server running at http://localhost:1234
    Built in 1.08s
    
  3. Ctrl +单击 http://localhost:1234 链接以打开浏览器。

  4. 在浏览器中,选择 “登录 ”按钮。

    此时会打开“ 登录到帐户 ”对话框。

  5. 在“ 登录帐户 ”对话框中,选择有权访问 Dataverse 的帐户。

    首次使用新应用程序(客户端)ID 值访问 Dataverse 时,会看到此 请求的权限 对话框:

    Dataverse Web API 快速入门应用程序的“权限请求”对话框的屏幕截图。

  6. 请求的权限对话框中选择“接受”。

  7. 选择 “WhoAmI ”按钮。

    消息恭喜!使用 Web API 连接到 Dataverse。将显示 WhoAmIResponseUserId 复杂类型的值。

Troubleshooting

本部分包含运行本快速入门时可能会遇到的错误。

注释

如果在完成本快速入门中的步骤时遇到问题,请尝试克隆或下载 PowerApps-Samples 存储库。 这些步骤的完整应用程序在 /dataverse/webapi/JS/quickspa 上提供。 按照自述文件中的说明操作。 如果这不起作用,请创建引用此示例quickspa应用程序的GitHub问题。

租户中不存在所选用户帐户

选择的帐户不属于与已注册应用程序相同的Microsoft Entra租户时,在“选取帐户”对话框中收到此错误:

Selected user account does not exist in tenant '{Your tenant name}' and cannot access the application '{Your application ID}' in that tenant. The account needs to be added as an external user in the tenant first. Please use a different account.

解决方法:确保选择正确的用户。

后续步骤

尝试使用客户端 JavaScript 的其他示例。

通过了解服务文档详细了解 Dataverse Web API 功能。