教程:使用 JavaScript 调用 ASP.NET Core Web API

作者:Rick Anderson

此教程介绍如何通过 Fetch API 使用 JavaScript 调用 ASP.NET Core Web API。

有关 ASP.NET Core 2.2,请参阅 2.2 版本的使用 JavaScript 调用 Web API

先决条件

使用 JavaScript 调用 Web API

在本部分中,将添加一个 HTML 页面,其中包含用于创建和管理待办事项的窗体。 事件处理程序会附加到页面上的元素。 事件处理程序导致对 Web API 的操作方法发出 HTTP 请求。 Fetch API 的 fetch 函数可启动每个 HTTP 请求。

fetch 函数可返回 Promise 对象,该对象包含表示为 Response 对象的 HTTP 响应。 常见模式是通过调用 Response 对象上的 json 函数来提取 JSON 响应正文。 JavaScript 会使用 Web API 响应的详细信息来更新页面。

最简单的 fetch 调用接受一个表示路由的参数。 第二个参数(称为 init 对象)是可选的。 init 用于配置 HTTP 请求。

  1. 配置应用提供静态文件启用默认文件映射。 在 Startup.cs 的 Configure 方法中需要以下突出显示的代码 :

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseDefaultFiles();
        app.UseStaticFiles();
    
        app.UseHttpsRedirection();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  2. 在项目根中创建 wwwroot 文件夹 。

  3. 在 wwwroot 文件夹中创建一个 js 文件夹 。

  4. 将一个名为 index.html 的 HTML 文件添加到 wwwroot 文件夹 。 将 index.html 的内容替换为以下标记 :

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>To-do CRUD</title>
        <link rel="stylesheet" href="css/site.css" />
    </head>
    <body>
        <h1>To-do CRUD</h1>
        <h3>Add</h3>
        <form action="javascript:void(0);" method="POST" onsubmit="addItem()">
            <input type="text" id="add-name" placeholder="New to-do">
            <input type="submit" value="Add">
        </form>
    
        <div id="editForm">
            <h3>Edit</h3>
            <form action="javascript:void(0);" onsubmit="updateItem()">
                <input type="hidden" id="edit-id">
                <input type="checkbox" id="edit-isComplete">
                <input type="text" id="edit-name">
                <input type="submit" value="Save">
                <a onclick="closeInput()" aria-label="Close">&#10006;</a>
            </form>
        </div>
    
        <p id="counter"></p>
    
        <table>
            <tr>
                <th>Is Complete?</th>
                <th>Name</th>
                <th></th>
                <th></th>
            </tr>
            <tbody id="todos"></tbody>
        </table>
    
        <script src="js/site.js" asp-append-version="true"></script>
        <script type="text/javascript">
            getItems();
        </script>
    </body>
    </html>
    
  5. 将名为 site.css 的 CSS 文件添加到 wwwroot/css 文件夹 。 将 site.js 的内容替换为以下类型:

    input[type='submit'], button, [aria-label] {
        cursor: pointer;
    }
    
    #editForm {
        display: none;
    }
    
    table {
        font-family: Arial, sans-serif;
        border: 1px solid;
        border-collapse: collapse;
    }
    
    th {
        background-color: #f8f8f8;
        padding: 5px;
    }
    
    td {
        border: 1px solid;
        padding: 5px;
    }
    
  6. 将一个名为 site.js 的 JavaScript 文件添加到 wwwroot/js 文件夹。 将 site.js 的内容替换为以下代码:

    const uri = 'api/TodoItems';
    let todos = [];
    
    function getItems() {
      fetch(uri)
        .then(response => response.json())
        .then(data => _displayItems(data))
        .catch(error => console.error('Unable to get items.', error));
    }
    
    function addItem() {
      const addNameTextbox = document.getElementById('add-name');
    
      const item = {
        isComplete: false,
        name: addNameTextbox.value.trim()
      };
    
      fetch(uri, {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)
      })
        .then(response => response.json())
        .then(() => {
          getItems();
          addNameTextbox.value = '';
        })
        .catch(error => console.error('Unable to add item.', error));
    }
    
    function deleteItem(id) {
      fetch(`${uri}/${id}`, {
        method: 'DELETE'
      })
      .then(() => getItems())
      .catch(error => console.error('Unable to delete item.', error));
    }
    
    function displayEditForm(id) {
      const item = todos.find(item => item.id === id);
      
      document.getElementById('edit-name').value = item.name;
      document.getElementById('edit-id').value = item.id;
      document.getElementById('edit-isComplete').checked = item.isComplete;
      document.getElementById('editForm').style.display = 'block';
    }
    
    function updateItem() {
      const itemId = document.getElementById('edit-id').value;
      const item = {
        id: parseInt(itemId, 10),
        isComplete: document.getElementById('edit-isComplete').checked,
        name: document.getElementById('edit-name').value.trim()
      };
    
      fetch(`${uri}/${itemId}`, {
        method: 'PUT',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(item)
      })
      .then(() => getItems())
      .catch(error => console.error('Unable to update item.', error));
    
      closeInput();
    
      return false;
    }
    
    function closeInput() {
      document.getElementById('editForm').style.display = 'none';
    }
    
    function _displayCount(itemCount) {
      const name = (itemCount === 1) ? 'to-do' : 'to-dos';
    
      document.getElementById('counter').innerText = `${itemCount} ${name}`;
    }
    
    function _displayItems(data) {
      const tBody = document.getElementById('todos');
      tBody.innerHTML = '';
    
      _displayCount(data.length);
    
      const button = document.createElement('button');
    
      data.forEach(item => {
        let isCompleteCheckbox = document.createElement('input');
        isCompleteCheckbox.type = 'checkbox';
        isCompleteCheckbox.disabled = true;
        isCompleteCheckbox.checked = item.isComplete;
    
        let editButton = button.cloneNode(false);
        editButton.innerText = 'Edit';
        editButton.setAttribute('onclick', `displayEditForm(${item.id})`);
    
        let deleteButton = button.cloneNode(false);
        deleteButton.innerText = 'Delete';
        deleteButton.setAttribute('onclick', `deleteItem(${item.id})`);
    
        let tr = tBody.insertRow();
        
        let td1 = tr.insertCell(0);
        td1.appendChild(isCompleteCheckbox);
    
        let td2 = tr.insertCell(1);
        let textNode = document.createTextNode(item.name);
        td2.appendChild(textNode);
    
        let td3 = tr.insertCell(2);
        td3.appendChild(editButton);
    
        let td4 = tr.insertCell(3);
        td4.appendChild(deleteButton);
      });
    
      todos = data;
    }
    

