by WebSurfer
2. July 2024 15:36
React アプリから、トークン (JWT) ベースの認証が必要な ASP.NET Core Web API にクロスドメインでアクセスしてデータを取得するサンプルを作ってみました。
先の記事「Blazor WASM から ASP.NET Core Web API を呼び出し」の Blazor WASM アプリを React アプリに代えただけです。
Web API アプリの作り方は先の記事に詳しく書きましたのでそちらを見てください。
React アプリは、Visual Studio 2022 v17.10.3 のテンプレートを使って「新しいプロジェクトの作成」ダイアログで「React and ASP.NET Core」のテンプレートを選び、ターゲットフレームワーク .NET 8.0 で作成したものです。
その中の .client プロジェクトの src\App.jsx ファイルの populateWeatherData メソッドに以下のように手を加えて、先の記事の Web API を呼び出すようにしています。その結果が上の画像です。
import { useEffect, useState } from 'react';
import './App.css';
function App() {
const [forecasts, setForecasts] = useState();
useEffect(() => {
populateWeatherData();
}, []);
const contents = forecasts === undefined
? <p><em>Loading... Please refresh once the ASP.NET backend has started.</em></p>
: <table className="table table-striped" aria-labelledby="tabelLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{forecasts.map(forecast =>
<tr key={forecast.date}>
<td>{forecast.date}</td>
<td>{forecast.temperatureC}</td>
<td>{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
)}
</tbody>
</table>;
return (
<div>
<h1 id="tabelLabel">Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
async function populateWeatherData() {
// 自動生成されたコード
//const response = await fetch('weatherforecast');
//const data = await response.json();
//setForecasts(data);
// ・・・を、以下のように書き換える
const tokenUrl = "https://localhost:44366/api/token";
const forecastUrl = "https://localhost:44366/WeatherForecast";
// 送信する ID とパスワード
const credentials = {
Username: "oz@mail.example.com",
Password: "myPassword"
};
const params = {
method: "POST",
body: JSON.stringify(credentials),
headers: { 'Content-Type': 'application/json' }
};
// ID とパスワードを POST 送信してトークンを取得
const responseToken = await fetch(tokenUrl, params);
if (responseToken.ok) {
const data = await responseToken.json();
let token = data.token;
// 取得したトークンを Authorization ヘッダに含めて
// 天気予報データを GET 要求し、応答を表示
const responseForecast = await fetch(forecastUrl,
{ headers: { 'Authorization': `Bearer ${token}` } });
if (responseForecast.ok) {
const data = await responseForecast.json();
setForecasts(data);
}
}
}
}
export default App;
不可解なのが、上のコードでは Web API が 2 回呼ばれることです。ネットの記事を見ると、上のコード例のように useState の第 2 引数に空の配列 [] を設定した場合は初回レンダリングの際の 1 回だけしか第 1 引数に設定した populateWeatherData() は実行されないそうですが、Fiddler で要求・応答をキャプチャしてみると 2 回呼び出しが行われていました。
ネットの記事をいろいろ探したところ、React の useEffect のドキュメント(これが公式?)の「外部システムへの接続」のセクションに以下の記述を見つけました。
"バグを見つけ出すために、開発中には React はセットアップとクリーンアップを、セットアップの前に 1 回余分に実行します。これは、エフェクトのロジックが正しく実装されていることを確認するストレステストです。"
この記事以外に 2 回呼ばれる理由と結びつくことが書かれた記事は見つけられませんでした。本番環境で検証するとかすれば分かるのかもしれませんが、今はその気力がありません。(汗) 調べる気力が出てきて、何か分かったら追記します。
ちなみに、React には「クラスコンポーネント」と「関数コンポーネント」というものがあるそうで、上のサンプルコードは「関数コンポーネント」を使っています。それゆえ React フックと呼ばれる useEffect を使わざるを得ないようです。
一方、古い Visual Studio のテンプレートで作った React アプリは「クラスコンポーネント」を使っており、componentDidMount で Web API の呼び出しを行っています。その場合は 2 回 Web API が呼ばれることは無いです。