이것은 많은 사람들을 허용하는 샘플 블레이저 응용 프로그램입니다. 여기에는 다음 스택이 표시됩니다.
실행중인 응용 프로그램 : 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 이 응용 프로그램은 클라이언트가 HTTP 호출을 REST API로 호출해야한다는 아이디어와 함께 WASM 호스팅 모델을 사용하여 구축됩니다. .NET의 HttpClient 의 WASM 버전을 사용하는이 모델이 fetch 사용하는 JavaScript 기반 클라이언트보다 낫거나 나쁘지는 않지만 클라이언트와 서버간에 요청/응답 모델을 공유 할 수 있습니다.
내가 실행 한 딸꾹질 중 하나는 이벤트 핸들러에게 인수를 전달하는 것이 사소한 것이 아니라는 것입니다. 예를 들어, 삭제 버튼을 클릭하여 사람을 삭제할 때 사람의 ID를 이벤트 핸들러로 전달하고 싶습니다. 나는 Lambda 표현을 사용하여 끝났습니다.
< button @onclick =" @((e) => DeletePerson(person.Id)) " > Delete </ button >이것은 작동하지만 경고와 함께 제공됩니다.
루프에서 많은 이벤트 대의원을 만들면 렌더링 성능이 저하 될 수 있습니다. 자세한 내용은 ASP.NET Core Blazor Performance 모범 사례를 참조하십시오.
전 세계 오류 처리 UI를 유지했지만이 경험을 향상시키고 싶습니다. API 응답 상태 코드를
response . EnsureSuccessStatusCode ( ) ; ...하지만 현재 try/catch 블록을 도입하지 않고 사용자에게 유용한 메시지를 표시 할 방법이 없습니다.
여기서 주목해야 할 것은 컨트롤러가 두 가지를 수행한다는 것입니다.
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 키워드를 사용하지 않고 정적 공장 방법을 사용해야하지만 그만한 가치가 있다고 생각되는 트레이드 오프입니다.
하나는 몇 가지 기본 반환 유형을 가지고 있지만 C# 레코드를 사용하여 자체 반환 유형을 만드는 것이 좋습니다. 일반적으로 하나의 라이너에 불과하며 하나의 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() 사용하면 Result<Error, Success> 유형을 얻을 수 있습니다 .IsT0
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를 사용하면 데이터베이스 서버를 설치하지 않고도 빠르게 일어나고 실행할 수 있습니다. 또한 인 메모리 버전은 통합 테스트에 적합합니다. "실제"데이터베이스 제공 업체를 사용할 수 있지만 각 테스트 후에 데이터베이스를 정리할 걱정이 필요하지 않기 때문입니다. 통합 테스트는 종종 Memory EF Core 데이터베이스로 전환되며 데이터베이스 별 기능을 사용하지 않아야하며 백킹 데이터 저장 메커니즘을 변경해야하므로 이러한 방식으로 통합 테스트를 실행할 때 실제로 동일한 데이터 저장소를 테스트하지 않습니다. MySQL 또는 Postgres와 같은 데이터베이스를 사용하는 경우 Test 데이터베이스를 호스팅하기 위해 Docker Compose 응용 프로그램을 작성합니다.
Meziantou.extensions.logging.xunit을 사용하여 ITestOutputHelper 사용하여 통합 테스트를 위해 로깅을 연결했습니다. 이를 통해 WebApplicationFactory 통합 테스트를 실행할 때 서버에서 로그를 볼 수 있습니다.
여기에서 권장되는 관행은 xunit [Fact] 을 .razor 파일로 작성하는 것입니다. 이것은 처음에는 이상하게 느껴지지만 Blazor 구성 요소의 풍부한 편집기 지원이 가능합니다.
마이그레이션 및 응용 프로그램 실행에 대한 dotnet 명령을 실행하면 복사/붙여 넣기에 피곤해 질 수 있으므로 대부분의 프로젝트에서는 "MakeFile"Style PowerShell 스크립트를 생성하여 일반적인 로컬 개발 작업을 실행합니다. 나는 이것에 대해 자세히 설명했다 : https://kevinareed.com/2021/04/14/creating-a-command 기반 Cli-in-powershell/.
