テーブル作成
Pages フォルダにある「_Host.cshtml」ファイルとなる。
wwwroot フォルダにある「index.html」ファイルとなる。
MatBlazor が一番有名な UI フレームワークなのですが、GitHub のスターがより多い「Ant Design Blazor」となる。
【無料】Blazor 対応の「使える」UI フレームワーク5選
NuGet参照したUIフレームワークの「BlazorStrap」など
以下の命名則の URL で、これらの静的 Web アセットを参照できる。
<script src="_content/BlazorStrap/popper.min.js"></script> <script src="_content/BlazorStrap/blazorStrap.js"></script>
NuGetのパッケージキャッシュフォルダは、デフォルトは「%userprofile%\.nuget\packages」
Blazor アプリケーションのスタートアップ ページは Index.razor です。
これを Login.razor に変更したい場合、下記のようにします。
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>
Web系の開発でアイコン表示などによく使われている。
現時点で、Font Awesome 6が最新である。
.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>
【Blazor】Razorコンポーネントのライフサイクルを解説する
実行の順番
メソッド名 | 呼び出し | 補足 |
---|---|---|
SetParametersAsync | パラメーターが設定されるタイミング | |
OnInitialized(Async) | コンポーネントが初期化されるタイミング | |
OnParametersSet(Async) | コンポーネントが初期化されるタイミングと、受け取るパラメータが更新されたタイミング | |
OnAfterRender(Async) | コンポーネントがレンダリングされたあと | 初回実行のときは、引数の firstRender が true となる |
ShouldRender | コンポーネントがレンダリングされるたび | true を返すとレンダリングを続行する |
StateHasChanged | レンダリングしたい任意のタイミング | コストが高いので不必要に呼び出さないこと |
Navigation.NavigateToで別ページに画面遷移させた時でもOnParametersSetAsyncが動作する。
Navigation.NavigateToで別ページに画面遷移させた時に例外エラー、原因として OnParametersSetAsync処理内で発生していた。 NavigateTo前に_isNextPageフラグをTrueにセットして使用して回避させた。
protected override async Task OnParametersSetAsync() { // 別ページに画面遷移する場合は何もしない。 if (_isNextPage) return;
ステートの変更通知はどのようなタイミングで発生するのかですが、おおまかに下記の3パターンで発生します。
2つ目と3つ目は暗黙的に StateHasChanged を呼び出している。
StateHasChanged はステートが変更されたことを通知するメソッドです。このメソッドを呼び出すと再レンダリング候補としてマークされます。
razorファイルの中にHTML要素とバインドするC#コードを合わせた記載はやめて、razorコンポーネントとC#コードを分離して記載する。
Blazorでコードビハインドでロジックとビューを分離して記述する
ロジックとビューを分離することで、下記のようなメリットなどが得られます。
raozrファイルと対になるようにcsのファイルを作成するだけです。
Counter.razor Counter.razor.cs(追加)
コード側のクラスには、partialキーワードを付けます。
例 public partial class Counter
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フレームワークでも、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/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で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>
bootstrap5では中東の言語で主流である右書きに対応したことからleft→start、right→endという概念の変更が導入されました。
これにより、ml-〇 → ms-〇にmr-〇 → me-〇 というようにクラス名が変更になりました。
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>"; } }
フォントサイズなど、bootstrap.css で定義されたサイズが優先されてしまう場合、important.css にてCSSを定義することにより優先させるようにする。
<link rel="stylesheet" href="css/important.css" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.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; } }
IsSubmit=“true” を付けることで、Enterキーでサブミットさせることが出来る。
<BSInput InputType="InputType.Text" IsSubmit="true" @bind-Value="Model.Input1" class="font-adjust" />
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() ?? ""; }
カスケーディングを使用してパラメーターを渡す。
カスケードとは、もともと「連なった小さな滝」という意味があります。
Blazor では滝が流れるイメージで、上位の階層から下位の階層にパラメーターを渡すことができる仕組みがあります。
これが「カスケーディングパラメーター」です。
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 を使用する。
NameValueCollection query = HttpUtility.ParseQueryString(new Uri(Navigation.Uri).Query); string key = query["Key"] ?? ""
Blazor WebAssembly - Get Query String Parameters with Navigation Manager
ブラウザー ストレージのデータの読み込みまたは保存が必要なすべてのコンポーネントで、@inject ディレクティブを使用して、次のいずれかのインスタンスを挿入します。
入れ子になったコンポーネントと入れ子になっていないコンポーネントでは、登録済みのメモリ内状態コンテナーを使用してデータへのアクセスを共有できます。
メソッド | 内容 |
---|---|
AddTransient() | インジェクション毎にインスタンスを生成 |
AddScoped() | リクエスト毎にインスタンスを生成 |
AddSingleton() | アプリケーション内で1つのインスタンスを生成 |
builder.Services.AddScoped<StateContainer>();
ディレクティブ記法でオブジェクトをインジェクトします。
@inject 【インジェクトするオブジェクトの型】 【インジェクトする変数名】 @inject StateService Service
上記の書き方以外にコードブックにて[Inject]属性を付与する記法があります。 今後コードビハインドで記述するならこっちの方が分かりやすいです。
[Inject] private StateService Service { get; set; } = default!;
Blazor Serverの場合、Directory.GetCurrentDirectory() を使用すればいい。
string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot");
Blazor ServerにWeb APIを追加する。
サンプル
下記コードをapp.Run()より前に、該当するところに追加する。
// Add MVC Controllers builder.Services.AddControllers(); app.MapRazorPages(); app.MapControllers();
Urlにてapiの後にコントローラー名を入れてアクセスすれば、値が返ってくる。
https://localhost:7072/api/Values
「@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() とする。
ボタンクリックイベントでは、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 を指定したたけでは、”does not have a property matching the name 'ValueChanged'“エラーとなり動作しない。
そのため、ParameterにValueChangedを定義する。
<CustomInput @bind-Value="@InputValue"></CustomInput> @code { public string InputValue = "Example"; }
<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 でサーバーとの接続障害からの復旧後、「再読込」をユーザに押させることなく自動でページ再読み込みする
Submitの先頭にawait.Task.Delay メソッドを実行すると、エラー用のメッセージモーダルが表示されなくなる現象があった。
async void を async Task に変更することで解決した。
async void Submit() ↓ async Task Submit()
jQueryコンポーネント または jQueryを使用しないJavaScriptコンポーネントをBlazorと連携する場合の注意点
下記のようにすれば、onchangeイベントを呼ぶことが出来る。この方法だと DotNet.invokeMethodAsync を使わずに済む。
$("#justAnotherInputBox").on('change', function () { var myElement = $(this)[0]; var event = new Event('change'); myElement.dispatchEvent(event); console.log("Change"); });
<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); } }
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);
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; } }
Structured Logging in ASP.NET Core With log4net
<?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
{ "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でファイル更新時にブラウザキャッシュを更新する方法
よって、ファイルバージョンを付けて回避している。
<link rel="stylesheet" href="css/app.css?@Version" /> @code { private string Version = "v=" + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion + ""; }
[サーバーの役割] ページで、[Web サーバー (IIS)] を展開し、[Web サーバー]、[アプリケーション開発] の順に展開し、[WebSocket プロトコル] を選択して、インストールする。
Blazor Server は、WebSocket では通信できない環境であっても、Long Polling 方式に動作を切り替えて、動作継続します。
WebSocket で通信できない環境では Blazor Server は動作しないのか?
※WebSocketを使用した場合、IISログファイルに通信ログが残らなくなる。
「警告 CA1416 この呼び出しサイトはすべてのプラットフォームで到達可能です。'xxxxx' は 'windows' でのみサポートされています。」
FreeSprire.pdf の印刷機能でカスタムサイズをセットした部分で警告の緑色の波線が出ました。
使用するメソッドの上に「[SupportedOSPlatform(“windows”)]」を記載することで、警告の緑色の波線を解除することが出来ます。
単体テストでモジュールのPrivateメソッドにアクセスする「PrivateObject」が、.NET Coreでは使用できなくなった。
.NetCore ではPrivateObjectが無いのでその代替案