Blazor Client Side Online Store: Teil 5 - Zeigen Sie den Papierkorb an und arbeiten Sie mit Stateful



Hallo Habr! Ich mache weiterhin Online-Shop auf Blazor. In diesem Teil werde ich darüber sprechen, wie ich die Möglichkeit hinzugefügt habe, einen Warenkorb anzuzeigen und die Arbeit mit dem Staat zu organisieren. Für Details willkommen bei Katze.

Inhalt



Verweise


→  Quellen
Bilder in der Docker-Registrierung

Staatsbürgerlich


Mir hat es nicht gefallen, dass der Status beim Wechseln zwischen Seiten verloren geht. Zum Beispiel die Felder, nach denen ich Produkte gefiltert habe. Um dieses Problem zu lösen, habe ich zu Stateful-Singleton-Diensten gewechselt und das ViewModel der Seite im DI-Container als Singleton registriert. Im Wesentlichen habe ich den DI-Container als Statusspeicher verwendet, und ViewModel hat begonnen, View as a Service einzufügen.

Der Code


1) Modelle


    public sealed class ProductModel
    {
        public Guid Id { get; set; }
        public string Version { get; set; }
        public string Title { get; set; }
        public decimal Price { get; set; }
    }

    public class BasketLineModel
    {
        public uint Quantity { get; set; }
        public ProductModel Product { get; set; }
    }

    public class BasketModel
    {
        public List<BasketLineModel> Lines { get; set; } = new List<BasketLineModel>();
    }

2) Dienstleistungen


    public class BasketService : IBasketService
    {
        private readonly IApiRepository _repository;
        private BasketModel _basket;

        public BasketService(IApiRepository repository)
        {
            _repository = repository;
            _basket = new BasketModel();
        }

        public string Error { get; private set; }
        public IReadOnlyList<BasketLineModel> Model => _basket.Lines.AsReadOnly();
        public event EventHandler OnBasketItemsCountChanged;
        public long ItemsCount => _basket?.Lines?.Sum(l => l.Quantity) ?? 0;

        public async Task Load()
        {
            var count = ItemsCount;
            var (r, e) = await _repository.GetBasket();
            _basket = r;
            Error = e;
            if (string.IsNullOrWhiteSpace(Error) && count != ItemsCount)
                OnBasketItemsCountChanged?.Invoke(null, null);
        }

        public async Task Add(ProductModel product)
        {
            var (_, e) = await _repository.AddToBasket(product);
            Error = e;
            if (!string.IsNullOrWhiteSpace(e))
                return;
            await Load();
        }

        public async Task Remove(ProductModel product)
        {
            var (_, e) = await _repository.Remove(product.Id);
            Error = e;
            if (!string.IsNullOrWhiteSpace(e))
                return;
            await Load();
        }
    }

Hier ist es notwendig zu erzählen

public event EventHandler OnBasketItemsCountChanged;

Ich wollte die aktuelle Anzahl der Produkte im Warenkorb im Seitenkopf anzeigen. Das Problem ist, dass der Titel kein untergeordnetes Element der Warenkorbseite ist und daher die Aktualisierung seines Status ignoriert. Damit er mich neu zeichnet und dieses Ereignis hinzufügt, habe ich hier einen solchen Handler aufgehängt:

@using BlazorEShop.Spa.BlazorWasm.Client.Core.Services
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
@inject IBasketService Basket

@implements IDisposable

<AuthorizeView>
    <Authorized>
        <span class="text-success">Total Items In Basket: @TotalItemsCount </span>  
        Hello, @context?.User?.Identity?.Name!
        <button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

@code
{
    public long TotalItemsCount { get; set; }

    protected override void OnInitialized()
    {
        Basket.OnBasketItemsCountChanged += Bind;
        TotalItemsCount = Basket.ItemsCount;
        base.OnInitialized();
    }

    public void Dispose()
    {
        Basket.OnBasketItemsCountChanged -= Bind;
    }

    public void Bind(object s, EventArgs e)
    {
        if (TotalItemsCount == Basket.ItemsCount)
            return;
        TotalItemsCount = Basket.ItemsCount;
        this.StateHasChanged();
    }

    private async Task BeginSignOut(MouseEventArgs args)
    {
        await SignOutManager.SetSignOutState();
        Navigation.NavigateTo("authentication/logout");
    }
}

3) ViewModel


    public class BasketViewModel
    {
        private bool _isInitialized;
        private readonly IBasketService _service;

        public BasketViewModel(IBasketService service)
        {
            _service = service;
        }

        public string Error => _service.Error;
        public IReadOnlyList<BasketLineModel> Model => _service.Model;

        public async Task OnInitializedAsync()
        {
            if (_isInitialized)
                return;
            Console.WriteLine("BASKET INIT!");
            await _service.Load();
            _isInitialized = true;
        }

        public Task Add(ProductModel product) => _service.Add(product);

        public Task Remove(ProductModel product) => _service.Remove(product);
    }

4) Anzeigen


@page "/basket"
@attribute [Authorize]
@inject BasketViewModel ViewModel

<h3>Basket</h3>
<Error Model="@ViewModel.Error" />
<input type="button" class="btn btn-primary my-3" value="Create Order" /> <!--TODO:   -->
<div class="table-responsive">
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Price</th>
                <th>Quantity</th>
                <th></th>
            </tr>
        </thead>
        <tbody>

            @if (ViewModel.Model == null)
            {
                <tr>
                    <td>
                        <em>Loading...</em>
                    </td>
                </tr>
            }
            else
            {
                foreach (var line in ViewModel.Model)
                {

                    <tr>
                        <td>@line.Product.Title</td>
                        <td>@line.Product.Price</td>
                        <td>@line.Quantity</td>
                        <td>
                            <input type="button"
                                   class="btn btn-success"
                                   @onclick="@(async x=>await ViewModel.Add(line.Product))"
                                   value="+" />
                            <input class="btn btn-warning"
                                   value="-"
                                   type="button"
                                   @onclick="@(async x=>await ViewModel.Remove(line.Product))" />
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>
</div>

@code
{
    protected override async Task OnInitializedAsync()
    {
        await ViewModel.OnInitializedAsync();
    }
}

5) Registrierung in einem DI-Container


services.AddTransient<IApiRepository, ApiRepository>();
services.AddSingleton<IBasketService, BasketService>();
services.AddSingleton<BasketViewModel>();

Option auf Winkel 9


Bisher macht mir die Entwicklung von Blazor mehr Freude als Angular.


All Articles