Toko Online Sisi Klien Blazor: Bagian 6 - Membuat Pesanan dan Bekerja dengan Tindakan Kompensasi



Halo, Habr! Saya terus membuat toko online dan belajar Blazor. Di bagian ini saya akan berbicara tentang bagaimana saya menambahkan kemampuan untuk membuat pesanan, melihat pesanan, dan bekerja dengan urutan tindakan, yang salah satunya dapat menyebabkan kesalahan. Untuk detailnya, selamat datang di kucing.

Kandungan



Referensi


β†’  Sumber
β†’ Gambar pada Register Docker

Kisah


Saya bosan dan saya memutuskan untuk meniru situasi ketika kami memiliki dua layanan mikro - untuk keranjang dan memesan. Biasanya, untuk situasi seperti itu, Orkestra layanan mikro dibuat, yang mengontrol urutan tindakan dan melakukan tindakan kompensasi. Misalnya, ini dibangun ke MassTransit, NServiceBus, MS Orleans (transaksi terdistribusi). Sebagai contoh, saya memutuskan untuk mensimulasikan situasi kecil ketika Anda perlu mengetuk dua layanan yang berbeda sehingga Anda tidak memiliki kesempatan untuk berubah. Google dan FaceBook misalnya. Meskipun lebih baik untuk melakukan ini melalui server, dan mengirim satu permintaan ke backend. Ini adalah contoh paling sederhana. Sedikit lebih rumit akan diperlukan untuk menyimpan keadaan tidak lengkap di browser LocalStorage dan mencoba untuk memutar kembali oleh timer, tetapi tidak kikuk seperti yang saya lakukan di sini.

Kode PesananViewModel:

        public async Task Create()
        {
          //    .
            await _basket.Load();
            if (!string.IsNullOrWhiteSpace(_basket.Error))
                return;
            //    .
            //      .
           //      .
            var lines = _basket.Model.Select(l => new LineModel()
            {
                Product = new ProductModel()
                {
                    Id = l.Product.Id,
                    Price = l.Product.Price,
                    Title = l.Product.Title,
                    Version = l.Product.Version
                },
                Quantity = l.Quantity
            }).ToList();
          //    -  .
            await _basket.Clear();
            if (!string.IsNullOrWhiteSpace(_basket.Error))
                return;
           //           .
            try
            {
                await _order.Create(lines, Address);
            }
            catch
            {
                //     . 
                await Restore(lines);
                throw;
            }
            if (!string.IsNullOrWhiteSpace(_order.Error))
            {
                await Restore(lines);
            }
            State = OrderVmState.List;
        }

        private async Task Restore(IEnumerable<LineModel> lines)
        {
            //            
            //      .
           //         .
            foreach (var line in lines)
            {
                for (int i = 0; i < line.Quantity; i++)
                {
                    await _basket.Add(line.Product);
                }
            }
        }

Kode


1) Model


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

    public enum OrderStatus
    {
        Created = 10,
        Delivered,
    }

    public class OrderModel
    {
        public Guid Id { get; set; }
        public string Buyer { get; set; }
        public OrderStatus Status { get; set; }
        public List<LineModel> Lines { get; set; } = new List<LineModel>();
        public string Address { get; set; }
    }

2) Layanan


    public sealed class OrderService : IOrderService
    {
        private readonly IApiRepository _api;
        private List<OrderModel> _orders;

        public OrderService(IApiRepository api)
        {
            _api = api;
            _orders = new List<OrderModel>();
        }

        public string Error { get; private set; }
        public IReadOnlyList<OrderModel> Orders => _orders?.AsReadOnly();

        public async Task Create(IEnumerable<LineModel> lines, string address)
        {
            var (_, e) = await _api.CreateOrder(lines, address);
            Error = e;
            if (!string.IsNullOrWhiteSpace(e))
                return;
            await Load();
        }

        public async Task Load()
        {
            var (r, e) = await _api.GetOrders();
            _orders = r;
            Error = e;
        }
    }

