BlazingPizza: تطبيق Blazor من البداية إلى النهاية. الجزء 2. إضافة مكون

مرحبا جميعا! لجميع أولئك الذين يرغبون في معرفة المزيد عن Blazor. اليوم سنواصل إنشاء موقعنا لمطعم بيتزا ، أي أننا سننشئ وحدة تحكم واجهة برمجة تطبيقات الويب ونحاول عرض البيانات التي تأتي منه على مكون Blazor.

نظرًا لأن طلبنا يتعلق بالبيتزا ، سيكون من المنطقي إضافة فئة تمثل منتجنا الرئيسي على الفور. أطلق

عليه BasePizza وأضفه إلى مشروع BlazingPizza.DomainModels . في رأيي ، إضافة فئة جديدة أمر رائع جدًا تم تنفيذه في Rider ، يظهر مربع حوار غير محظور ، نقوم بإدخال اسم الفئة ثم يمكننا اختيار ما نحتاج إلى إنشائه:



بعد ذلك ، سيظهر مربع حوار مع طلب لإضافة ملف إلى git ، وسوف نجيب بالإيجاب.

محتوى الفصل:

public class BasePizza
{
  public int Id { get; set; }
    
    public string Name { get; set; }
    
    public decimal BasePrice { get; set; }
    
    public string Description { get; set; }
    
    public string ImageUrl { get; set; }
}

إنه قالب لبعض أنواع البيتزا ، وبعد ذلك يمكن تكوينه كما نحب ، ونغير الحجم ، ونضيف الإضافات والمزيد. يبدو لي أن أسماء الحقول تتحدث عن نفسها.

في مشروع BlazingPizza.DomainPizza ، سيكون لدينا فصول تمثل المجال التجاري لتطبيقنا. أي أنه لا ينبغي لهم ولن يعرفوا شيئًا عن كيفية تخزين بياناتنا أو كيفية عرضها. فقط معلومات حول كائن الأعمال ، أي بيتزا.

بعد ذلك ، نحتاج إلى شيء ما لنقل هذه البيانات إلى العميل بطريقة أو بأخرى. للقيام بذلك، انتقل إلى BlazingPizza.Server المشروع و إضافة PizzasController لل تحكم مجلد :

public class PizzasController : Controller
{
    // GET
    public IActionResult Index()
    {
        return View();
    }
}

نحتاج إلى طريقة تعطينا قائمة بجميع أساسيات البيتزا.

بالإضافة إلى إضافة طريقة ، تحتاج إلى القيام ببعض الخطوات البسيطة:

  1. دعنا نضع علامة على وحدة التحكم بسمة [ApiController] التي تعطي بعض المزايا ، على وجه الخصوص ، الإرجاع التلقائي لرمز 400 إذا لم ينجح النموذج في التحقق ، بدونها وحدة تحكم MVC عادية تمنح View.
  2. [Route(«pizzas»)]. Attribute Routing, , Conventional Routing, . “pizzas” ? http{s}://hostName/pizzas/{}
    .
  3. Controller ControllerBase, MVC .

حسنًا ، على سبيل المثال ، قدمنا مضيفًا محليًا : 5000 / طلب بيتزا على أمل الحصول على قائمة بجميع البيتزا ولم يحدث شيء. مرة أخرى ، الصفقة في الاتفاقات.

إذا كان طلب Get ، فيجب إما أن يكون لدينا طريقة ( Action من حيث Asp.Net ) مميزة بسمة [HttpGet] أو ، حتى أكثر وضوحًا ، مجرد طريقة تسمى Get وهذا كل شيء! كل شيء آخر .الشبكة والتفكير سيفيداننا.
لذا قم بإعادة تسمية طريقة الفهرس الوحيدة التي يجب الحصول عليها. قم بتغيير نوع القيمة المرتجعة إلى IEnumerable <BasePizza>، لا تنس أن تضيف ما يلزم باستخدام. حسنًا ، أدخل مؤقتًا كعبًا لم يتم تنفيذ الطريقة من أجل تجميع الشفرة بطريقة أو بأخرى والتأكد من عدم وجود أخطاء.

نتيجة لذلك ، سيبدو PizzasController.cs كما يلي:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using BlazingPizza.DomainModels;

namespace BlazingPizza.Server.Controllers
{
    [ApiController]
    [Route("pizzas")]
    public class PizzasController : ControllerBase
    {
        // GET
        public IEnumerable<BasePizza>  Get()
        {
            throw new NotImplementedException();
        }
    }
}

