はじめに

BlazorCodeFirst を使うと、Blazor の UI を通常の C# として書けます。

導入#

ランタイムとソースジェネレーターをプロジェクトに追加し、コンポーネントを BodyComponentBase から派生させます。

dotnet add package BlazorCodeFirst --prerelease

公開しているバージョンには prerelease の接尾辞が付いています。--prerelease を外すと、コマンドは最新の安定版を探します。安定版はまだないので、パッケージは復元されません。

最初のコンポーネント#

コンポーネントは、プロパティを1つオーバーライドした partial クラスです。partial が必要なのは、ジェネレーターがそのクラスの中に描画を書き込むためです。トップレベルである必要があるのは、入れ子のクラスを生成ファイル側で開き直すには、外側の型の宣言を型引数まで含めて書き直さねばならないためです。

using Microsoft.AspNetCore.Components;
using BlazorCodeFirst;
using static BlazorCodeFirst.Html;

[Route("/")]
public partial class Home : BodyComponentBase
{
    protected override View Body =>
        Div[
            H1["Hello"],
            Span["Welcome to BlazorCodeFirst."]];
}

この式は、生成される HTML をそのまま表します。属性は要素に繋げ、子ノードは角括弧に入れます。文字列をそのまま書くとテキストノードになります。

protected override View Body =>
    Div[
        H1["Hello"],
        Span["Welcome to BlazorCodeFirst."]];
<div>
    <h1>Hello</h1>
    <span>Welcome to BlazorCodeFirst.</span>
</div>

ゲッターが返すのは1つの式#

書き方は3通りあり、どれも同じものへ翻訳されます。

protected override View Body => Div[H1["Hello"]];                    // これでよい
protected override View Body { get => Div[H1["Hello"]]; }            // これでもよい
protected override View Body { get { return Div[H1["Hello"]]; } }    // これでもよい

その return の手前には、ローカル変数の宣言と式文を置けます。これらのステートメントは、生成される RenderView の中で、レンダーツリーのフレームを発行する呼び出しより前にそのまま出力されます。ForEachcontent に書いたステートメントも、同じ位置に出力されます。

protected override View Body
{
    get
    {
        var greeting = $"Hello, {_name}";
        return Div[H1[greeting]];
    }
}

2つ目の return と C# 本来の制御構文は、それぞれ専用のシーケンス空間を必要とするため、どちらも受け付けません(BCF1004)。本体がどうしてもこの形にならないなら、 RenderView を手で書いてください。そのとき設計時の式は使われなくなり、何も報告されません。

この API が読まれる場所#

Html.Div.Class(...).OnClick(...) をはじめ、要素のファクトリと装飾は、それ自体では何もしません。View は空の構造体で、要素ヘルパーは何も返さず、装飾はレシーバーをそのまま返すだけです。ジェネレーターが読むのは書かれた 構文 であって、値ではありません。読む場所も3つしかありません。コンポーネントの Body、レイアウトの Chrome、そして [ViewPart] メソッドの本体です。

同じ API はどこからでも呼べますが、この3か所の外では誰も読みません。イベントハンドラー、サービス、ヘルパーメソッドのどこに書いてもコンパイルは通ります。ただしレンダーツリーのフレームを 1つも出さないので、何も描画されず、イベントハンドラーも登録されません。これが BCF3029 で、呼び出し側から見た同じ誤りが BCF3030 です。

ビルドが止まる理由#

コンパイラは、RenderTreeBuilder の呼び出しへ翻訳できない式を報告します。書いたものと違う描画になるコードを出すより、そこで止めるためです。診断はすべてリファレンスに項があり、よく出るのは次の5つです。

BCF1001 クラスが partial でない
BCF1005 クラスが入れ子になっている
BCF1004 ゲッターが1つの返り値の式に収まらない
BCF1002 生成ファイルから見えないローカル変数を式が参照している
BCF1003 ジェネレーターが読まない構文を式が使っている

この5つは、ジェネレーターがそもそも読めない構文を拒否するものです。同じ規則は、構文としては読めても、プリレンダリングとインタラクティブで別々の DOM になる形も検出します。 BCF3016 は void 要素への子ノードを拒否します。プリレンダリングは閉じタグを出力するため HTML パーサーが子ノードを兄弟要素として押し出す一方、インタラクティブな描画ではその経路を一切通らないためです。

1つのクラスが partial の欠落とゲッターの問題を同時に持つことはありますが、報告されるのは一度に1つです。partial の検査が先に実行されるので、まず BCF1001 だけが出ます。修飾子を足すと、次に BCF1004 が出ます。

次に読むもの#

An unhandled error has occurred. Reload 🗙