チュートリアル: JavaScript を使用して ASP.NET Core Web API を呼び出す

作成者: Rick Anderson

このチュートリアルでは、Fetch API を使用して JavaScript で ASP.NET Core Web API を呼び出す方法について説明します。

必須コンポーネント

JavaScript で Web API を呼び出す

このセクションでは、To Do アイテムを作成および管理するためのフォームが含まれる HTML ページを追加します。 イベント ハンドラーを、ページ上の要素にアタッチします。 イベント ハンドラーによって、Web API のアクション メソッドに HTTP 要求が送信されます。 Fetch API の fetch 関数により、各 HTTP 要求が開始されます。

fetch 関数からは Promise オブジェクトが返され、このオブジェクトには Response オブジェクトとして表された HTTP 応答が含まれます。 一般的なパターンでは、Response オブジェクトに対して json 関数を呼び出して、JSON 応答の本文を抽出します。 JavaScript により、Web API の応答からの詳細を使ってページが更新されます。

fetch の最も簡単な呼び出しでは、ルートを表す 1 つのパラメーターが受け付けられます。 init オブジェクトである 2 番目のパラメーターは省略可能です。 init は、HTTP 要求を構成するために使用されます。

  1. 静的ファイルを提供し既定のファイル マッピングを有効にするように、アプリを構成します。 Program.cs に、次の強調表示されたコードが必要です。
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));

var app = builder.Build();

if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
  1. プロジェクト ルートに wwwroot フォルダーを作成します。

  2. wwwroot フォルダー内に css フォルダーを作成します。

  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.css の内容を、次のスタイルに置き換えます。

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

ローカルで HTML ページをテストするために、ASP.NET Core プロジェクトの起動設定への変更が要求される場合があります。

  1. Properties\launchSettings.json を開きます。
  2. アプリが強制的に index.html (プロジェクトの既定ファイル) で開くようにするには、launchUrl プロパティを削除します。

このサンプルでは、Web API のすべての CRUD メソッドを呼び出します。 以下では、Web API 要求について説明します。

To Do アイテムのリストの取得

次のコードでは、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 によって受け入れられた配列パラメーターの各 To Do アイテムが、[Edit] ボタンと [Delete] ボタンのあるテーブルに追加されます。 Web API 要求が失敗した場合は、ブラウザーのコンソールにエラーが記録されます。

To Do アイテムの追加

次のコードの内容は以下のとおりです。

  • item 変数は、To Do アイテムのオブジェクト リテラル表現を構築するために宣言されています。
  • フェッチ要求は、次のオプションで構成されます。
    • method - POST HTTP アクション動詞が指定されています。
    • body によって、要求本文の JSON 表現が指定されています。 JSON は、item に格納されているオブジェクト リテラルを JSON.stringify 関数に渡すことによって生成されます。
    • headers - Accept および Content-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 要求が失敗した場合は、ブラウザーのコンソールにエラーが記録されます。

To Do アイテムの更新

To Do アイテムの更新は追加と似ていますが、2 つの大きな違いがあります。

  • ルートに、更新するアイテムの一意の識別子がサフィックスとして付けられます。 たとえば、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));

To Do アイテムの削除

To Do アイテムを削除するには、要求の method オプションを DELETE に設定し、URL でアイテムの一意の識別子を指定します。

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

Web API のヘルプ ページを生成する方法については、次のチュートリアルに進んでください。

必須コンポーネント

JavaScript で Web API を呼び出す

このセクションでは、To Do アイテムを作成および管理するためのフォームが含まれる HTML ページを追加します。 イベント ハンドラーを、ページ上の要素にアタッチします。 イベント ハンドラーによって、Web API のアクション メソッドに HTTP 要求が送信されます。 Fetch API の fetch 関数により、各 HTTP 要求が開始されます。

fetch 関数からは Promise オブジェクトが返され、このオブジェクトには Response オブジェクトとして表された HTTP 応答が含まれます。 一般的なパターンでは、Response オブジェクトに対して json 関数を呼び出して、JSON 応答の本文を抽出します。 JavaScript により、Web API の応答からの詳細を使ってページが更新されます。

fetch の最も簡単な呼び出しでは、ルートを表す 1 つのパラメーターが受け付けられます。 init オブジェクトである 2 番目のパラメーターは省略可能です。 init は、HTTP 要求を構成するために使用されます。

  1. 静的ファイルを提供し既定のファイル マッピングを有効にするように、アプリを構成します。 Startup.csConfigure メソッドでは、次の強調表示されたコードが必要です。

    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 フォルダー内に css フォルダーを作成します。

  4. wwwroot フォルダー内に js フォルダーを作成します。

  5. 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>
    
  6. site.css という名前の CSS ファイルを wwwroot/css フォルダーに追加します。 site.css の内容を、次のスタイルに置き換えます。

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

ローカルで HTML ページをテストするために、ASP.NET Core プロジェクトの起動設定への変更が要求される場合があります。

  1. Properties\launchSettings.json を開きます。
  2. アプリが強制的に index.html (プロジェクトの既定ファイル) で開くようにするには、launchUrl プロパティを削除します。

このサンプルでは、Web API のすべての CRUD メソッドを呼び出します。 以下では、Web API 要求について説明します。

To Do アイテムのリストの取得

次のコードでは、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 によって受け入れられた配列パラメーターの各 To Do アイテムが、[Edit] ボタンと [Delete] ボタンのあるテーブルに追加されます。 Web API 要求が失敗した場合は、ブラウザーのコンソールにエラーが記録されます。

To Do アイテムの追加

次のコードの内容は以下のとおりです。

  • item 変数は、To Do アイテムのオブジェクト リテラル表現を構築するために宣言されています。
  • フェッチ要求は、次のオプションで構成されます。
    • method - POST HTTP アクション動詞が指定されています。
    • body によって、要求本文の JSON 表現が指定されています。 JSON は、item に格納されているオブジェクト リテラルを JSON.stringify 関数に渡すことによって生成されます。
    • headers - Accept および Content-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 要求が失敗した場合は、ブラウザーのコンソールにエラーが記録されます。

To Do アイテムの更新

To Do アイテムの更新は追加と似ていますが、2 つの大きな違いがあります。

  • ルートに、更新するアイテムの一意の識別子がサフィックスとして付けられます。 たとえば、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));

To Do アイテムの削除

To Do アイテムを削除するには、要求の method オプションを DELETE に設定し、URL でアイテムの一意の識別子を指定します。

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

Web API のヘルプ ページを生成する方法については、次のチュートリアルに進んでください。