الآن ، قم بتشغيل تطبيق التصحيح ، وهو زر به خطأ أخضر.



وتأكد من تكوين الطرق بشكل صحيح. يمكن رؤية المنفذ الذي تريد إجراء الطلبات عليه في علامة التبويب وحدة التحكم :



في حالتنا ، يكون 5000 ، إذا قمت بتقديم طلب على طول المسار المحلي : 5000 / pizzas ، فإننا ننتقل إلى الإجراء Get ونلتقط NotImplementedException. بمعنى ، في حين أن وحدة التحكم الخاصة بنا لا تفعل شيئًا مفيدًا ، فإنها ببساطة تقبل الطلبات وتفشل مع وجود خطأ.

نعيد البيانات من وحدة التحكم


حان الوقت لجعل التعليمات البرمجية لدينا تفعل شيئًا مفيدًا ، مثل إعادة البيتزا. وحتى الآن، ونحن لم تنفذ طبقة البيانات، لذلك نعود مجرد بضع البيتزا من وجهة نظرنا العمل . للقيام بذلك ، قم بإرجاع صفيف يتكون من كائنين BasePizza . ستبدو طريقة Get مثل المثال أدناه:

// GET
public IEnumerable<BasePizza>  Get()
{
    return new[]
    {
        new BasePizza()
        {
            BasePrice = 500,
            Description = "     ",
            Id = 0,
            ImageUrl = "img/pizzas/pepperoni.jpg"
        },
        new BasePizza()
        {
            BasePrice = 400,
            Description = "   ",
            Id = 1,
            ImageUrl = "img/pizzas/meaty.jpg"
        },
    };
}

ستكون نتيجة الطلب في المتصفح كما يلي:



قم بإعداد الصفحة الرئيسية


الجزء المرئي من التطبيق في مكونات .razor في BlazingPizza.Client المشروع . نحن مهتمون بـ Index.razor في مجلد الصفحات ، وافتحه واحذف جميع محتوياته التي ورثناها من المشروع الافتراضي. ولنبدأ بإضافة ما نحتاج إليه حقًا.

1. أضف:الصفحة"/" يتم استخدام هذا التوجيه لتكوين توجيه العميل ويقول أنه سيتم تحميل عنصر التحكم هذا افتراضيًا ، أي إذا انتقلنا إلى عنوان تطبيق localhost : 5000 / بدون أي / فهرس / / Pizzas أو أي شيء آخر.

2.حقنHttpClient HttpClient باستخدام التوجيهحقن أضف خدمة مثل HttpClient إلى صفحتنا واستدعي الكائن HttpClient أيضًا . تم بالفعل تكوين كائن من نوع HttpClient لنا من خلال إطار عمل Blazor ، لذا يمكننا فقط تقديم الطلبات التي نحتاجها. يسمى هذا النوع من الحقن Property Injection ، ولا يتم دعم التنفيذ الأكثر شيوعًا من خلال المنشئ ، وكما يقول المطورون ، من غير المحتمل أن يظهر على الإطلاق ، ولكن هل هو مطلوب هنا؟

3. إضافة توجيه

 @code{

 }

هناك حاجة خاصة لاستضافة كود العميل C # ، وهو نفس الرمز البديل لـ JavaScript. داخل هذه الكتلة ، نضع مجموعة من الكائنات من النوع BasePizzaViewModel

IEnumerable<BasePizzaViewModel> PizzaViewModels;

4. كما فهمت بالفعل ، BasePizzaViewModel غير موجود ، لقد حان الوقت لإنشائه ، سيكون هذا النموذج مشابهًا تمامًا لنموذج مجال BasePizza ، باستثناء أنه سيكون له نص تعبير GetFormattedBasePrice الذي يعرض سعر البيتزا الأساسية بالصيغة التي نحتاجها. فإن نموذج إضافة إلى جذر المشروع BlazingPizza.ViewModels ملف BasePizzaViewModel.cs :

public class BasePizzaViewModel
{
    public int Id { get; set; }
    
    public string Name { get; set; }
    
    public decimal BasePrice { get; set; }
    
    public string Description { get; set; }
    
    public string ImageUrl { get; set; }
    
    public string GetFormattedBasePrice() => BasePrice.ToString("0.00");
}

5. العودة إلى Index.razor والكتلةالشفرة، أضف رمزًا للحصول على جميع البيتزا المتاحة. سنضع هذا الكود في طريقة OnInitializedAsync المتزامنة :

protected async override Task OnInitializedAsync() {
	
}