可能需要更改 ASP.NET Core 项目的启动设置,以便对 HTML 页面进行本地测试:

  1. 打开 Properties\launchSettings.json。
  2. 删除 launchUrl 以便在项目的默认文件 index.html 强制打开应用。

此示例调用 Web API 的所有 CRUD 方法。 下面说明 Web API 请求。

获取待办事项的列表

在以下代码中,会将 HTTP GET 请求发送到 api/TodoItems 路由:

fetch(uri)
  .then(response => response.json())
  .then(data => _displayItems(data))
  .catch(error => console.error('Unable to get items.', error));

当 Web API 返回成功状态的代码时,将调用 _displayItems 函数。 将 _displayItems 接受的数组参数中的每个待办事项添加到具有“编辑”和“删除”按钮的表中。 如果 Web API 请求失败,则会将错误记录到浏览器的控制台中。

添加待办事项

在以下代码中:

  • 声明 item 变量来构造待办事项的对象文字表示形式。
  • 使用以下选项来配置提取请求:
    • method—指定 POST HTTP 操作谓词。
    • body—指定请求正文的 JSON 表示形式。 通过将存储在 item 中的对象文字传递到 JSON.stringify 函数来生成 JSON。
    • headers—指定 AcceptContent-Type HTTP 请求标头。 将两个标头都设置为 application/json,以便分别指定接收和发送的媒体类型。
  • 将 HTTP POST 请求发送到 api/TodoItems 路由。
function addItem() {
  const addNameTextbox = document.getElementById('add-name');

  const item = {
    isComplete: false,
    name: addNameTextbox.value.trim()
  };

  fetch(uri, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(item)
  })
    .then(response => response.json())
    .then(() => {
      getItems();
      addNameTextbox.value = '';
    })
    .catch(error => console.error('Unable to add item.', error));
}

当 Web API 返回成功状态的代码时,将调用 getItems 函数来更新 HTML 表。 如果 Web API 请求失败,则会将错误记录到浏览器的控制台中。

更新待办事项

更新待办事项类似于添加待办事项;但是,二者存在两个重大差异:

  • 路由的后缀为待更新项的唯一标识符。 例如,api/TodoItems/1。
  • 正如 method 选项所示,HTTP 操作谓词是 PUT。
fetch(`${uri}/${itemId}`, {
  method: 'PUT',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));

删除待办事项

若要删除待办事项,请将请求的 method 选项设置为 DELETE 并指定该项在 URL 中的唯一标识符。

fetch(`${uri}/${id}`, {
  method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));

前进到下一个教程,了解如何生成 Web API 帮助页: