متجر Blazor Client Side Online: الجزء 5 - عرض سلة المحذوفات والعمل مع Stateful



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

المحتوى



المراجع


→  المصادر
صور في سجل Docker

رنان


لم يعجبني فقدان الدولة عند التبديل بين الصفحات. على سبيل المثال ، تلك الحقول التي قمت بتصفية المنتجات من خلالها. لحل هذه المشكلة ، قمت بالتبديل إلى خدمات أحادية المفرد وسجلت نموذج العرض للصفحة في حاوية DI كلغة مفردة. بشكل أساسي ، استخدمت حاوية DI كمخزن حكومي ، وبدأت ViewModel بالحقن في View كخدمة.

الرمز


1) النماذج


    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) الخدمات


    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();
        }
    }

هنا من الضروري أن نتحدث عنه

public event EventHandler OnBasketItemsCountChanged;

كنت أرغب في عرض العدد الحالي للمنتجات في السلة في رأس الصفحة. تكمن المشكلة في أن العنوان ليس تابعًا لصفحة عربة التسوق ، وبالتالي فهو يتجاهل تحديث حالته. حتى يعيد رسمه ويضيف هذا الحدث ، وفيه علقت هنا مثل هذا المعالج:

@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) عرض


@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) التسجيل في حاوية DI


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

الخيار على الزاوي 9


حتى الآن ، يجلب لي التطور على Blazor متعة أكثر من Angular.


All Articles