يتم استدعاء هذه الطريقة بعد تهيئة المكون وفي وقت المكالمة ، تتم تهيئة جميع معلماته بالفعل بواسطة المكون الرئيسي. في ذلك ، يمكنك تنفيذ بعض العمليات غير المتزامنة ، وبعد ذلك يلزم تحديث الحالة. في وقت لاحق سأتحدث عن هذا بمزيد من التفصيل. يتم استدعاء الأسلوب مرة واحدة فقط عند إنشاء المكون.

أخيرًا ، أضف البيتزا في هذه الطريقة:

var queryResult = await HttpClient.GetJsonAsync<IEnumerable<BasePizza>>("pizzas");

pizzas - مسار نسبي تمت إضافته إلى القاعدة وقد تم تعيينه بالفعل من قبل Blazor . على النحو التالي من توقيع الطريقة ، يتم طلب البيانات من خلال طلب الحصول ثم يحاول العميل إجراء تسلسل لها في IEnumerable <BasePizza> .

6. نظرًا لأننا تلقينا بيانات من النوع الخاطئ الذي نريد عرضه في المكون ، فإننا نحتاج إلى الحصول على كائنات من النوع BasePizzaViewModel ، وسوف تستخدم Linq وطريقتها Select لتحويلها من المجموعة الواردة إلى كائنات من النوع الذي نخطط لاستخدامه. أضف طريقة OnInitializedAsync إلى النهاية :

PizzaViewModels = queryResult.Select(i => new BasePizzaViewModel()
{
    BasePrice = i.BasePrice,
    Description = i.Description,
    Id = i.Id,
    ImageUrl = i.ImageUrl,
    Name = i.Name
});

في وقت لاحق سأوضح كيفية القيام بذلك دون كتابة رمز القالب هذا ، ولكن الآن ، دعنا نتركه كما هو. يبدو أن لدينا كل ما نحتاجه ويمكننا المتابعة لعرض البيانات المستلمة.

7. فوق توجيه الكود ، أضف كود html ، والذي سيكون داخله البيتزا نفسها:

<div class="main">
    <ul class="pizza-cards">

    </ul>
</div>

كما ترى ، قائمة ul التي تحمل اسم الفصل المتحدث "بطاقات بيتزا" فارغة حتى الآن ، سنصلح هذا الإشراف:

@foreach (var pizza in PizzaViewModels)
{
    <li style="background-image: url('@pizza.ImageUrl')">
        <div class="pizza-info">
            <span class="title">@pizza.Name</span>
                @pizza.Description
            <span class="price">@pizza.GetFormattedBasePrice()</span>                    
        </div>
    </li>
}

كل المرح هنا هو داخل الحلقة. foreach(var {item} in {items})
هذا هو ترميز Razor النموذجي الذي يسمح لنا باستخدام قوة C # في نفس الصفحة مثل شفرة html العادية . الشيء الرئيسي هو وضع الرمز "@" أمام الكلمات الأساسية والمتغيرات اللغوية .

داخل الحلقة ، نصل ببساطة إلى خصائص كائن البيتزا .

في النهاية ، نعرض السعر الأساسي المنسق للبيتزا باستخدام طريقة GetFormattedBasePrice . هذا، بالمناسبة، هو الفرق بين BasePizza نموذج المجال ولها ViewModel التمثيلات ، لأن هذه الطريقة تحتوي على أبسط منطق لعرض السعر بالتنسيق المطلوب ، والذي لا نحتاجه على مستوى الخدمة ، حيث نتعامل مع السعر بطريقة ما ، لكننا لا نعرضه في أي مكان.

نعرض البيانات المستلمة في المتصفح


لقد حصلنا على جميع البيانات اللازمة لعرضها. حان الوقت لإطلاق تطبيقنا والتأكد من أن كل شيء يعمل. نضغط على زر Debug (في Rider ، يقوم زر Run بتشغيل التطبيق بدون إمكانية التصحيح ).

ويا هو ، لا شيء يعمل أيضًا :) افتح وحدة التحكم ( F12 ) وشاهد أن كل شيء أحمر ، حدث خطأ واضح. Blazor ليس ميئوسًا جدًا منه في تصحيح الأخطاء ويمكن رؤية مجموعة المكالمات بالكامل في وحدة التحكم ، وفي رأيي أن هذا يتم بشكل أفضل من نفس Angular . لا حاجة للتخمين من خلال العلامات غير المباشرة حيث حدث الخطأ ، ما عليك سوى إلقاء نظرة على مكدس المكالمة: حدثت