3) ViewModel


    public class OrdersViewModel
    {
        private readonly IOrderService _order;
        private readonly IBasketService _basket;

        public OrdersViewModel(IOrderService order, IBasketService basket)
        {
            _order = order;
            _basket = basket;
            OrderFormContext = new EditContext(this);
        }

        public bool CanCreateOrder => _basket.ItemsCount > 0;
        public string Error => _order.Error + _basket.Error;
        public IReadOnlyList<OrderModel> Model => _order.Orders;
        public decimal Sum => _basket.Model.Sum(m => m.Quantity * m.Product.Price);
        public EditContext OrderFormContext { get; }
        public OrderVmState State { get; set; }
        [Required]
        [StringLength(255, MinimumLength = 3)]
        public string Address { get; set; }

        public void ChangeState(string value)
        {
            State = OrderVmState.List;
            if (string.IsNullOrWhiteSpace(value))
                return;
            if (Enum.TryParse(value, true, out OrderVmState state))
                State = state;
            if (_basket.ItemsCount == 0 && State == OrderVmState.Create)
                State = OrderVmState.List;
        }

        public async Task OnInitializedAsync()
        {
            await _order.Load();
            await _basket.Load();
        }

        public async Task Create()
        {
            if (!OrderFormContext.Validate())
                return;
            await _basket.Load();
            if (!string.IsNullOrWhiteSpace(_basket.Error))
                return;
            var lines = _basket.Model.Select(l => new LineModel()
            {
                Product = new ProductModel()
                {
                    Id = l.Product.Id,
                    Price = l.Product.Price,
                    Title = l.Product.Title,
                    Version = l.Product.Version
                },
                Quantity = l.Quantity
            }).ToList();
            await _basket.Clear();
            if (!string.IsNullOrWhiteSpace(_basket.Error))
                return;
            try
            {
                await _order.Create(lines, Address);
            }
            catch
            {
                await Restore(lines);
                throw;
            }
            if (!string.IsNullOrWhiteSpace(_order.Error))
            {
                await Restore(lines);
            }
            State = OrderVmState.List;
        }

        private async Task Restore(IEnumerable<LineModel> lines)
        {
            foreach (var line in lines)
            {
                for (int i = 0; i < line.Quantity; i++)
                {
                    await _basket.Add(line.Product);
                }
            }
        }
    }

4) Lihat


@page "/orders"
@page "/orders/{operation}"

@attribute [Authorize]

@inject OrdersViewModel ViewModel


<h3>Orders</h3>
<div>
    <Error Model="@ViewModel.Error" />
</div>
@if (ViewModel.State == OrderVmState.Create)
{
    <EditForm EditContext="@ViewModel.OrderFormContext" OnValidSubmit="@ViewModel.Create">
        <DataAnnotationsValidator />
        <div class="form-group"><label class="form-label"> Sum: @ViewModel.Sum</label></div>
        <div class="form-group">
            <label class="form-label" for="address">Address</label>
            <InputTextArea id="address" name="address" class="form-control" @bind-Value="@ViewModel.Address" />
            <ValidationMessage For="@(() => ViewModel.Address)" />
        </div>
        <button type="submit" class="btn btn-primary" disabled="@(!context.Validate())">Save</button>
        <button class="btn btn-default" @onclick="@(x => ViewModel.State = OrderVmState.List)">Cancel</button>
    </EditForm>
}
else
{
    @if (ViewModel.CanCreateOrder)
    {
        <input type="button" class="btn btn-primary" value="Create Order" @onclick="@(x=>ViewModel.State = OrderVmState.Create)" />
    }
    <div class="table-responsive">
        <table class="table">
            <thead>
                <tr>
                    <AuthorizeView Roles="admin">
                        <Authorized>
                            <th>Id</th>
                            <th>Buyer Id</th>
                        </Authorized>
                    </AuthorizeView>
                    <th>Status</th>
                    <th>Products Count</th>
                    <th>Sum</th>
                    <th>Address</th>
                </tr>
            </thead>
            <tbody>
                @if (ViewModel.Model == null)
                {
                    <tr>
                        <td>
                            <em>Loading...</em>
                        </td>
                    </tr>
                }
                else
                {
                    foreach (var order in ViewModel.Model)
                    {

                        <tr>
                            <AuthorizeView Roles="admin">
                                <Authorized>
                                    <td>@order.Id</td>
                                    <td>@order.Buyer</td>
                                </Authorized>
                            </AuthorizeView>
                            <td>@order.Status.ToString("G")</td>
                            <td>@order.Lines.Sum(l => l.Quantity)</td>
                            <td>@order.Lines.Sum(l => l.Quantity * l.Product.Price)</td>
                            <td>@order.Address</td>
                        </tr>
                    }
                }
            </tbody>
        </table>
    </div>
}

@functions {

    [Parameter]
    public string Operation
    {
        get => ViewModel.State.ToString("G");
        set => ViewModel.ChangeState(value);
    }

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

Tangkapan layar






Opsi pada Angular 9



All Articles