นี่คือแอปพลิเคชันตัวอย่าง Blazor ที่อนุญาตให้คนอื่น ๆ สิ่งนี้แสดงสแต็คต่อไปนี้:
ดูแอปพลิเคชันที่กำลังทำงานอยู่: Blazor-Was 
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 ด้วยแนวคิดที่ว่าไคลเอนต์ควรโทร HTTP ไปยัง REST API ฉันไม่แน่ใจว่ารุ่นของการใช้ HttpClient รุ่น WASM ของ. NET นั้นดีกว่าหรือแย่กว่าไคลเอนต์ที่ใช้ JavaScript ที่ใช้ fetch แต่แน่นอนว่าอนุญาตให้แชร์โมเดลการร้องขอ/การตอบกลับระหว่างไคลเอนต์และเซิร์ฟเวอร์
อาการสะอึกอย่างหนึ่งที่ฉันพบคือมันไม่ได้เป็นเรื่องเล็กน้อยที่จะผ่านข้อโต้แย้งไปยังตัวจัดการเหตุการณ์ ตัวอย่างเช่นเมื่อคลิกปุ่มลบเพื่อลบบุคคลฉันต้องการส่ง ID ของบุคคลไปยังตัวจัดการเหตุการณ์ ฉันลงเอยด้วยการใช้นิพจน์แลมบ์ดา:
< button @onclick =" @((e) => DeletePerson(person.Id)) " > Delete </ button >ผลงานนี้มาพร้อมกับคำเตือน:
การสร้างผู้แทนเหตุการณ์จำนวนมากในลูปอาจทำให้ประสิทธิภาพการเรนเดอร์ไม่ดี สำหรับข้อมูลเพิ่มเติมดูที่ ASP.NET Core Blazor Performance แนวทางปฏิบัติที่ดีที่สุด
ฉันรักษาข้อผิดพลาดระดับโลกในการจัดการ 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 เนื่องจากสามารถ deserialize การตอบสนองในประเภทเดียวกันและจากนั้นสามารถตรวจสอบ .error === true เพื่อตรวจสอบว่าการตอบสนองหรือไม่ รูปร่างของวัตถุนี้กลับมาดูเหมือน:
{
"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# Records เนื่องจากพวกเขามักจะมีเพียงหนึ่งเดียวเท่านั้นและมีการแสดงออกมากกว่า Success ของ Oneof / 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 เราจะได้ Result<Error, Success> .IsError() ที่ห่อหุ้ม .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 ช่วยให้เราได้อย่างรวดเร็วและทำงานโดยไม่ต้องติดตั้งเซิร์ฟเวอร์ฐานข้อมูล นอกจากนี้เวอร์ชันในหน่วยความจำนั้นยอดเยี่ยมสำหรับการทดสอบการรวมเนื่องจากมันช่วยให้เรายังคงใช้ผู้ให้บริการฐานข้อมูล "ของจริง" แต่ไม่ต้องกังวลเกี่ยวกับการทำความสะอาดฐานข้อมูลหลังจากการทดสอบแต่ละครั้ง บ่อยครั้งที่การทดสอบการรวมจะเปลี่ยนไปใช้ฐานข้อมูลหลัก EF ในหน่วยความจำซึ่งต้องการให้คุณไม่ใช้คุณลักษณะเฉพาะฐานข้อมูลใด ๆ และเปลี่ยนกลไกการจัดเก็บข้อมูลสำรองดังนั้นเมื่อคุณเรียกใช้การทดสอบการรวมในลักษณะนี้คุณไม่ได้ทดสอบที่เก็บข้อมูลเดียวกัน หากใช้ฐานข้อมูลเช่น MySQL หรือ Postgres ฉันจะสร้างแอปพลิเคชัน Docker Compose เพื่อโฮสต์ฐานข้อมูลทดสอบ
ฉันมีสายเข้าสู่ระบบสำหรับการทดสอบการรวมด้วย ITestOutputHelper ด้วย meziantou.extensions.logging.xunit สิ่งนี้จะช่วยให้เห็นบันทึกจากเซิร์ฟเวอร์เมื่อเรียกใช้การทดสอบการรวม WebApplicationFactory
การปฏิบัติที่แนะนำที่นี่คือการเขียน XUNIT [Fact] ของคุณในไฟล์. .razor สิ่งนี้ให้ความรู้สึกแปลก ๆ ในตอนแรก แต่ช่วยให้การสนับสนุนโปรแกรมแก้ไขที่หลากหลายของส่วนประกอบ Blazor
การเรียกใช้คำสั่ง dotnet สำหรับการย้ายถิ่นและการรันแอปพลิเคชันอาจกลายเป็นเรื่องน่าเบื่อหน่ายในการคัดลอก/วางดังนั้นสำหรับโครงการส่วนใหญ่ฉันสร้างสคริปต์ PowerShell สไตล์ "MakeFile" เพื่อเรียกใช้งาน DEV ท้องถิ่นทั่วไป ฉันเขียนบล็อกเกี่ยวกับเรื่องนี้โดยละเอียดที่นี่: https://kevinareed.com/2021/04/14/creating-a-plemand-pleli-in-in-powershell/
