初めて Blazor のプロジェクトを作り、動かしたところまでやった人は多いと思います。
ですが実際にプログラムの中身を見てみると、全体的な構造や文法があまり理解できないんですよね。
公式ドキュメントには部分的な解説は載っていますが、「サンプルアプリがどう動いているのか」までは解説されていません。
そこで本記事では、サンプルアプリがどのように動いているのかをひと通り解説しました。
「全体的な構造を理解したい」という人におすすめな内容です。
プロジェクトの構成は次のようになっています。
それでは、さっそく見ていきましょう。
Client プロジェクト
Client はクライアント側で動作するプログラムを書くプロジェクトですね。
詳しく見ていきましょう。
Pages フォルダ
URL に対応する画面を置いておくフォルダです。
デフォルトは次の3画面が用意されています。
- Index.razor
- Counter.razor
- FetchData.razor
Index.razor
具体的にプログラムに中身を見てみましょう。
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@page
を指定することで、ページの具体的な URL を定義することができます。
スラッシュが定義されているので、ホーム画面として表示されるというわけです。
それより下は HTML を書くことで画面として表示されます。
SurveyPrompt
ってタグは初めて聞いた。SurveyPrompt
という見慣れないタグがありますね。
これは何かというと、子コンポーネントとして作成されている UI 部品です。
実際にプログラムの中身を見てみましょう。
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2137916">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string Title { get; set; }
}
子コンポーネントの場合は、単体でページにはならないため、@page
の宣言はありません。
同じような UI をいろんな画面でも使用したい場合に、何も考えなければコピペになってしまいますよね。
子コンポーネントを使用することで、プログラムで言うところの「メソッド化」をすることができ、コピペをしなくてもよくなります。
呼び出し元を「親コンポーネント」と呼び、子コンポーネントを呼び出すときにはパラメータを渡すことも可能です。
パラメータとして受け取りたいプロパティに対して [Parameter]
のアトリビュートをつけることで、メソッドの引数の役割をつけることができます。
サンプルでは Title
プロパティを受け取って、文字列を表示する仕組みになっているので、動的に表示する文字列を変えることができるというわけです。
Counter.razor
Index.razor
では @page
の指定が「/」でしたが、今回は「/counter」になっていますね。
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
p タグの中に@currentCount
という表現があります。
これは、C# 側で定義した変数をタグに組み込み、動的に値を変えることができる仕組みです。
@code
の中身には、C# のロジックを書きます。
C# の内部変数として currentCount
が定義されていますね。
HTML のタグ内で参照するときは、@ をつければ C# の変数という意味になります。
また、ボタンタグに「@onclick=”IncrementCount”」という表現を見つけましたか?
これは、「ボタンをクリックしたときに IncrementCount
のメソッドを呼びますよ」という意味です。
ボタンを押すことで変数がインクリメントされ、画面上の表示も1ずつ増えていくというわけですね。
FetchData.razor
FetchData.razor
は、サーバー側からデータを受け取って画面に表示するサンプルになっています。
少し長いですが、落ち着いて見ていけば難しくないので安心してください。
@page "/fetchdata"
@using SampleApp.Shared
@inject HttpClient Http
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
}
先ほど「@code
で囲まれたところに C# の処理を書く」と説明しましたが、実は HTML 部分にも C# の処理を書くことができます。
@if
や @foreach
の部分ですね。@をつけてあげることで、条件によってタグを表示したりしなかったり制御できます。
上の方を見ると @using
の記述がありますが、これは C# の使い方と同じで参照の追加です。
その下にある @inject HttpClient Http
は、依存性注入(DI:Dependency Injection)と呼ばれています。
ざっくり「インスタンスの使い回しができる宣言」と覚えておけばいいでしょう。
よく見ると Program.cs
の AddScoped
で追加されているので、見てみてください。
宣言した Http
を使用して、サーバー側の WeatherForecastController
の Get メソッドを呼んでいます。
Properties フォルダ
launchSettings.json
はローカルでのみ使用されるもので、開発環境の構成を書いておくファイルですが、あまり触ることはないと思います。
Shared フォルダ
UI コンポーネントを置いておくフォルダです。
MainLayout,razor
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>
ナビゲーションメニューやヘッダ、フッタの情報は、どのページでも同じレイアウトになりますよね。
どのページでも同じなのに、各ページにプログラムを書いていては無駄が発生してしまいます。
そこで、あらかじめ全体的なレイアウトを決めておくことで、各ページで違うところだけ置き換えるのみで済む仕組みが用意されています。
LayoutComponentBase
を継承することで、レイアウトのコンポーネントとして定義ができます。
@Body
は、各ページのことを指しています(Index.razor
など)。
NavMenu.razor
ナビゲーションメニューを定義している UI コンポーネントです。
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">SampleApp</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
NavLink
コンポーネントは a タグのように動きますが、選択したメニューをハイライトしてくれたり、href は最低限のパスだけ指定するだけでよくなったりします。
wwwroot フォルダ
CSS や Javascript、画像など静的なファイルを置いておく場所です。
favicon.ico
はブラウザで開いたときのアイコンになります。
index.html
は、読み込む Javascript や CSS を書いておくファイルです。
_imports.razor
各画面の .razor
ファイルで毎回 @using
を書かなくて済むように、まとめて参照を追加することができるファイルです。
よく使う参照は、_imports.razor
に追加しておきましょう。
App.razor
ルーティングを設定するコンポーネントです。
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
ざっくりと以下の動きになっています。
- デフォルトのレイアウトは
MainLayout
を使用する - 指定した URL が存在すれば該当ページに遷移する
- 指定した URL が存在しなければ「Sorry,…」を表示する
MainLayout
については、Shared フォルダの説明を参照してください。
Program.cs
Client 側の開始プログラムです。
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SampleApp.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
}
先ほど解説した HttpClient の依存性注入もここでやっていますね。
Server プロジェクト
Server はサーバー側で動作するプログラムを書くプロジェクトですね。
詳しく見ていきましょう。
Controllers フォルダ
Controllers フォルダには、クライアント側から API として呼び出す処理を書くクラスをまとめておきます。
サンプルでは、WeatherForecastController.cs
が用意されていますね。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SampleApp.Shared;
namespace SampleApp.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
[Route("[controller]")]
というのは、リクエスト URL をコントローラー名を基準に設定するという意味です。
WeatherForecastController
が名前なので、クライアント側から WetherForecast で呼び出せば OK です。
この API は FetchData.razor
から呼び出されています。
Pages フォルダ
Error.cshtml
はアプリ側でハンドリングできなかったエラーが発生したときに表示されます。
Properties フォルダ
Client 側と同じ情報が書かれています。
appsettings.json
アプリの構成設定を書いておくファイルです。
Program.cs
サーバー側の開始プログラムです。
Startup.cs
こちらも開始時に読み込まれるプログラムで、お作法の処理がほとんどです。
認証を追加するときなどは、このファイルに処理を追加したりします。
Shared プロジェクト
Shared はクライアント側とサーバー側で共通して使用したいプログラムを入れておくプロジェクトです。
サンプルでは WeatherForecast.cs が用意されています。
中身を見るとエンティティとして使っているので、データ保持を目的として使っていますね。
あとは、ユーティリティ的な処理とかも置いておくと便利かもしれません。
最後に
Blazor のプロジェクトを新規作成したときの構成について解説しました。
かなり盛りだくさんだったので、全部を一気に覚えられなかったかもしれません。
私も最初はさっぱりでしたが、繰り返し処理を追っていくごとに少しずつ理解できるようになってきました。
この記事を読んで、少しずつ自分なりにカスタマイズもしてみましょう。
入門編から EC サイトを作る応用編まで、Blazor の本を3冊執筆しました。
私が1年以上かけて学習した内容をすべて詰め込んでいるので、さらにステップアップしたい方はぜひご覧ください。