WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

React アプリから ASP.NET Core Web API を呼び出し

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 が呼ばれることは無いです。

Tags: , , ,

React

CORS 非対応の場合のエラーメッセージ(その2)

by WebSurfer 4. June 2024 12:47

先の記事「CORS 非対応の場合のエラーメッセージ」の続きで、fetch メソッドで指定した URL が間違っていた場合どうなるかを調べたので、備忘録として書いておきます。

URL が間違っているとサーバーからは HTTP 404 Not Found 応答が返ってきますが、それがどのように表示されるかが書いておきたかったことです。

ちなみに、404 応答というのは、(1) URL のホスト名は正しく名前解決されブラウザからの要求は Web サーバーに届いた、(2) 要求を受け取った Web サーバーは URL に指定されたリソースを探したが見つからなかった、(3) Web サーバーは見つからなかったという応答を返した・・・ということです。

検証に使ったのは、先の記事「Web API に CORS 実装 (CORE)」に書いた ASP.NET Core Web API アプリで、Web API の CORS を無効にして、ドメインが異なる MVC アプリから fetch API を使って Web API に要求を出します。その際、正しい URL は https://localhost:44371/api/values とすべきところを、values ⇒ valuesx としました。

実は、CORS を無効にしたら 404 応答は返ってこないと思い違いをしていたのですが、そうではなかったです。バックエンドが CORS 非対応でもフロントエンドから要求は出て、URL が間違っているため指定されたリソースが見つからない場合は 404 応答が返ってきます。

以下に、「CORS 非対応 URL 誤」「CORS 非対応 URL 正」「CORS 有効 URL 誤」の 3 つのケースで、Chrome のディベロッパーツールの Console に表示されるエラーメッセージと、Fiddler で要求・応答をキャプチャした画像を貼っておきます。

(1) CORS 非対応 URL 誤

CORS 非対応でもブラウザから要求は出ます。URL が間違っているので Web サーバーは 404 応答を返します。それを受けたブラウザでは、まず応答ヘッダに Access-Control-Allow-Origin が無いということで CORS 関係のエラーメッセージを出しています。次に、404 応答なのでそのエラーメッセージ、最後に Failed to fetch (fetch 失敗) というエラーメッセージを出しています。

CORS 非対応 URL 誤

Fiddler の画像です。404 応答が返ってきています。バックエンドが CORS 非対応なので応答ヘッダには Access-Control-Allow-Origin は含まれていません。

CORS 非対応 URL 誤


(2) CORS 非対応 URL 正

上の (1) のケースから URL を正しいものにした場合の結果です。URL は正しいので当然 404 応答ではなく 200 応答が返ってきます。JSON 文字列も正しく応答コンテンツに含まれて返ってきます。しかし、応答ヘッダに Access-Control-Allow-Origin が無いので fetch メソッドでのデータの取得に失敗しています。

CORS 非対応 URL 正

Fiddler の画像です。URL は正しいので 200 応答が返ってきています。応答の TextView (コンテンツ) を見ると期待通り JSON 文字列が返ってきています。しかし、バックエンドが CORS 非対応なので応答ヘッダには Access-Control-Allow-Origin は含まれていません。

CORS 非対応 URL 正


(3) CORS 有効 URL 誤

上の (1) のケースからバックエンドの CORS 対応を有効にした結果です。URL は間違ったままなので 404 応答が返ってきています。上の (1), (2) と違って fetch 失敗とはみなされないようです。

CORS 有効 URL 誤

CORS を有効にしたので応答ヘッダには Access-Control-Allow-Origin が含まれています。

CORS 有効 URL 誤

Chrome のディベロッパーツールの Source タブの画面です。上の (1), (2) のケースでは fetch(url) の実行で例外がスローされて処理が終わってしまいますが、(3) のケースでは if(response.ok) 以降に処理が進みます。

CORS 有効 URL 誤

Tags: , , , ,

JavaScript

CORS 非対応の場合のエラーメッセージ

by WebSurfer 2. April 2024 16:33

ブラウザの fetch API を使ってドメインが異なる Web API などに要求を出した時、Web API 側が CORS に対応していない場合のブラウザのエラーメッセージや Fiddler で見た時の要求・応答がどのようになるかを書きます。

