ASP.NET Core + React + Typescriptで認証付きWebアプリのひな型を作る②クライアント生成

31877 ワード

概要

前回の続きまでで、サーバーサイドのプロジェクトを生成しました。
今回は、クライアントサイドのプロジェクトを生成します。

作業の流れ

  1. Create React Appを使用してテンプレートを生成する
  2. webapi wheather forecastの結果をクライアントで表示できるようにサーバーサイドとクライアントサイドを変更する

Create React Appを使用してテンプレートを生成する

Create React AppはFacebookが提供するReactフロントエンドのひな型を生成してくれるツールです。煩雑な環境設定や関連するパッケージ類のインポートなどの手間を省略して、初心者でも簡単にひな型を作ることができます。

今回は、Create React Appを使用して、ソリューションフォルダの直下に、「client-app」という名称で、TypeScript有りでひな型を作ります。
ソリューションがおかれているフォルダに移動してから、以下のコマンドを入力します。

> npx create-react-app client-app --use-npm --template typescript

生成が成功すると、以下のように成功メッセージが表示され、client-appが生成されます

成功した段階で、以下のコマンドを実行すればトップ画面を表示させることが可能になります。

> cd client-app
> npm start

実行結果です。

②webapi wheather forecastの結果をクライアントで表示できるようにサーバーサイドとクライアントサイドを変更する

自動生成した状態のWEBAPIにあらかじめ組み込まれている「WeatherForecast」の結果を表示するように、クライアントのコードを変更していきましょう。
この表示を加えることで、まず認証機能を組み込む前にサーバーサイドとクライアントサイドで通信ができていることを確認します。

変更の流れは以下です

  • サーバーサイド
    • Startup.csを変更
  • クライアントサイド
    • App.tsxを変更
    • WeatherForecast.tsxを追加

Startup.csを変更

ASP.NET Coreは、初期状態だと別ドメインからのアクセスを制限する設定がされています。
元々はCSRF等の攻撃に対策するための機能ですが、今回のアプリではサーバーサイドとクライアントサイドでドメインが異なるので、初期設定のままだとクライアントからのアクセスも制限されてしまいます。
そのため、Startup.csを変更して、別ドメインからのアクセスを許容する設定を行います。
(本記事では全開にしていますが、本番環境では特定のドメインからのアクセスのみを許容する様に変更する方が安全です。)

コード(ハイライト部分が変更点)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

namespace server_app
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

+        readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
+
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "server_app", Version = "v1" });
            });


+            services.AddCors(o => o.AddPolicy(MyAllowSpecificOrigins, builder =>
+            {
+                builder.AllowAnyOrigin()    // Allow CORS Recest from all Origin
+                       .AllowAnyMethod()    // Allow All Http method
+                       .AllowAnyHeader();   // Allow All request header
+            }));

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "server_app v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

+            app.UseCors(MyAllowSpecificOrigins);   // Add For CORS
+
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

App.tsxを変更

元々の表示を消して、事項で説明する「<WeatherForecast />」を表示する様に変更します
コード(ハイライト部分が変更点)

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { FetchData } from './WeatherForecast';

function App() {
  return (
-    <div className="App">
-      <header className="App-header">
-        <img src={logo} className="App-logo" alt="logo" />
-        <p>
-          Edit <code>src/App.tsx</code> and save to reload.
-        </p>
-        <a
-          className="App-link"
-          href="https://reactjs.org"
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          Learn React
-        </a>
-      </header>
-    </div>
+    <div>
+      <WeatherForecast />
+    </div>
  );
}

export default App;

WeatherForecast.tsx追加

API:WeatherForecastのデータを表示するファイルを追加します

WeatherForecast.tsx(開くと表示)
import React, { FC, useEffect, useState } from 'react';

type Forecast = {
  date: Date;
  temperatureC: number;
  temperatureF: number;
  summary: string;
};

type State = {
  forecasts: Forecast[];
  loading: boolean;
};

export const WeatherForecast: FC = () => {
  const [state, setState] = useState<State>({ forecasts: [], loading: true });

  useEffect(() => {
    populateWeatherData();
  }, []);

  const populateWeatherData = async () => {
    const response = await fetch('https://localhost:5001/weatherforecast');
    const data = await response.json();
    setState({ forecasts: data, loading: false });
  };

  const renderForecastsTable = (forecasts: Forecast[]) => {
    return (
      <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.toString()}>
              <td>{forecast.date.toString()}</td>
              <td>{forecast.temperatureC}</td>
              <td>{forecast.temperatureF}</td>
              <td>{forecast.summary}</td>
            </tr>
          ))}
        </tbody>
      </table>
    );
  };

  let contents = state.loading ? (
    <p>
      <em>Loading...</em>
    </p>
  ) : (
    renderForecastsTable(state.forecasts)
  );

  return (
    <div>
      <h1 id="tabelLabel">Weather forecast</h1>
      <p>This component demonstrates fetching data from the server.</p>
      {contents}
    </div>
  );
};

実行結果