BlazingPizza:Blazor应用程序从头到尾。第2部分。添加组件

大家好!对于所有想更多了解Blazor的人。今天,我们将继续为比萨店创建网站,即,我们将创建Web api控制器并尝试在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添加Controllers文件夹

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

我们需要一种方法,该方法可以列出比萨的所有基础知识。

除了添加方法之外,您还需要执行一些简单的步骤:

  1. 我们使用[ApiController]属性标记该控制器,该属性具有一些优点,尤其是如果模型未通过验证则自动返回400代码,而没有模型,它是提供View 的普通MVC控制器。
  2. [Route(«pizzas»)]. Attribute Routing, , Conventional Routing, . “pizzas” ? http{s}://hostName/pizzas/{}
    .
  3. Controller ControllerBase, MVC .

好的,例如,我们发出了一个localhost:5000 / pizzas请求,希望得到所有披萨的列表,但没有任何反应。同样,交易在协议中。

如果这是一个Get请求,那么我们要么必须要使用一个标有[HttpGet]属性的方法(Asp.Net而言就是Action),或者甚至更明显地是一个名为Get的方法! .Net和反射的其他所有内容都会为我们做。 因此,将唯一的Index方法重命名为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,如果沿着localhost:5000 / pizzas路径发出请求,我们将进入Get操作并捕获NotImplementedException。也就是说,虽然我们的控制器没有做任何有用的事情,但它只是接受请求并因错误而失败。

我们从控制器返回数据


现在是时候让我们的代码做一些有用的事情了,例如返回披萨。到目前为止,我们尚未实现数据层,因此我们仅从action中返回了几个披萨为此,返回一个由两个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"
        },
    };
}

浏览器中的请求结果将如下所示:



设置主页


该应用程序的可见部分在BlazingPizza.Client项目的.razor组件中我们对Pages文件夹中的Index.razor感兴趣,请打开它并删除它从默认项目继承的所有内容。让我们开始添加我们真正需要的东西。 1.添加:

“ /”该伪指令用于配置客户端路由,它表示默认情况下将加载此控件,即,如果我们仅访问localhost应用程序地址:5000 /不带任何/索引,/披萨或其他。

2。注入HttpClient HttpClient使用指令注入 将HttpClient之类的服务添加到我们的页面并调用对象HttpClientBlazor框架已经为我们配置了类型为HttpClient的对象,因此我们只需发出所需的请求即可。这种类型的注入称为“ 属性注入”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并阻止,添加代码以获取所有可用的披萨。我们将这段代码放在async OnInitializedAsync方法中

protected async override Task OnInitializedAsync() {
	
}

在组件初始化之后调用此方法,并且在调用时,其所有参数已由父组件初始化。在其中,您可以执行一些异步操作,然后需要状态更新。稍后我将更详细地讨论这一点。创建组件后,该方法仅被调用一次。

最后,在此方法内添加比萨饼:

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

比萨饼-Blazor已为我们设置了相对路径。如下面的方法签名所示,由get请求请求数据,然后客户端尝试将其序列化为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.在code指令上方,添加html代码,其中将是披萨本身:

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

    </ul>
</div>

如您所见,语音类名称为“ pizza-cards”的列表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>
}

这里所有的乐趣都在循环内。 前言({items}中的var {item})
这是一种典型的Razor标记,它使我们能够在与常规html代码相同的页面上使用C#的功能。最主要的是在语言关键字和变量之前放置“ @”符号 在循环内部,我们只需访问披萨对象的属性 最后,我们使用GetFormattedBasePrice方法显示比萨饼的格式化基本价格。顺便说一下,这就是BasePizza域模型与其ViewModel之间的区别



表示形式,因为此方法包含用于以所需格式显示价格的最简单逻辑,而在服务级别,我们以某种方式操纵价格,而无需在任何地方显示价格,因此我们不需要这种格式。

我们在浏览器中显示接收到的数据


我们获得了所有必要的数据以显示。现在是启动我们的应用程序并确保一切正常的时候了。我们单击“ 调试”按钮(在Rider中,运行”按钮只是启动没有调试功能的应用程序)。

哦,天哪,也没有任何效果:)打开控制台(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。然后,如预期的那样,在TaskOnInitializedAsync方法返回RanToCompletion状态之后,就对控件进行了重新设计。值得注意的是,在第二遍中,我们进入了一个循环,从控制台中的消息可以看出。但是目前,UI不再更新,并且看不到任何可见的更改。



实际上,这个问题很容易解决,您只需要在执行循环之前异步运行null检查,那么第一次就不会发生异常,在第二遍过程中,我们将看到所需的数据。
@if (PizzaViewModels != null)
{
    @foreach (var pizza in PizzaViewModels)
    {
        ……………………….. //    
    }
}


现在看来好一点了,控制台中没有更多错误消息了,您可以看到服务器发出的信息:



更好,但是样式和资源不足,尤其是图片,请用文件夹“〜/ Articles / Part2”中的内容替换wwwroot文件夹中的内容/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


在这一部分中,我们学习了如何从控制器接收数据并将其显示给用户。
在下一部分中,我们将整理Layout并添加一个数据访问层以在主页上显示实际数据。

链接到本系列文章的资源库。
链接到原始来源。

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


All Articles