これは、大勢の人々を可能にするサンプルブレザーアプリケーションです。これは、次のスタックを紹介します。
実行中のアプリケーションを参照してください:Blazor-Wasm-Crud Dot Fly Dot Dev 
dotnet ef (> = v7.0.13)をインストールするdotnet tool install -- global dotnet - ef -- version 7.0 . 13SQLiteデータベースを作成および移行し、サーバーを起動します。
.local.ps1 runこれがアプリケーションを実行するのが初めてである場合、これは.srcPeople.BlazorWasmServerpeople.dbを作成します。
https:// localhost:7102で実行中のアプリケーションを表示します

テストを実行します
.local.ps1 test.local.ps1 migrateデータベースモデルに変更を加えると、新しい移行を作成する必要があります。これにより.srcPeople.InfrastructureMigrationsに新しい移行ファイルが作成されます。
.local.ps1 migration MyNewMigrationこのアプリケーションは、クライアントがREST APIにHTTP呼び出しを行う必要があるという考えを備えたWASMホスティングモデルを使用して構築されています。 .NETのHttpClientのWASMバージョンを使用するこのモデルが、 fetchを使用するJavaScriptベースのクライアントよりも優れている、または悪いことを確認してください。
私が出会った1つのしゃっくりは、イベントハンドラーに議論を渡すことは些細なことではないということです。たとえば、削除ボタンをクリックして人を削除する場合、その人のIDをイベントハンドラーに渡したいと思います。私は最終的にラムダの式を使用しました:
< button @onclick =" @((e) => DeletePerson(person.Id)) " > Delete </ button >これは機能しますが、警告が付いています。
ループで多数のイベント代表を作成すると、レンダリングパフォーマンスが低下する可能性があります。詳細については、ASP.NET Core Blazor Performance Best Practicesを参照してください。
グローバルなエラー処理UIを維持しましたが、このエクスペリエンスを改善したいと考えています。これは、API応答ステータスコードをで確認するときに特に顕著です
response . EnsureSuccessStatusCode ( ) ; ...しかし、 try/catchブロックを導入せずにユーザーに有用なメッセージを表示する方法は現在ありません。
ここで注意すべき主なことは、コントローラーが2つのことをしていることです。
IPeopleServiceに電話してください基本的なパターンは次のとおりです。
[ HttpPost ]
public IResult HandleSomePost ( [ FromBody ] SomeRequest request )
{
var result = _peopleService . DoSomething ( request ) ;
return result . Match (
( error ) => Results . BadRequest ( new SomeResponse ( error . Value ) ) ) ,
( success ) => Results . Ok ( new SomeResponse ( success . Value ) ) ) ;
}同じ形状( SomeResponse )の400と200の応答は、APIを呼び出すクライアントに柔軟性を簡素化し、同じタイプにゆるくすることができ、(1) .error === true応答がエラーであるかどうかを判断するか、(2)HTTPステータスコードを確認できるかどうかを確認できます。返されたこのオブジェクトの形状は次のように見えます:
{
"error" : false ,
"errors" : [],
"person" : {
"id" : " 00000000-0000-0000-0000-000000000000 " ,
"firstName" : " John " ,
"lastName" : " Doe "
}
}これにより、必要に応じて将来の応答を拡張できます。たとえば、 warningsアレイを追加したい場合は、クライアントにとって壊れた変更でなくてもそうすることができます。
{
"error" : false ,
"errors" : [],
"warnings" : [],
"person" : {
"id" : " 00000000-0000-0000-0000-000000000000 " ,
"firstName" : " John " ,
"lastName" : " Doe "
}
} IPeopleServiceとそのPerson 、 OneOfタイプで応答します。例えば:
interface IPeopleService {
Result < PersonNotFound , Person > FindPerson ( Guid id ) ;
}そして
class Person {
private Person ( ) { }
public static Result < PersonInvalid , Person > Create ( PersonRequest request ) {
if ( /* request is invalid */ ) {
return new PersonInvalid ( ) ;
}
return new Person ( ) {
// ...
} ;
}
}これにより、ドメインとアプリケーションが何がうまくいかないかについて明示的になります。これにより、発信者(アプリケーションとコントローラー)がすべてのリターンシナリオを処理するようになります。また、検証のために例外をスローするのを防ぎます。これは非常に頻繁に見られます。
class Person {
public Person ( string name ) {
if ( string . IsNullOrWhiteSpace ( name ) ) {
// bad!
throw new PersonInvalidException ( ) ;
}
Name = name ;
}
}例外は例外的でなければなりません。 Person無効になる可能性があると予想される場合は、発信者にそのように通知する必要があります。これには、 newキーワードを使用するのではなく、静的ファクトリーメソッドを使用する必要がありますが、それは価値があると思うトレードオフです。
Oneはいくつかの基本的なリターンタイプがありますが、C#レコードを使用して独自のリターンタイプを作成することを好みます。これは、通常1つのライナーのみであり、OneofのSuccess / NotFound /などよりも表現力豊かです。
public record PersonNotFound ( Guid Id ) {
public string Message => $ "Person with ID { Id } not found" ;
}
public record PersonBirthCannotBeInFuture ( ) ;
public record PersonInvalid ( FieldErrors Errors ) ; Oneof.monadsがなければ、 PeopleService .IsT1をチェックし、 .AsT0および.AsT1を使用する必要があります。
public OneOf < PersonAddError , PersonResponse > AddPerson ( PersonAddEditRequest request )
{
var result = Person . Add ( _clock , request ) ;
if ( result . IsT0 )
{
return new PersonAddError ( result . AsT0 ) ;
}
var person = result . AsT1 ;
_repository . InsertPerson ( person ) ;
return person . ToPersonResponse ( ) ;
} Oneof.monadsを使用すると、より表現力豊かな.IsError()で.IsT0をラップするResult<Error, Success>タイプが得られます。
public Result < PersonAddError , PersonResponse > AddPerson ( PersonAddEditRequest request )
{
var result = Person . Add ( _clock , request ) ;
if ( result . IsError ( ) )
{
return new PersonAddError ( result . ErrorValue ( ) ) ;
}
var person = result . SuccessValue ( ) ;
_repository . InsertPerson ( person ) ;
return person . ToPersonResponse ( ) ;
}DbContextリポジトリとして直接使用することについて多くの議論がありますが、これにより、モック/偽のDbSetを作成する必要があるため、ユニットテストがより困難になります。このアプリケーションでは、ユニットテスト用の偽のリポジトリを簡単に作成できるようにする独自のリポジトリを使用することを好みました。
interface IPeopleRepository {
void Insert ( Person person ) ;
// ...
}
class PeopleEfRepository : IPeopleRepository {
public PeopleEfRepository ( PeopleDbContext db ) {
_db = db ;
}
public void Insert ( Person person ) {
_db . People . Add ( person ) ;
_db . SaveChanges ( ) ;
}
}
class FakePeopleRepository : IPeopleRepository {
public List < Person > People { get ; } = new ( ) ;
public void Insert ( Person person ) {
People . Add ( person ) ;
}
}SQLiteを使用すると、データベースサーバーをインストールすることなく、すぐに起きて実行できます。また、インメモリバージョンは統合テストに最適です。これにより、「実際の」データベースプロバイダーを使用できるが、各テスト後にデータベースのクリーンアップを心配する必要はないためです。多くの場合、統合テストはメモリEFコアデータベースに切り替わります。これにより、データベース固有の機能を使用せず、バッキングデータストレージメカニズムを変更する必要があるため、統合テストをこのように実行すると、同じデータストアを実際にテストしません。 MySQLやPostgresなどのデータベースを使用する場合、テストデータベースをホストするためにDocker Composeアプリケーションを作成します。
meziantou.extensions.logging.xunitを使用したITestOutputHelperを使用した統合テスト用のロギングを配線しました。これにより、 WebApplicationFactory統合テストを実行するときにサーバーからログを表示できます。
ここで推奨される練習は、Xunit [Fact]を.razorファイルに記述することです。これは最初は奇妙に感じますが、Blazorコンポーネントの豊富な編集者サポートが可能になります。
移行のためにdotnetコマンドを実行し、アプリケーションを実行するとコピー/ペーストが疲れる可能性があるため、ほとんどのプロジェクトでは、一般的なローカル開発タスクを実行する「MakeFile」スタイルのPowerShellスクリプトを作成します。これについて詳しく説明しました:https://kevinareed.com/2021/04/14/creating-a-commandに拠点を置くcli-in-powershell/。
