.NET Core Blazor + MicroCMSでブログサイト作成④
前回、アクセス時にMicroCMSからデータを取得し画面に表示させるところまで作成しました。
しかし現在のままでは問題があります。
- サーバー側へのリクエストのエンドポイントがコントローラー単位になっている
- appsettings.jsonの値を使うための機能がコントローラーで挿入されている
それぞれ解決していきます。
コントローラーに複数アクションを定義できるようにする
// 抜粋
namespace BlazorBlog.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class MicroCMSController : ControllerBase
{
// ...
[HttpGet]
public async Task<SiteData> Get()
{
// ...
}
}
}
上の状態ではhttps://.../fetchdataに対するGETリクエストをpublic async Task<SiteData> Get()
が受け付ける動作しか定義されていません。
属性ルーティング/従来型ルーティング
.NET Coreには2種類のルーティング方法があり、それぞれ属性ルーティング(Attribute Routing)と従来型ルーティング(Conventional Routing)というそうです。
ASP.NET Core apps can mix the use of conventional routing and attribute routing. It's typical to use conventional routes for controllers serving HTML pages for browsers, and attribute routing for controllers serving REST APIs.
ASP.NET Coreアプリは従来型ルーティングと属性ルーティングを組み合わせることができます。通常、HTMLページを返すコントローラーには従来型ルーティング、REST APIを返すコントローラーには属性ルーティングを使います。
Actions are either conventionally routed or attribute routed. Placing a route on the controller or the action makes it attribute routed. Actions that define attribute routes cannot be reached through the conventional routes and vice-versa. Any route attribute on the controller makes all actions in the controller attribute routed.
アクションは従来型/属性どちらのルーティングも使用できます。コントローラーやアクションの上にルートを配置すると属性ルーティングになります。属性ルーティングされたアクションは従来型ルートからはアクセスできず、その逆もできません。コントローラーに付与されたどんな属性も、すべてのアクションを属性ルーティングにします。
従来型
MVCで用いられる従来型ルーティングは、ルートテンプレートを定義し、ルーティングのDIに渡しています。
属性型
今回使用する属性型では、DIでルートテンプレートを定義しません。
ルート結合
コントローラーのルートとアクションのルートは自動で結合されます。
アクションにルートのフルネームを書かなくてもよいです。
ルーティングしてみる
メチャ簡単です。アノテーションを変えるだけです。
// 抜粋
namespace BlazorBlog.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class MicroCMSController : ControllerBase
{
// ...
// 修正 [HttpGet] -> [Route("sitedata")]
[Route("sitedata")]
public async Task<SiteData> Get()
{
// ...
}
}
}
クライアントからAPIを叩くときの引数も、合わせて変更します。
// 抜粋
@code {
private SiteData sitedata;
protected override async Task OnInitializedAsync()
{
try
{
// 修正 "MicroCMS" -> "MicroCMS/SiteData"
sitedata = await Http.GetFromJsonAsync<SiteData>("MicroCMS/SiteData");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
デバッグ実行してFetch Dataにアクセスすると、ちゃんと取得データが画面に出るはずです。
デベロッパーツールからリクエストのタイムラインを見ると、リクエストURLも想定通りになっています。
データもちゃんと届いてますね。
TTFBが120~160ミリ秒だったので十分速いでしょう。
ついでの補足
.NET MVCでは各コントローラーはController
クラスを継承していますが、BlazorではControllerBase
クラスを継承しています。
Controller
はControllerBase
のサブクラスです。
Controller
にはビューを返す機能がありますが、Blazorには必要ないためスーパークラスであるControllerBase
を継承しています。
公式でも「Web APIではControllerを継承しないでください」と書かれています。👇
appsettings.jsonのDI
公式から「乳首ドリルコントローラーにDIすな!」と言われているので、以下の方針で修正します。
- 設定内容を保持するクラスを作成
- スタートアップのDIで設定値をクラスにバインド
- サーバー側のどこからでも設定にアクセスできる!!幸せ!!
設定値クラス
クラスの配置場所は.Server内ならどこでも大丈夫です。
namespace BlazorBlog.Server
{
public class MicroCMSSettingModel
{
public string BaseUrl { get; set; }
public string ApiKey { get; set; }
public MicroCMSEndpointsSettingModel Endpoints { get; set; }
}
public class MicroCMSEndpointsSettingModel
{
public string SiteData { get; set; }
public string Tags { get; set; }
public string Blogs { get; set; }
}
}
appsettings.jsonではAPIキーの名前をX-API-KEY
としていましたが、クラスのフィールド名にハイフンが使えないので上記に合わせます。
// 抜粋
"MicroCMS": {
"BaseUrl": "~",
// 👇
"ApiKey": "~",
"Endpoints": {
// ...
}
}
DI設定
スタートアップに1行追加します。
// 抜粋
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
// 追加
services.Configure<MicroCMSSettingModel>(Configuration.GetSection("MicroCMS"));
}
コントローラー内で設定値を使用する
3つ修正箇所があります。細かい修正もあるので全文お見せします。
using BlazorBlog.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace BlazorBlog.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class MicroCMSController : ControllerBase
{
// 1. 設定値クラスをprivate readonlyで準備
// ついでにloggerは使わないので削除
private readonly MicroCMSSettingModel apiSettings;
public MicroCMSController(IOptions<MicroCMSSettingModel> setting)
{
// 2. 設定値インポート
apiSettings = setting.Value;
}
/// <summary>
/// サイトデータ取得
/// </summary>
/// <returns>サイトデータ</returns>
[Route("SiteData")] // 👈念のためクライアントからのリクエストURLに合わせた
public async Task<SiteData> GetSiteData()
{
// 3. 設定値クラスから値を引っ張るように修正
// APIコールのベースURL
string baseUrl = apiSettings.BaseUrl;
// エンドポイント
string endpoint = apiSettings.Endpoints.SiteData;
// X-API-KEY
string apikey = apiSettings.ApiKey;
// URL全体
string url = baseUrl + endpoint;
using (var client = new HttpClient())
{
// ヘッダーに認証キーを登録
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("X-API-KEY", apikey);
// リクエスト送信
var response = await client.SendAsync(request);
// レスポンスから結果を取り出しデシリアライズ
var result = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
SiteData deserialisedResult = JsonSerializer.Deserialize<SiteData>(result, options);
return deserialisedResult;
}
}
}
}
これでデバッグ実行すると、きちんと設定値を取ってデータ取得できていることが確認できます。
ついでに修正
ページ名がデフォルトのままFetch Dataになっているので、ここら辺でBlogページに変えておきます。(以下BlazorBlog.Client内での変更)
- Pages下のFetchData.razorをBlog.razorに変更
- Blog.razor内の
@pages "/fetchdata"
を@page "/bog"
に変更 - Shared下のNavMenu.razor内で、Fetch Dataページへリンクしている部分をBlogページへリンクするように修正
修正内容
<NavLink class="nav-link" href="blog">
Blog
</NavLink>
下のようになっていればOKです。
次回
いよいよブログページ本体を実装していきます。
これまでサーバー側メインでやってきましたが、次回からクライアント側メインにシフトします。
Discussion