検証に使ったのは、先の記事「Web API に CORS 実装 (CORE)」に書いた ASP.NET Core Web API アプリで、Web API の CORS を無効にして、ドメインが異なる MVC アプリから fetch API を使って Web API に要求を出しました。ブラウザは Windows 10 の Chrome 123.0.6312.86 と Edge 123.0.2420.65 です。

ブラウザのエラーメッセージは、ディベロッパーツールの Console タブを開くと見ることができ、Web API 側が CORS 非対応では以下のようになるはずです。赤枠が「単純リクエスト」の場合で、青枠が「プリフライトリクエスト」の場合です。(注: 「単純リクエスト」というのは古い CORS 仕様書の用語で、現在 CORS を定義している Fetch 仕様書 ではその用語を使用していないそうです)

ブラウザのエラーメッセージ

エラーメッセージを以下にコピペしておきます。文章中の 'https://localhost:44371/api/values' が Web API 側で 'https://localhost:44343' がブラウザ側です。

「単純リクエスト」の場合

Access to fetch at 'https://localhost:44371/api/values' from origin 'https://localhost:44343' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

「プリフライトリクエスト」の場合

Access to fetch at 'https://localhost:44371/api/values' from origin 'https://localhost:44343' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

「プリフライトリクエスト」の場合は "Response to preflight request doesn't pass access control check:" という文が追加されています。その文の有無で「プリフライトリクエスト」か否かが分かるはずです。

蛇足ですが、エラーメッセージに含まれる "If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled." を見て mode を no-cors に設定してもほとんどの場合解決にはなりませんので注意してください。詳しくは先の記事「fetch API の mode:"no-cors"」を見てください。

「単純リクエスト」と「プリフライトリクエスト」が必要になる場合の違いについて簡単に書いておきます。「単純リクエスト」になるのは、メソッドが GET, HEAD, POST のいずれかで、かつ、要求ヘッダが「CORS-safelisted request header (CORS セーフリストリクエストヘッダー)」の場合です。それ以外は「プリフライトリクエスト」が必要になります。(詳しくは、MDN のドキュメント「オリジン間リソース共有 (CORS)」を見てください)

例えば、JSON 文字列をコンテンツに含めて POST 送信する場合は要求ヘッダに Content-Type: application/json を含めると思いますが、その場合は「プリフライトリクエスト」が必要になります。


Fiddler で見た時の要求・応答は以下のようになります。CORS の要求側はブラウザの仕事で、プリフライトが必要か否かもブラウザが判断して、下の画像の赤枠で示した CORS に必要な要求を出してくれます。

「単純リクエスト」の場合

ブラウザは Origin を要求ヘッダに含めて送信しますが、応答ヘッダに Access-Control-Allow-Origin が含まれないということででエラーとなっています。

「単純リクエスト」の場合

「プリフライトリクエスト」の場合

OPTIONS メソッドを使って CORS に必要なヘッダを要求に含めて出しています。下の画像の赤枠部分を見てください。しかし、応答ヘッダに Access-Control-Allow-Origin が含まれないということでエラーとなっています。

「プリフライトリクエスト」の場合

上の「プリフライトリクエスト」の場合の画像で、応答が 405 Method Not Allowed、Allow: GET, POST となっています。これは ASP.NET Core Web API による応答と思われますが、なぜ Method Not Allowed となるのか、GET, POST でないとダメと言われるのかは調べ切れてません。想像ですが、CORS を有効にしないと OPTIONS メソッドが許可されないということではないかと思われます。


参考に、Web API 側が CORS に対応していて、ブラウザ側でデータの取得に成功する場合の要求・応答を Fiddler で見た画像も下に貼っておきます。

「単純リクエスト」の場合

単純リクエスト

「プリフライトリクエスト」の場合

Web API が CORS に対応しているので「プリフライトリクエスト」の応答ヘッダに Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin が含まれて返ってきます。

プリフライトリクエスト

ブラウザはそれを見て再度要求を出しデータを取得します。

データの取得

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  September 2024  >>
MoTuWeThFrSaSu
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

View posts in large calendar