رسالة NullReferenceException أثناء عرض الصفحة . كيف يمكن أن يحدث هذا ، لأننا قمنا بتهيئة المجموعة الوحيدة التي نستخدمها في طريقة OnInitializedAsync .

لفهم أفضل قليلاً ، أدخل مخرجات الوقت في الأماكن المناسبة لرؤية الإطار الزمني لما حدث:

  1. Console.WriteLine($"Time from markup block: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  2. Console.WriteLine($"Time from cycle: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  3. Console.WriteLine($"Time from code block, before await: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}");
  4. Console.WriteLine($"Time from code block, after await: {DateTime.Now.ToString()}:{DateTime.Now.Millisecond.ToString()}"); 



في لقطة الشاشة أدناه ، في وحدة التحكم ، ما حدث في وقت عرض الصفحة. يمكن ملاحظة أن الصفحة تبدأ في العرض حتى قبل إكمال الطرق غير المتزامنة.
في المرة الأولى ، لم تتم تهيئة PizzaViewModels بعد وتم اكتشاف NullReferenceException . بعد ذلك ، كما هو متوقع ، بعد أن أعادت المهمة أسلوب OnInitializedAsync إلى حالة RanToCompletion ، أعيد تصميم عنصر التحكم . ما هو جدير بالملاحظة ، خلال الممر الثاني ، دخلنا في دورة ، كما يمكن رؤيته من الرسائل الموجودة في وحدة التحكم. ولكن في هذه المرحلة ، لم يعد يتم تحديث واجهة المستخدم ولا نرى أي تغييرات مرئية.



في الواقع ، من السهل جدًا حل المشكلة ، ما عليك سوى تشغيل فحص فارغ بشكل غير متزامن قبل تنفيذ الحلقة ، ثم لن يحدث استثناء للمرة الأولى وخلال التمرير الثاني سنرى البيانات التي نحتاجها.
@if (PizzaViewModels != null)
{
    @foreach (var pizza in PizzaViewModels)
    {
        ……………………….. //    
    }
}


يبدو الأمر أفضل قليلاً الآن ، لا توجد المزيد من رسائل الخطأ في وحدة التحكم ويمكنك رؤية المعلومات التي وصلت إلينا من الخادم:



إنه أفضل بكثير ، ولكن لا توجد أنماط وموارد كافية ، ولا سيما الصور ، تستبدل محتويات المجلد wwwroot بمحتويات المجلد "~ / Articles / Part2 /BlazingPizza.Client/wwwroot "مستودع (رابط في نهاية المقال) وتشغيل المشروع مرة أخرى ، أفضل بكثير. على الرغم من أنها لا تزال بعيدة عن المثالية:



أحداث حياة المكون


نظرًا لأننا التقينا بالفعل بأحد أحداث حياة مكون OnInitializedAsync ، فسيكون من المنطقي ذكر الآخرين:
طرق التهيئة
غير مهيأ
يتم استدعاؤه عند تهيئة المكون بالفعل وتعيين المعلمات الخاصة به بالفعل بواسطة المكون الأصلي. خلال حياة المكون ، يتم استدعاؤه مرة واحدة بعد التهيئة.
OnInitializedAsync
نسخة غير متزامنة من الطريقة الأولى ، بعد التنفيذ ، يتم إعادة تقديم المكون. لذلك ، عند كتابة التعليمات البرمجية ، يجب مراعاة أن بعض الكائنات قد تكون فارغة.
طريقة قابلة للتنفيذ قبل تحديد قيم المعلمات
SetParametersAsync
, . ParameterView .

[Parameter] [CascadingParameter] ParameterView. , . , )

OnParametersSet
. ,

— .
— , .
OnParametersSetAsyncOnParametersSet
OnAfterRender. . JavaScript DOM . bool firstRender true .
OnAfterRenderAsync
, Task , - .

ShouldRender
UI, - . . .
StateHasChanged
, Blazor .
Dispose
, UI. StateHasChanged Dispose . Dispose IDisposable, @implements IDisposable


في هذا الجزء ، تعلمنا كيفية استقبال البيانات من وحدة التحكم وعرضها للمستخدم.
في الجزء التالي ، سنقوم بتنظيم التخطيط وإضافة طبقة وصول إلى البيانات لعرض بيانات حقيقية على الصفحة الرئيسية.

رابط إلى مستودع سلسلة المقالات هذه.
رابط للمصدر الأصلي.

Source: https://habr.com/ru/post/undefined/


All Articles