Blazor Client Side Online Store: Teil 6 - Erstellen einer Bestellung und Arbeiten mit Ausgleichsmaßnahmen



Hallo Habr! Ich mache weiterhin einen Online-Shop und lerne Blazor. In diesem Teil werde ich darüber sprechen, wie ich die Möglichkeit hinzugefügt habe, einen Auftrag zu erstellen, Aufträge anzuzeigen und mit einer Abfolge von Aktionen zu arbeiten, von denen eine zu einem Fehler führen kann. Für Details willkommen bei Katze.

Inhalt



Verweise


→  Quellen
Bilder in der Docker-Registrierung

Saga


Mir wurde langweilig und ich beschloss, die Situation nachzuahmen, wenn wir zwei Microservices haben - für den Warenkorb und die Bestellung. In solchen Situationen wird normalerweise ein Microservice-Orchester erstellt, das die Abfolge der Aktionen steuert und Ausgleichsaktionen ausführt. Dies ist beispielsweise in MassTransit, NServiceBus, MS Orleans (verteilte Transaktionen) integriert. Zum Beispiel habe ich beschlossen, eine kleine Situation zu simulieren, in der Sie auf zwei verschiedene Dienste klopfen müssen, die Sie nicht ändern können. Google und FaceBook zum Beispiel. Es ist jedoch auch besser, dies über den Server zu tun und eine Anfrage an das Backend zu senden. Hier ist das einfachste Beispiel. Etwas komplizierter wäre es, den unvollständigen Status im LocalStorage- Browser zu speichern und zu versuchen, einen Rollback per Timer durchzuführen , aber nicht so ungeschickt wie hier.

OrdersViewModel Code:

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

Der Code


1) Modell


    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) Service


    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) Anzeigen


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

Screenshots






Option auf Winkel 9



All Articles