Hola Habr! Sigo haciendo una tienda en línea y aprendo Blazor. En esta parte, hablaré sobre cómo agregué la capacidad de crear un pedido, ver pedidos y trabajar con una secuencia de acciones, una de las cuales puede provocar un error. Para más detalles, bienvenido a cat.Contenido
Referencias
→ Fuentes→ Imágenes en el Registro DockerSaga
Me aburrí y decidí imitar la situación cuando tenemos dos microservicios: para la cesta y hacer pedidos. Normalmente, para tales situaciones, se crea un orquestador de microservicios, que controla la secuencia de acciones y realiza acciones compensatorias. Por ejemplo, esto está integrado en Mass transit, NServiceBus, MS Orleans (transacciones distribuidas). Por ejemplo, decidí simular una pequeña situación en la que necesita llamar a dos servicios diferentes que no tiene la oportunidad de cambiar. Google y FaceBook por ejemplo. Aunque también es mejor hacerlo a través del servidor y enviar una solicitud al backend. Aquí está el ejemplo más simple. Un poco más complicado sería guardar el estado incompleto en el navegador LocalStorage e intentar retroceder por temporizador, pero no tan torpe como lo hice aquí.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);
}
}
}
El código
1) modelo
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) servicio
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) Ver
@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();
}
}
Capturas de pantalla


Opción en Angular 9
