Tạo một dự án Web API với JavaScript

Trong phần này chúng ta sẽ thêm một trang HTML đến dự án chứa các form tạo và quản lý các công việc phải làm (to-do items). Các trình xử lý sự kiện (các phương thức) được đính kèm đến các phần tử trang. Các trình xử lý sự kiện tạo ra các yêu cầu HTTP đến các phương thức hành động của Web API. Hàm fetch của Fetch API khởi tạo mỗi yêu cầu HTTP.

1. Cấu hình ứng dụng đến các tập tin tĩnh (serve static files – https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.staticfileextensions.usestaticfiles?view=aspnetcore-6.0#microsoft-aspnetcore-builder-staticfileextensions-usestaticfiles(microsoft-aspnetcore-builder-iapplicationbuilder) ) và cho phép ánh xạ tập tin mặc định (enable default file mapping -https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.defaultfilesextensions.usedefaultfiles?view=aspnetcore-6.0#microsoft-aspnetcore-builder-defaultfilesextensions-usedefaultfiles(microsoft-aspnetcore-builder-iapplicationbuilder) ) bằng cách mở tập tin Startup.cs và cập nhật nội dung phương thức 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. Tạo thư mục wwwroot bằng cách nhấn chuột phải vào tên dự án TodoApi và chọn Add > New Folder, đổi tên thư mục là wwwroot. Trong thư mục wwwroot thêm hai thư mục con là css (chứa các tập tin css) và js (chứa các tập tin css) và tập tin index.html (Add > New Item > HTML Page). Cấu trúc thư mục wwwroot như sau:

Thay đổi nội dung mặc định của tập tin index.html bằng nội dung sau:

<!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">✖</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>

    http:&#047;&#047;js/site.js

    <script type="text/javascript">

        getItems();

    </script>

</body>

</html>

Trong thư mục wwwroot / css (Nhấn chuột phải thư mục css chọn Add > New Item > Style Sheet) thêm tập tin site.css và thay nội dung mặc định bằng nội dung sau:

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;

}

Trong thư mục wwwroot / js thêm tập tin site.js ((Nhấn chuột phải thư mục js chọn Add > New Item > JavaScript File)) và thay nội dung mặc định bằng nội dung sau:

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;

}

Mặc định khi thực thi, ứng dụng sẽ thực thi controller WeatherForecast. Chúng ta sẽ thay đổi cấu hình để ứng dụng thực thi trang index.html từ thư mục wwwroot bằng cách mở tập tin launchSettings.json từ thư mục Properties và xóa thuộc tính launchUrl từ IIS Express:

Thực thi ứng dụng:

Nhận danh sách các to-do item

Trong đoạn mã sau, một yêu cầu HTTP GET được gửi đến định tuyến api/todoitems

fetch(uri)

  .then(response => response.json())

  .then(data => _displayItems(data))

  .catch(error => console.error('Unable to get items.', error));

Khi web API trả về mã trạng thái thành công, hàm _displayItems được gọi. Mỗi to-do item trong tham số mảng được chấp nhận bởi _displayItems được thêm đến một bảng (table) với các nút Edit, Delete. Nếu web api thất bại, một thông điệp lỗi được gửi đến cosole của trình duyệt.

Thêm một to-do item

Trong đoạn mã sau, một yêu cầu HTTP POST được gửi đến định tuyến 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));

}

Đoạn mã trên có một số chú ý:

  • Biến item được khai báo để cấu trúc đối tượng thể hiện to-do item
  • Một yêu cầu Fetch được cấu hình với các tùy chọn sau:
    • method: xác định hành động HTTP POST
    • body: xác định thể hiện JSON của yêu cầu
    • headers: xác định giá trị cho các thuộc tính Accept và Content-Type là application/json

Khi web API trả về mã trạng thái thành công, hàm getItems được gọi để cập nhật bảng HTML. Nếu web api thất bại, một thông điệp lỗi được gửi đến cosole của trình duyệt.

Cập nhật một to-do item

Đoạn mã cập nhật:

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

Cập nhật tương tự thêm một to-do item nhưng có hai khác biệt:

  • Định tuyến được kèm theo chỉ danh (Id) của to-do item được cập nhật. Ví dụ: api/todoitems/1
  • Tùy chọn method của Fetch xác định là HTTP PUT

Xóa một to-do item

Đoạn mã xóa:

fetch(`${uri}/${id}`, {

  method: 'DELETE'

})

.then(() => getItems())

.catch(error => console.error('Unable to delete item.', error));

Khi xóa, định tuyến được kèm theo chỉ danh (Id) của to-do item được xóa và tùy chọn method của Fetch là HTTP DELETE.