目次
Blazor
便利ツール
テーブル作成
Tips
初回アクセスページ
Blazor Server
Pages フォルダにある「_Host.cshtml」ファイルとなる。
Blazor WebAssembly
wwwroot フォルダにある「index.html」ファイルとなる。
UIフレームワーク
MatBlazor が一番有名な UI フレームワークなのですが、GitHub のスターがより多い「Ant Design Blazor」となる。
【無料】Blazor 対応の「使える」UI フレームワーク5選
AntDesign 採用例
BlazorStrap 採用例
Blazor Server Side へのインストール方法
- nuget から BlazorStrap と BlazorStrap.V5 の2つのパッケージをダウンロードします。
- _host.cshtml を次のように変更します。
- <head>追加されていることを確認 (YourAssemblyName)の部分は書き換える
- <link rel=“stylesheet” href=“css/bootstrap/bootstrap.min.css” />
- <link href=“(YourAssemblyName).styles.css” rel=“stylesheet”>
- <body>追加 の最後に
- <script src=“_content/BlazorStrap/popper.min.js”></script>
<script src=“_content/BlazorStrap/blazorstrap.js”></script>
V5.2.100-Preview3 でblazorstrap.jsを削除
- _Program.cs に追加
- using BlazorStrap;
- builder.Services.AddBlazorStrap();
- _Imports.razor に追加
- @using BlazorStrap.V5
- MainLayout.razor の class=“page”の最後に下記を追加
- <BSCore />
NuGet参照したフレームワークの場所「_content」
NuGet参照したUIフレームワークの「BlazorStrap」など
以下の命名則の URL で、これらの静的 Web アセットを参照できる。
- “_content/“で始まり、
- 続けて、パッケージの名前、
- 最後に、アセットファイルのファイル名
<script src="_content/BlazorStrap/popper.min.js"></script> <script src="_content/BlazorStrap/blazorStrap.js"></script>
NuGetのパッケージキャッシュフォルダは、デフォルトは「%userprofile%\.nuget\packages」
スタートアップページの設定
Blazor アプリケーションのスタートアップ ページは Index.razor です。
これを Login.razor に変更したい場合、下記のようにします。
- Index.razorから「@page ”/“」 を削除します。
- Login.razor に「@page ”/“」 を追加します。
アイコン使用
Bootstrap Icons
1,800以上のアイコンが使用できる。
https://icons.getbootstrap.com/
本家からはnpmのみで、NuGetによるインストールは用意されていない。
任意のサイズ
font-size と color を使用して、アイコンの外観を変更
https://bootstrap-guide.com/extend/icons/bootstrap-icons
<i class="bi bi-alarm" style="font-size: 2rem; color: cornflowerblue;"></i>
FontAwesome
Web系の開発でアイコン表示などによく使われている。
現時点で、Font Awesome 6が最新である。
Open Iconic
.NET 7までは、「Open Iconic v1.1.1」のアイコン223種類が使用できる。.NET 8から除外された。
https://www.nsbasic.com/app/OpenIconic.html
Open Iconicは223ものアイコンをOSSとして公開されているアイコンフォントです。EOTやWOFF、OTFだけでなく、PNG、SVGなどのフォーマットも用意されています。また、マルチカラー化や用意された複数サイズのフォントを利用してRWDに対応する、等も可能のようです。シンプルで汎用性の高そうなデザインですね。ライセンスはMIT、フォントはOFLとして公開されています。
オープンソースとして公開されているアイコンフォントのセット・「Open Iconic」
ナビゲーションのホームアイコン
<span class="oi oi-home"></span>
Razorコンポーネントのライフサイクル
【Blazor】Razorコンポーネントのライフサイクルを解説する
実行の順番
メソッド名 | 呼び出し | 補足 |
---|---|---|
SetParametersAsync | パラメーターが設定されるタイミング | |
OnInitialized(Async) | コンポーネントが初期化されるタイミング | |
OnParametersSet(Async) | コンポーネントが初期化されるタイミングと、受け取るパラメータが更新されたタイミング | |
OnAfterRender(Async) | コンポーネントがレンダリングされたあと | 初回実行のときは、引数の firstRender が true となる |
ShouldRender | コンポーネントがレンダリングされるたび | true を返すとレンダリングを続行する |
StateHasChanged | レンダリングしたい任意のタイミング | コストが高いので不必要に呼び出さないこと |
OnParametersSet(Async)について
Navigation.NavigateToで別ページに画面遷移させた時でもOnParametersSetAsyncが動作する。
Navigation.NavigateToで別ページに画面遷移させた時に例外エラー、原因として OnParametersSetAsync処理内で発生していた。 NavigateTo前に_isNextPageフラグをTrueにセットして使用して回避させた。
protected override async Task OnParametersSetAsync() { // 別ページに画面遷移する場合は何もしない。 if (_isNextPage) return;
Razorコンポーネントのステート
ステートの変更通知はどのようなタイミングで発生するのかですが、おおまかに下記の3パターンで発生します。
- StateHasChanged メソッドを明示的に呼び出したとき
- イベント発火時 (@bind や @onclick とか)
- Parameter が変わったとき
2つ目と3つ目は暗黙的に StateHasChanged を呼び出している。
StateHasChanged はステートが変更されたことを通知するメソッドです。このメソッドを呼び出すと再レンダリング候補としてマークされます。
コードビハインド
razorファイルの中にHTML要素とバインドするC#コードを合わせた記載はやめて、razorコンポーネントとC#コードを分離して記載する。
Blazorでコードビハインドでロジックとビューを分離して記述する
ロジックとビューを分離することで、下記のようなメリットなどが得られます。
- ロジックに対するUnitTestの記載が可能
- 基底のコンポーネントクラスの作成などによる共通処理の作成
方法
raozrファイルと対になるようにcsのファイルを作成するだけです。
Counter.razor Counter.razor.cs(追加)
コード側のクラスには、partialキーワードを付けます。
例 public partial class Counter
最大長(maxlength)を設定する方法
BlazorのInputコンポーネントに maxLength が見つからない。
これらの組み込みコンポーネントは、レンダリングされた出力に HTML 属性を渡します。したがって、例で行ったのとまったく同じように、HTML 属性を指定するだけでかまいません
How to set textarea/input max length in Blazor - stackoverflow
組み込み専用の属性がないだけで、HTML 属性はそのまま指定すれば機能する。
<BSInput InputType="InputType.Text" placeholder="工程コード" Value="@("")" maxlength="5" />
ボタンにフォーカスを設定する方法
UIフレームワーク未使用
有名なUIフレームワークでも、Buttonのフォーカスが用意されていないことがあるので、その場合に標準機能で代替する。
<button class="btn btn-primary" @onclick="IncrementCount" @ref="ControlButton">Click me</button> @code { public ElementReference? ControlButton { get; set; } = default!; protected override async Task OnAfterRenderAsync(bool firstRender) { if (ControlButton != null) await ControlButton.Value.FocusAsync(); } }
BlazorStrap使用
※BlazorStrap/5.2.100-Preview3b が必要
https://github.com/chanan/BlazorStrap/issues/599
<BSButton Color="BSColor.Primary"@onclick="IncrementCount" @ref="ControlButton">Click me</BSButton> @code { public ElementReference? ControlButton { get; set; } = default!; protected override async Task OnAfterRenderAsync(bool firstRender) { if (ControlButton != null && ControlButton.Element != null) await ControlButton.Element.Value.FocusAsync(); } }
BSInputGroupを中央寄せにする方法
BSInputGroupでdisplay:flex となっているため、style属性で”display:block に書き換えてから中央寄せ(text-align: center)とする。
<BSInputGroup MarginBottom="Margins.Medium" style="display:block; text-align: center"> <div Class="@BS.Form_Check_Inline"> <BSInputRadio CheckedValue="@("on")" @bind-Value="_value" @onchange="OnChange" /> <BSLabel IsCheckLabel="true">バーコード</BSLabel> </div> <div Class="@BS.Form_Check_Inline"> <BSInputRadio CheckedValue="@("off")" @bind-Value="_value" @onchange="OnChange" /> <BSLabel IsCheckLabel="true">手動選択</BSLabel> </div> </BSInputGroup>
Boostrap 5による変更点
bootstrap5では中東の言語で主流である右書きに対応したことからleft→start、right→endという概念の変更が導入されました。
これにより、ml-〇 → ms-〇にmr-〇 → me-〇 というようにクラス名が変更になりました。
HTMLタグを出力する
MarkupStringオブジェクトにキャストすることでタグの内容を出力できます。
- 例
<div> <button @onclick="ButtonClick">Button1</button> <p>@((MarkupString)messageText)</p> </div> @code { string messageText; void ButtonClick() { messageText = "<h3>見出しです</h3><p>My Button Clicked</p>"; } }
最上位のCSSを定義
フォントサイズなど、bootstrap.css で定義されたサイズが優先されてしまう場合、important.css にてCSSを定義することにより優先させるようにする。
- _Host.cshtml
<link rel="stylesheet" href="css/important.css" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
画面サイズによりフォントサイズを変更する。
- important.css
.font-adjust { font-size: 16px !important; } @media (min-width: 760px) { .font-adjust { font-size: calc(16px + ((1vw - 7.6px)*(16/4.4))) !important; } } @media (min-width: 1200px) { .font-adjust { font-size: 32px !important; } }
テキストボックスのEnterキーでサブミットさせる
IsSubmit=“true” を付けることで、Enterキーでサブミットさせることが出来る。
<BSInput InputType="InputType.Text" IsSubmit="true" @bind-Value="Model.Input1" class="font-adjust" />
bindとonchangeの併用は不可
onchange を追加すると、Model.Procedure に値がセットされなくなる。
<BSInput InputType="InputType.Select" @bind-Value="Model.Procedure" @ref="ProcedureControl" @onchange="OnProcejureChange">
対応
onchange イベント処理内で値をセットする。
private void OnProcejureChange(ChangeEventArgs e) { Model.Procedure = e.Value.ToString() ?? ""; }
MainLayout コンポーネント側にデータを反映させる
カスケーディングを使用してパラメーターを渡す。
カスケードとは、もともと「連なった小さな滝」という意味があります。
Blazor では滝が流れるイメージで、上位の階層から下位の階層にパラメーターを渡すことができる仕組みがあります。
これが「カスケーディングパラメーター」です。
クライアントのIPアドレスの取得
BlazorServerではサーバーで動作しているので、クライアントのIPアドレスを取得するにはクライアントから情報を取得する。
MainLayoutの子コンポーネント側に表示させるため、カスケーディングを使用してパラメーターを渡す方式にした。
クエリパラメーターのセットと取得
単独変数
var parameter = new Dictionary<string, object?> { { "CPCode", value }, { "TermID", Layout.Info.IPAddress } }; var newuri = Navigation.GetUriWithQueryParameters("./Machine", parameter); Navigation.NavigateTo(newuri, false);
[Parameter, SupplyParameterFromQuery] public string? CPCode { get; set; } [Parameter, SupplyParameterFromQuery] public string? TermID { get; set; }
複数変数
var readOnlyDictionary = new Dictionary<string, object> { ["film"] = 1, ["film"] = 2, }; var uri = Navigation.GetUriWithQueryParameters(readOnlyDictionary);
// http://localhost:5000/MovieComparer?film=1&film=2&film=3 [Parameter, SupplyParameterFromQuery(Name = "film")] public string[]? Films { get; set; }
通常クラス
Blazorコンポーネント内では Navigation.GetUriWithQueryParametersを使用できるが、Blazorコンポーネント外の一般クラスでは、HttpUtility.ParseQueryString を使用する。
Uriからパラメータ取得
NameValueCollection query = HttpUtility.ParseQueryString(new Uri(Navigation.Uri).Query); string key = query["Key"] ?? ""
Blazor WebAssembly - Get Query String Parameters with Navigation Manager
参照
セッションの扱い(状態管理)
コンポーネント内でデータを保存し、読み込む
ブラウザー ストレージのデータの読み込みまたは保存が必要なすべてのコンポーネントで、@inject ディレクティブを使用して、次のいずれかのインスタンスを挿入します。
- ProtectedSessionStorage
- ProtectedLocalStorage
メモリ内状態コンテナー サービス
入れ子になったコンポーネントと入れ子になっていないコンポーネントでは、登録済みのメモリ内状態コンテナーを使用してデータへのアクセスを共有できます。
Scopeについて
メソッド | 内容 |
---|---|
AddTransient() | インジェクション毎にインスタンスを生成 |
AddScoped() | リクエスト毎にインスタンスを生成 |
AddSingleton() | アプリケーション内で1つのインスタンスを生成 |
- Program.cs
builder.Services.AddScoped<StateContainer>();
injectの記載方法
ディレクティブ記法でオブジェクトをインジェクトします。
- xxxx.razor
@inject 【インジェクトするオブジェクトの型】 【インジェクトする変数名】 @inject StateService Service
上記の書き方以外にコードブックにて[Inject]属性を付与する記法があります。 今後コードビハインドで記述するならこっちの方が分かりやすいです。
- xxxx.razor.cs
[Inject] private StateService Service { get; set; } = default!;
物理パスの取得
Blazor Serverの場合、Directory.GetCurrentDirectory() を使用すればいい。
string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot");
Web APIを追加
Blazor ServerにWeb APIを追加する。
サンプル
- Controllerフォルダを作成
- 右クリックメニューから[追加]の[コントローラー]をクリックする
- 共通のAPIにある「読み取り/書き込みアクションがある API コントローラー」を選択して[追加]ボタンをクリックする
- ValuesController.cs を追加する
下記コードをapp.Run()より前に、該当するところに追加する。
- program.cs
// Add MVC Controllers builder.Services.AddControllers(); app.MapRazorPages(); app.MapControllers();
Urlにてapiの後にコントローラー名を入れてアクセスすれば、値が返ってくる。
https://localhost:7072/api/Values
参照
HttpClientの使用
「@Inject HttpClient httpClient」を使用すると下記エラーが発生する。
Unhandled Promise Rejection: Error: System.InvalidOperationException: Cannot provide a value for property 'Http' on type . There is no registered service of type 'System.Net.Http.HttpClient'.
Injectを使用しないで、new HttpClient() とする。
ボタンクリックイベントでSUBMIT処理
ボタンクリックイベントでは、F1キーを入力させたようにしたいため、IsSubmit(type=“submit”)を使わないで制御したい。
<BSButton Color="BSColor.Info" Class="font-adjust" IsSubmit="true"> ↓ <BSButton Color="BSColor.Info" Class="font-adjust" OnClick="@OnMenuClick">
メニューボタンをクリックしたら、入力値にF1キーをセットしてSUBMIT処理を呼ぶ。
public void OnMenuClick() { MatchViewModel Model = new(); Model.Input1 = Const.INPUT_KEY_F1; EditContext e = new(Model); OnSubmit(e); }
カスタムコンポーネントのbind-value
bind-value を指定したたけでは、”does not have a property matching the name 'ValueChanged'“エラーとなり動作しない。
そのため、ParameterにValueChangedを定義する。
- Index.razor
<CustomInput @bind-Value="@InputValue"></CustomInput> @code { public string InputValue = "Example"; }
- CustomInput.razor
<input value="@Value" @oninput="@OnInputChange" /> <h4>Welcome to Blazor Application, @Value</h4> @code { [Parameter] public string? Value { get; set; } [Parameter] public EventCallback<string> ValueChanged { get; set; } private async Task OnInputChange(ChangeEventArgs args ) { Value = (string?)args.Value; await ValueChanged.InvokeAsync(Value); } }
How do you use bind-value and bind-value:event on a custom component?
カスタムコンポーネントで親コンポーネントの処理を実行
カスタムコンポーネントのOnClickCallbackパラメータに親側の実行処理を記載する。
カスタムコンポーネント側のボタンをクリックすると親側の処理が実行される。
// イベント処理をラムダ式で書いた場合 <BSButton OnClick=@(async () => await OnClickCallback.InvokeAsync()> @code { [Parameter] public EventCallback OnClickCallback { get; set; } }
子コンポーネントのメソッドを実行
親コンポーネント内で子コンポーネントを@refを用いて参照することでメソッドを呼ぶことが出来る。
リトライ回数を増やす
リトライ回数はデフォルトで8回となっている。
Blazor Server でサーバーとの接続障害からの復旧後、「再読込」をユーザに押させることなく自動でページ再読み込みする
awaitのメソッドを追加すると動作しなくなる
Submitの先頭にawait.Task.Delay メソッドを実行すると、エラー用のメッセージモーダルが表示されなくなる現象があった。
async void を async Task に変更することで解決した。
async void Submit() ↓ async Task Submit()
Javascriptとの連携
BlazorとjQueryコンポーネント等との連携
jQueryコンポーネント または jQueryを使用しないJavaScriptコンポーネントをBlazorと連携する場合の注意点
下記のようにすれば、onchangeイベントを呼ぶことが出来る。この方法だと DotNet.invokeMethodAsync を使わずに済む。
- combotree.js
$("#justAnotherInputBox").on('change', function () { var myElement = $(this)[0]; var event = new Event('change'); myElement.dispatchEvent(event); console.log("Change"); });
- combotree.razor.cs
<input type="text" id="justAnotherInputBox" class="form-control" autocomplete="off" placeholder="Select" @onchange="OnChange"/> private void OnChange(ChangeEventArgs e) { if (_comboTree != null || e.Value != null) { Console.WriteLine(e.Value); } }
@ref 属性による弊害
inputタグに @ref 属性を付けて、 @ref=“CurrentElement” として、JQuery Pluginである ComboTree に渡していた。これで問題なく表示して動作していたが、同じ画面を再度表示しようとした際に this._input.addClass や this._input.wrap で例外エラーが発生してしまう。どうも追加するinputタグが見つけれない状態
_comboTree = await _jsClass.InvokeAsync<IJSObjectReference>("createComboTree", CurrentElement, options);
createComboTree(element, Options) { this.combo = $(element).comboTree(Options);
@ref 属性ではなく、一般的なid属性の使用に切り替えることで例外エラーが発生しなくなった。
_comboTree = await _jsClass.InvokeAsync<IJSObjectReference>("createComboTree", "#" + ElementId, options);
nullを変換できない場合
Datetimepicker(tempusdominus)のv6を使用した際に、範囲最小値(minDate)と範囲最大値(maxDate)の指定でminDate側にnullをセットして実行した段階で、nullに変換できないとしてエラーとなった。
null 値のプロパティを無視する属性を付けることで回避できた。
System.Text.Json で null 値のプロパティを無視する
private class Restrictions { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTime? minDate { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTime? maxDate { get; set; } }
ログ出力にlog4netを使用する
Structured Logging in ASP.NET Core With log4net
- log4net.config
<?xml version="1.0" encoding="utf-8"?> <configuration> <log4net> <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender"> <param name="Encoding" value="utf-8" /> <!-- ファイル名 --> <param name="File" value="../../../logs/KEYENCE/ServerLog/IIS" /> <!-- 追記する場合true/上書きする場合false --> <param name="AppendToFile" value="true" /> <!-- 日付や時刻の制約によるログファイルの切替 --> <param name="RollingStyle" value="Date" /> <param name="DatePattern" value='"."yyyy-MM-dd-HH".log"' /> <param name="StaticLogFileName" value="false" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender> <root> <level value="ALL" /> <appender-ref ref="RollingFileAppender" /> </root> </log4net> </configuration>
デバッグ詳細
an unhandled exception on the current circuit のエラーの場合、詳細エラーを出力
https://stackoverflow.com/questions/57514541/how-to-turn-on-circuitoptions-detailederrors
- appsettings,json
{ "DetailedErrors": true, // turns on CircuitOptions.DetailedErrors "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.SignalR": "Debug" // turns on SignalR debugging } }, "AllowedHosts": "*" }
ブラウザのキャッシュ回避
Blazorには、ASP.NET Coreにあった「asp-append-version=“true”」 が見当たらない。
Razor Pagesでファイル更新時にブラウザキャッシュを更新する方法
よって、ファイルバージョンを付けて回避している。
- App.razor
<link rel="stylesheet" href="css/app.css?@Version" /> @code { private string Version = "v=" + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion + ""; }
WebSocketプロトコルを使用する
[サーバーの役割] ページで、[Web サーバー (IIS)] を展開し、[Web サーバー]、[アプリケーション開発] の順に展開し、[WebSocket プロトコル] を選択して、インストールする。
Blazor Server は、WebSocket では通信できない環境であっても、Long Polling 方式に動作を切り替えて、動作継続します。
WebSocket で通信できない環境では Blazor Server は動作しないのか?
※WebSocketを使用した場合、IISログファイルに通信ログが残らなくなる。
CA1416警告 Windowsでのみサポートされています
「警告 CA1416 この呼び出しサイトはすべてのプラットフォームで到達可能です。'xxxxx' は 'windows' でのみサポートされています。」
FreeSprire.pdf の印刷機能でカスタムサイズをセットした部分で警告の緑色の波線が出ました。
使用するメソッドの上に「[SupportedOSPlatform(“windows”)]」を記載することで、警告の緑色の波線を解除することが出来ます。
PrivateObjectの代替
単体テストでモジュールのPrivateメソッドにアクセスする「PrivateObject」が、.NET Coreでは使用できなくなった。
.NetCore ではPrivateObjectが無いのでその代替案