這是一個允許人crud的樣本大發應用程序。這顯示了以下堆棧:
請參閱運行應用程序:Blazor-Wasm-Crud Dot fly dot dev 
dotnet ef (> = = V7.0.13) dotnet tool install -- global dotnet - ef -- version 7.0 . 13創建和遷移SQLITE數據庫,然後啟動服務器。
.local.ps1 run如果這是第一次運行該應用程序,這將創建.srcPeople.BlazorWasmServerpeople.db 。
在https:// localhost:7102查看運行應用程序

運行測試
.local.ps1 test.local.ps1 migrate如果您更改數據庫模型,則需要創建一個新的遷移。這將創建一個新的遷移文件.srcPeople.InfrastructureMigrations 。
.local.ps1 migration MyNewMigration該應用程序是使用WASM託管模型構建的,其想法是客戶端應向REST API進行HTTP調用。我不確定這種使用.NET的HttpClient的WASM版本的模型比使用fetch的JavaScript客戶端更好或更糟,但是它肯定允許在客戶端和服務器之間共享請求/響應模型。
我遇到的一個打ic是,將爭論傳遞給事件處理人員並不是很小的。例如,在單擊“刪除”按鈕刪除一個人時,我想將人的ID傳遞給活動處理程序。我最終使用了lambda表達式:
< button @onclick =" @((e) => DeletePerson(person.Id)) " > Delete </ button >這起作用,但帶有警告:
在循環中創建大量的事件代表可能會導致渲染性能差。有關更多信息,請參見ASP.NET Core Blazor績效最佳實踐。
我保留了全球錯誤處理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 ) ) ) ;
} 400和200的響應具有相同的形狀( SomeResponse )簡化並為調用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關鍵字,而是使用靜態工廠方法,但這是我認為值得的權衡。
Oneof具有一些基本的返回類型,但是我更喜歡使用C#記錄創建自己的返回類型,因為它們通常只有一個襯裡,並且比Oneof的Success / NotFound / etc更具有表現力。
public record PersonNotFound ( Guid Id ) {
public string Message => $ "Person with ID { Id } not found" ;
}
public record PersonBirthCannotBeInFuture ( ) ;
public record PersonInvalid ( FieldErrors Errors ) ; 如果沒有一個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 ( ) ;
}使用One.monads,我們將獲得Result<Error, Success>將.IsT0包裝的type tith .ist0更具表現力.IsError() :
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組成的應用程序來託管測試數據庫。
我已經將LIGNG與Meziantou.extensions.logging.xunit的ITestOutputHelper進行了登錄。這允許在運行WebApplicationFactory集成測試時從服務器中查看日誌。
建議的做法是在.razor文件中編寫您的xunit [Fact] s。一開始這感覺很奇怪,但可以提供大型構件的豐富編輯器支持。
運行用於遷移和運行應用程序的dotnet命令可能會變得累人/粘貼,因此,對於大多數項目,我創建了一個“ makefile”樣式的powerShell腳本來運行常見的本地開發任務。我在此處詳細介紹了這一點:https://kevinareed.com/2021/04/14/creating-a-command-cli-cli-cli-in-cli-in-powershell/。
