Commit 45ac7894 authored by Ed Charbeneau's avatar Ed Charbeneau Committed by Ryan Nowak
Browse files

Migrated full workshop to Preview 8 (#134)

* Updated code to preview 8 and part way through docs

* Fix for missing js and css

* Updated docs
replaced @functions => @code
Made all [Parameter] public
Made refs wildcard version => Version="$(AspNetCoreVersion)

* Completed migration to preivew 8 for all starting points.

* Fix for missing js and css

* More steps migrated to p8

* Update BlazingPizza.Server.csproj
parent 4b8a617b
<Project>
<PropertyGroup>
<AspNetCoreVersion>3.0.0-preview6.19307.2</AspNetCoreVersion>
<EntityFrameworkVersion>3.0.0-preview6.19304.10</EntityFrameworkVersion>
<AspNetCoreVersion>3.0.0-preview8.19405.7</AspNetCoreVersion>
<EntityFrameworkVersion>3.0.0-preview8.19405.11</EntityFrameworkVersion>
</PropertyGroup>
</Project>
......@@ -35,15 +35,15 @@ The home page is implemented as a single component. The `@page` directive specif
First we'll update the home page to display the list of available pizza specials. The list of specials will be part of the state of the `Index` component.
Add a `@functions` block to *Index.razor* with a list field to keep track of the available specials:
Add a `@code` block to *Index.razor* with a list field to keep track of the available specials:
```csharp
@functions {
@code {
List<PizzaSpecial> specials;
}
```
The code in the `@functions` block is added to the generated class for the component. The `PizzaSpecial` type is already defined for you in the Shared project.
The code in the `@code` block is added to the generated class for the component. The `PizzaSpecial` type is already defined for you in the Shared project.
To get the available list of specials we need to call an API on the backend. Blazor provides a preconfigured `HttpClient` through dependency injection that is already setup with the correct base address. Use the `@inject` directive to inject an `HttpClient` into the `Index` component.
......@@ -54,13 +54,13 @@ To get the available list of specials we need to call an API on the backend. Bla
The `@inject` directive essentially defines a new property on the component where the first token specified the property type and the second token specifies the property name. The property is populated for you using dependency injection.
Override the `OnInitAsync` method in the `@functions` block to retrieve the list of pizza specials. This method is part of the component lifecycle and is called when the component is initialized. Use the `GetJsonAsync<T>()` method to handle deserializing the response JSON:
Override the `OnInitializedAsync` method in the `@code` block to retrieve the list of pizza specials. This method is part of the component lifecycle and is called when the component is initialized. Use the `GetJsonAsync<T>()` method to handle deserializing the response JSON:
```csharp
@functions {
@code {
List<PizzaSpecial> specials;
protected async override Task OnInitAsync()
protected async override Task OnInitializedAsync()
{
specials = await HttpClient.GetJsonAsync<List<PizzaSpecial>>("specials");
}
......
......@@ -27,7 +27,7 @@ Run the app and check that the pizza name is written to the browser console when
The `@` symbol is used in Razor files to indicate the start of C# code. Surround the C# code with parens if needed to clarify where the C# code begins and ends.
Update the `@functions` block in *Index.razor* to add some additional fields for tracking the pizza being customized and whether the pizza customization dialog is visible.
Update the `@code` block in *Index.razor* to add some additional fields for tracking the pizza being customized and whether the pizza customization dialog is visible.
```csharp
List<PizzaSpecial> specials;
......@@ -35,7 +35,7 @@ Pizza configuringPizza;
bool showingConfigureDialog;
```
Add a `ShowConfigurePizzaDialog` method to the `@functions` block for handling when a pizza special is clicked.
Add a `ShowConfigurePizzaDialog` method to the `@code` block for handling when a pizza special is clicked.
```csharp
void ShowConfigurePizzaDialog(PizzaSpecial special)
......@@ -66,11 +66,11 @@ Add a *ConfigurePizzaDialog.razor* file under the *Shared* directory. Since this
> Note: There currently is no option for adding a new file with a .razor extension. Simply use the Razor View (.cshtml) file template, and manually name the file with a .razor extension.
The `ConfigurePizzaDialog` should have a `Pizza` parameter that specifies the pizza being configured. Component parameters are defined by adding a writable property to the component decorated with the `[Parameter]` attribute. Add a `@functions` block to the `ConfigurePizzaDialog` with the following `Pizza` parameter:
The `ConfigurePizzaDialog` should have a `Pizza` parameter that specifies the pizza being configured. Component parameters are defined by adding a writable property to the component decorated with the `[Parameter]` attribute. Add a `@code` block to the `ConfigurePizzaDialog` with the following `Pizza` parameter:
```csharp
@functions {
[Parameter] Pizza Pizza { get; set; }
@code {
[Parameter] public Pizza Pizza { get; set; }
}
```
......@@ -177,10 +177,10 @@ The user should also be able to select additional toppings on `ConfigurePizzaDia
...
</div>
@functions {
@code {
List<Topping> toppings;
[Parameter] Pizza Pizza { get; set; }
[Parameter] public Pizza Pizza { get; set; }
protected async override Task OnInitAsync()
{
......@@ -354,9 +354,9 @@ Create a new `ConfiguredPizzaItem` component for displaying a configured pizza.
</div>
</div>
@functions {
[Parameter] Pizza Pizza { get; set; }
[Parameter] EventCallback OnRemoved { get; set; }
@code {
[Parameter] public Pizza Pizza { get; set; }
[Parameter] public EventCallback OnRemoved { get; set; }
}
```
......
......@@ -80,10 +80,10 @@ Switch back to the `MyOrders` component code. Once again we're going to inject a
@inject HttpClient HttpClient
```
Then add a `@functions` block that makes an asynchronous request for the data we need:
Then add a `@code` block that makes an asynchronous request for the data we need:
```csharp
@functions {
@code {
List<OrderWithStatus> ordersWithStatus;
protected override async Task OnParametersSetAsync()
......@@ -121,7 +121,7 @@ It's simple to express this using `@if/else` blocks in Razor code. Update your c
}
</div>
@functions {
@code {
List<OrderWithStatus> ordersWithStatus;
protected override async Task OnParametersSetAsync()
......@@ -201,8 +201,8 @@ Once again we'll add a component to handle this. In the `Pages` directory, creat
TODO: Show details for order @OrderId
</div>
@functions {
[Parameter] int OrderId { get; set; }
@code {
[Parameter] public int OrderId { get; set; }
}
```
......@@ -238,11 +238,11 @@ Before we can implement the polling, we'll need to add the following directives
You've already seen `@inject` used with `HttpClient`, so you know what that is for. Plus, you'll recognize `@using` from the equivalent in regular `.cs` files, so this shouldn't be much of a mystery either. Unfortunately, Visual Studio does not yet add `@using` directives automatically in Razor files, so you do have to write them in yourself when needed.
Now you can implement the polling. Update your `@functions` block as follows:
Now you can implement the polling. Update your `@code` block as follows:
```cs
@functions {
[Parameter] int OrderId { get; set; }
@code {
[Parameter] public int OrderId { get; set; }
OrderWithStatus orderWithStatus;
bool invalidOrder;
......@@ -362,8 +362,8 @@ Create a new file, `OrderReview.razor` inside the `Shared` directory, and have i
</strong>
</p>
@functions {
[Parameter] Order Order { get; set; }
@code {
[Parameter] public Order Order { get; set; }
}
```
......@@ -417,7 +417,7 @@ Now if you try to compile the application, the compiler will complain:
error CS0535: 'OrderDetails' does not implement interface member 'IDisposable.Dispose()'
```
Resolve this by adding the following method inside the `@functions` block:
Resolve this by adding the following method inside the `@code` block:
```cs
void IDisposable.Dispose()
......
......@@ -13,11 +13,11 @@ Start by adding a new page component, `Checkout.razor`, with a `@page` directive
<div class="checkout-cols">
<div class="checkout-order-details">
<h4>Review order</h4>
<OrderReview Order="@OrderState.Order" />
<OrderReview Order="OrderState.Order" />
</div>
</div>
<button class="checkout-button btn btn-warning" @onclick="@PlaceOrder">
<button class="checkout-button btn btn-warning" @onclick="PlaceOrder">
Place order
</button>
</div>
......@@ -26,7 +26,7 @@ Start by adding a new page component, `Checkout.razor`, with a `@page` directive
To implement `PlaceOrder`, copy the method with that name from `Index.razor` into `Checkout.razor`:
```cs
@functions {
@code {
async Task PlaceOrder()
{
var newOrderId = await HttpClient.PostJsonAsync<int>("orders", OrderState.Order);
......@@ -57,8 +57,8 @@ We've now got a good place to put some UI for entering a delivery address. As us
Create a new component in the `BlazingPizza.Client` project's `Shared` folder called `AddressEditor.razor`. It's going to be a general way to edit `Address` instances, so have it receive a parameter of this type:
```cs
@functions {
[Parameter] Address Address { get; set; }
@code {
[Parameter] public Address Address { get; set; }
}
```
......@@ -68,47 +68,47 @@ The markup here is going to be a bit tedious, so you probably want to copy and p
<div class="form-field">
<label>Name:</label>
<div>
<input @bind="@Address.Name" />
<input @bind="Address.Name" />
</div>
</div>
<div class="form-field">
<label>Line 1:</label>
<div>
<input @bind="@Address.Line1" />
<input @bind="Address.Line1" />
</div>
</div>
<div class="form-field">
<label>Line 2:</label>
<div>
<input @bind="@Address.Line2" />
<input @bind="Address.Line2" />
</div>
</div>
<div class="form-field">
<label>City:</label>
<div>
<input @bind="@Address.City" />
<input @bind="Address.City" />
</div>
</div>
<div class="form-field">
<label>Region:</label>
<div>
<input @bind="@Address.Region" />
<input @bind="Address.Region" />
</div>
</div>
<div class="form-field">
<label>Postal code:</label>
<div>
<input @bind="@Address.PostalCode" />
<input @bind="Address.PostalCode" />
</div>
</div>
@functions {
[Parameter] Address Address { get; set; }
@code {
[Parameter] public Address Address { get; set; }
}
```
......@@ -207,12 +207,12 @@ One of the most important built-in UI components for data entry is the `EditForm
```html
<div class="main">
<EditForm Model="@OrderState.Order.DeliveryAddress">
<EditForm Model="OrderState.Order.DeliveryAddress">
<div class="checkout-cols">
... leave unchanged ...
</div>
<button class="checkout-button btn btn-warning" @onclick="@PlaceOrder">
<button class="checkout-button btn btn-warning" @onclick="PlaceOrder">
Place order
</button>
</EditForm>
......@@ -239,7 +239,7 @@ If you ran your application now, you could still submit a blank form (and the se
Next, instead of triggering `PlaceOrder` directly from the button, you need to trigger it from the `EditForm`. Add the following `OnValidSubmit` attribute onto the `EditForm`:
```html
<EditForm Model="@OrderState.Order" OnValidSubmit="@PlaceOrder">
<EditForm Model="OrderState.Order" OnValidSubmit="PlaceOrder">
```
As you can probably guess, the `<button>` no longer triggers `PlaceOrder` directly. Instead, the button just asks the form to be submitted. And then the form decides whether or not it's valid, and if it is, *then* it will call `PlaceOrder`.
......@@ -258,7 +258,7 @@ Start by removing the `<ValidationSummary>` component entirely. Then, switch ove
<div class="form-field">
<label>Name:</label>
<div>
<input @bind="@Address.Name" />
<input @bind="Address.Name" />
<ValidationMessage For="@(() => Address.Name)" />
</div>
</div>
......@@ -294,7 +294,7 @@ Go back to `AddressEditor.razor` once again. Replace each of the `<input>` eleme
<div class="form-field">
<label>Name:</label>
<div>
<InputText @bind-Value="@Address.Name" />
<InputText @bind-Value="Address.Name" />
<ValidationMessage For="@(() => Address.Name)" />
</div>
</div>
......
......@@ -195,10 +195,10 @@ To fix this, let's make the UI prompt the user to log in (if necessary) as part
In the `Checkout` page component, add some logic to `OnInitAsync` to check whether the user is currently authenticated. If they aren't, send them off to the login endpoint.
```cs
@functions {
@code {
[CascadingParameter] Task<AuthenticationState> AuthenticationStateTask { get; set; }
protected override async Task OnInitAsync()
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateTask;
if (!authState.User.Identity.IsAuthenticated)
......@@ -244,7 +244,7 @@ We'll fix the bug by persisting the order state in the browser's `localStorage`.
@inject IJSRuntime JSRuntime
```
Then, inside `OnInitAsync`, add the following line just above the `UriHelper.NavigateTo` call:
Then, inside `OnInitializedAsync`, add the following line just above the `UriHelper.NavigateTo` call:
```cs
await LocalStorage.SetAsync(JSRuntime, "currentorder", OrderState.Order);
......
......@@ -14,7 +14,7 @@ Open *Map.razor* and take a look at the code:
<div id="@elementId" style="height: 100%; width: 100%;"></div>
@functions {
@code {
string elementId = $"map-{Guid.NewGuid().ToString("D")}";
[Parameter] double Zoom { get; set; }
......@@ -56,7 +56,7 @@ Add the `Map` component to the `OrderDetails` page by adding the following just
```html
<div class="track-order-map">
<Map Zoom="13" Markers="@orderWithStatus.MapMarkers" />
<Map Zoom="13" Markers="orderWithStatus.MapMarkers" />
</div>
```
......
......@@ -30,7 +30,8 @@ It looks like:
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>
......@@ -94,8 +95,8 @@ Next, to give this dialog some conditional behavior, let's add a parameter of ty
}
@functions {
[Parameter] RenderFragment ChildContent { get; set; }
[Parameter] bool Show { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool Show { get; set; }
}
```
......@@ -204,7 +205,7 @@ Now that we've defined by a generic type parameter we can use it in a parameter
@functions {
List<TItem> items;
[Parameter] Func<Task<List<TItem>>> Loader { get; set; }
[Parameter] public Func<Task<List<TItem>>> Loader { get; set; }
protected override async Task OnParametersSetAsync()
{
......@@ -241,9 +242,9 @@ Now, these are our three states of the dialog, and we'd like accept a content pa
Here's an example of the three parameters to add:
```C#
[Parameter] RenderFragment LoadingContent { get; set; }
[Parameter] RenderFragment EmptyContent { get; set; }
[Parameter] RenderFragment<TItem> ItemContent { get; set; }
[Parameter] public RenderFragment LoadingContent { get; set; }
[Parameter] public RenderFragment EmptyContent { get; set; }
[Parameter] public RenderFragment<TItem> ItemContent { get; set; }
```
note: naming a `RenderFragment` parameter with the suffix *Content* is just a convention.
......@@ -282,11 +283,11 @@ Let's add another `string` parameter, and finally the functions block of `Templa
@functions {
List<TItem> items;
[Parameter] Func<Task<List<TItem>>> Loader { get; set; }
[Parameter] RenderFragment LoadingContent { get; set; }
[Parameter] RenderFragment EmptyContent { get; set; }
[Parameter] RenderFragment<TItem> ItemContent { get; set; }
[Parameter] string ListGroupClass { get; set; }
[Parameter] public Func<Task<List<TItem>>> Loader { get; set; }
[Parameter] public RenderFragment LoadingContent { get; set; }
[Parameter] public RenderFragment EmptyContent { get; set; }
[Parameter] public RenderFragment<TItem> ItemContent { get; set; }
[Parameter] public string ListGroupClass { get; set; }
protected override async Task OnParametersSetAsync()
{
......@@ -323,11 +324,11 @@ else
@functions {
List<TItem> items;
[Parameter] Func<Task<List<TItem>>> Loader { get; set; }
[Parameter] RenderFragment LoadingContent { get; set; }
[Parameter] RenderFragment EmptyContent { get; set; }
[Parameter] RenderFragment<TItem> ItemContent { get; set; }
[Parameter] string ListGroupClass { get; set; }
[Parameter] public Func<Task<List<TItem>>> Loader { get; set; }
[Parameter] public RenderFragment LoadingContent { get; set; }
[Parameter] public RenderFragment EmptyContent { get; set; }
[Parameter] public RenderFragment<TItem> ItemContent { get; set; }
[Parameter] public string ListGroupClass { get; set; }
protected override async Task OnParametersSetAsync()
{
......
......@@ -22,7 +22,7 @@ This is a rough guide of what topics are best to introduce with each section.
- Introduce @inject and DI - can show how that's a shorthand for a property in @functions
- Introduce http + JSON in Blazor (`GetJsonAsync`)
- Talk about async and the interaction with rendering
- Introduce `OnInitAsync` and the common pattern of starting async work
- Introduce `OnInitializedAsync` and the common pattern of starting async work
- Introduce @layout - mention that `_Imports.razor` is the most common way to hook it up
- Introduce NavLink and talk about various `NavLinkMatch` options
......@@ -46,10 +46,10 @@ This is a rough guide of what topics are best to introduce with each section.
- @page and routing (again)
- route parameter constraints
- reminders about async, inject, http, json
- difference between `OnInitAsync` and `OnParametersSetAsync`
- difference between `OnInitializedAsync` and `OnParametersSetAsync`
- Introduce `StateHasChanged` with the context about background processing
- introduce `@implements` - implementing an interfact
- introduce `Dispose` as the counterpart to `OnInit`
- introduce `Dispose` as the counterpart to `OnInitialized`
- introduce `IUriHelper` and programmatic navigation
## 04 Refactor state management
......@@ -100,9 +100,9 @@ In the following example the event handler delegate is `TestComponent.Clicked` a
```html
@* TestComponent.razor *@
<button @onclick="@Clicked">Click me!</Clicked>
<button @onclick="Clicked">Click me!</Clicked>
<p>Clicked @i times!</p>
@functions {
@code {
int i;
void Clicked()
{
......@@ -118,14 +118,14 @@ Now let's consider what happens when we want an event to rerender an *ancestor*
```html
@* CoolButton.razor *@
<button @onclick="Clicked">Clicking this will be cool!</button>
@functions {
[Parameter] Action Clicked { get; set; }
@code {
[Parameter] public Action Clicked { get; set; }
}
@* TestComponent2.razor *@
<CoolButton Clicked="@Clicked" />
<CoolButton Clicked="Clicked" />
<p>Clicked @i times!</p>
@functions {
@code {
int i;
void Clicked()
{
......@@ -153,8 +153,8 @@ public class TestState
```html
@* CoolButton.razor *@
<button @onclick="Clicked">Clicking this will be cool!</button>
@functions {
[Parameter] Action Clicked { get; set; }
@code {
[Parameter] public Action Clicked { get; set; }
}
@* TestComponent3.razor *@
......@@ -176,9 +176,9 @@ Let's jump back to our application. If you like you can reproduce the problem th
@if (OrderState.ShowingConfigureDialog)
{
<ConfigurePizzaDialog
Pizza="@OrderState.ConfiguringPizza"
OnConfirm="@OrderState.ConfirmConfigurePizzaDialog"
OnCancel="@OrderState.CancelConfigurePizzaDialog" />
Pizza="OrderState.ConfiguringPizza"
OnConfirm="OrderState.ConfirmConfigurePizzaDialog"
OnCancel="OrderState.CancelConfigurePizzaDialog" />
}
```
......
......@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="$(AspNetCoreVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<ItemGroup>
......
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Layouts
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.JSInterop
@using BlazingPizza.Client
......
......@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Browser" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>
......@@ -3,11 +3,11 @@
<div id="@elementId" style="height: 100%; width: 100%;"></div>
@functions {
@code {
string elementId = $"map-{Guid.NewGuid().ToString("D")}";
[Parameter] double Zoom { get; set; }
[Parameter] List<Marker> Markers { get; set; }
[Parameter] public double Zoom { get; set; }
[Parameter] public List<Marker> Markers { get; set; }
protected async override Task OnAfterRenderAsync()
{
......
......@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="$(AspNetCoreVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<ItemGroup>
......
......@@ -19,10 +19,10 @@
</ul>
</div>
@functions {
@code {
List<PizzaSpecial> specials;
protected async override Task OnInitAsync()
protected async override Task OnInitializedAsync()
{
specials = await HttpClient.GetJsonAsync<List<PizzaSpecial>>("specials");
}
......
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Layouts
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.JSInterop
@using BlazingPizza.Client
......
......@@ -7,7 +7,7 @@
<link rel="icon" href="data:;base64,=" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
<link href="_content/blazingpizzacomponentslibrary/leaflet/leaflet.css" rel="stylesheet" />
<link href="_content/blazingpizza.componentslibrary/leaflet/leaflet.css" rel="stylesheet" />
<title>Blazing Pizza</title>
</head>
<body>
......@@ -16,8 +16,8 @@
</app>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/blazingpizzacomponentslibrary/localStorage.js"></script>
<script src="_content/blazingpizzacomponentslibrary/deliveryMap.js"></script>
<script src="_content/blazingpizzacomponentslibrary/leaflet/leaflet.js"></script>
<script src="_content/blazingpizza.componentslibrary/localStorage.js"></script>
<script src="_content/blazingpizza.componentslibrary/deliveryMap.js"></script>
<script src="_content/blazingpizza.componentslibrary/leaflet/leaflet.js"></script>
</body>
</html>
......@@ -6,7 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Browser" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>
......@@ -3,11 +3,11 @@
<div id="@elementId" style="height: 100%; width: 100%;"></div>
@functions {
@code {
string elementId = $"map-{Guid.NewGuid().ToString("D")}";
[Parameter] double Zoom { get; set; }
[Parameter] List<Marker> Markers { get; set; }
[Parameter] public double Zoom { get; set; }
[Parameter] public List<Marker> Markers { get; set; }
protected async override Task OnAfterRenderAsync()
{
......
......@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="$(AspNetCoreVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="$(AspNetCoreVersion)" />