هذا هو تطبيق عينة من البلازور يسمح 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 مع فكرة أنه يجب على العميل إجراء مكالمات HTTP إلى API REST. لست متأكدًا من أن هذا النموذج لاستخدام إصدار WASM من .NET's HttpClient أفضل أو أسوأ من العميل الذي يعتمد على JavaScript يستخدم fetch ، ولكنه بالتأكيد يتيح مشاركة نماذج طلب/استجابة بين العميل والخادم.
أحد الفواق التي واجهتها هي أنه ليس من التافهة نقل الحجج إلى معالجات الأحداث. على سبيل المثال ، عند النقر فوق الزر Delete لحذف شخص ما ، أريد تمرير معرف الشخص إلى معالج الأحداث. انتهى بي الأمر باستخدام تعبير lambda:
< button @onclick =" @((e) => DeletePerson(person.Id)) " > Delete </ button >هذا يعمل ، لكنه يأتي مع التحذير:
قد يؤدي إنشاء عدد كبير من مندوبي الأحداث في حلقة إلى ضعف أداء تقديم. لمزيد من المعلومات ، راجع أفضل ممارسات أداء ASP.NET Core Blazor.
حافظت على الخطأ العالمي في التعامل مع واجهة المستخدم ، لكنني أرغب في تحسين هذه التجربة. هذا ملحوظ بشكل خاص عندما نتحقق من رموز حالة استجابة 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 ) ) ) ;
} إن استجابة 400 و 200 من نفس الشكل ( SomeResponse ) تبسط ويضيف المرونة إلى العميل الذي يدعو واجهة برمجة التطبيقات ، لأنه يمكن دائمًا إلغاء تخصيص الاستجابة في نفس النوع ، ثم يمكن (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# ، نظرًا لأنها عادة ما تكون بطانات واحدة فقط وهي أكثر تعبيرية من 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 ، نحصل على Result<Error, Success> النوع الذي يلتف .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 Core في الذاكرة ، والتي تتطلب منك عدم استخدام أي ميزات محددة لقاعدة بيانات وتغيير آلية تخزين بيانات الدعم ، لذلك عندما تقوم بتشغيل اختبارات التكامل بهذه الطريقة ، فأنت لا تختبر نفس مخزن البيانات. إذا كنت أستخدم قاعدة بيانات مثل MySQL أو Postgres ، فسأقوم بإنشاء تطبيق Docker Compens لاستضافة قاعدة بيانات اختبار.
لقد قمت بتسجيل الدخول لاختبارات التكامل مع ITestOutputHelper مع meziantou.extensions.logging.xunit. يتيح ذلك رؤية السجلات من الخادم عند تشغيل اختبارات تكامل WebApplicationFactory .
الممارسة الموصى بها هنا هي كتابة xunit [Fact] في ملفات .razor . هذا يبدو غريبًا في البداية ، لكنه يسمح بدعم المحرر الغني لمكونات Blazor.
يمكن أن يصبح تشغيل أوامر dotnet للترحيل وتشغيل التطبيق متعبًا لنسخ/لصق ، لذلك بالنسبة لمعظم المشاريع أقوم بإنشاء برنامج نصي "Makefile" على نمطه لتشغيل مهام DEV المحلية المشتركة. لقد قمت بالتدوين حول هذا الأمر بالتفصيل هنا: https://kevinareed.com/2021/04/14/creating-a-command cli-in-powershell/.
