рдирдорд╕реНрдХрд╛рд░, рд╣реЗрдмреНрд░! рдореИрдВ рдПрдХ рдСрдирд▓рд╛рдЗрди рд╕реНрдЯреЛрд░ рдмрдирд╛рдирд╛ рдФрд░ рдмреНрд▓реЗрдЬрд╝рд░ рд╕реАрдЦрдирд╛ рдЬрд╛рд░реА рд░рдЦрддрд╛ рд╣реВрдВред рдЗрд╕ рднрд╛рдЧ рдореЗрдВ рдореИрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реВрдБрдЧрд╛ рдХрд┐ рдХреИрд╕реЗ рдореИрдВрдиреЗ рдПрдХ рдЖрджреЗрд╢ рдмрдирд╛рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝реА, рдЖрджреЗрд╢ рджреЗрдЦреЗ рдФрд░ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдЕрдиреБрдХреНрд░рдо рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд┐рдпрд╛, рдЬрд┐рдирдореЗрдВ рд╕реЗ рдПрдХ рдореЗрдВ рддреНрд░реБрдЯрд┐ рд╣реЛ рд╕рдХрддреА рд╣реИред рд╡рд┐рд╡рд░рдг рдХреЗ рд▓рд┐рдП, рдмрд┐рд▓реНрд▓реА рдореЗрдВ рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИредрд╕рд╛рдордЧреНрд░реА
рд╕рдВрджрд░реНрдн
тЖТ рд╕реНрд░реЛрддтЖТ рдбреЛрдХрд░ рд░рдЬрд┐рд╕реНрдЯреНрд░реА рдкрд░ рдЫрд╡рд┐рдпрд╛рдВрдХрдерд╛
рдореИрдВ рдКрдм рдЧрдпрд╛ рдФрд░ рдореИрдВрдиреЗ рдЙрд╕ рд╕реНрдерд┐рддрд┐ рдХрд╛ рдЕрдиреБрдХрд░рдг рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ рдЬрдм рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рджреЛ рдорд╛рдЗрдХреНрд░реЛрд╕рд░реНрд╡рд┐рд╕ рд╣реИрдВ - рдЯреЛрдХрд░реА рдХреЗ рд▓рд┐рдП рдФрд░ рдСрд░реНрдбрд░ рджреЗрдиреЗ рдХреЗ рд▓рд┐рдПред рдЖрдорддреМрд░ рдкрд░, рдРрд╕реА рд╕реНрдерд┐рддрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╛рдЗрдХреНрд░реЛрд╕реИрд╕ рдСрд░реНрдХреЗрд╕реНрдЯреНрд░рд╛ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЬреЛ рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЗ рдЕрдиреБрдХреНрд░рдо рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддрд╛ рд╣реИ рдФрд░ рдкреНрд░рддрд┐рдкреВрд░рдХ рдХреНрд░рд┐рдпрд╛рдПрдВ рдХрд░рддрд╛ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрд╣ MassTransit, NServiceBus, MS Orleans (рд╡рд┐рддрд░рд┐рдд рд▓реЗрдирджреЗрди) рдореЗрдВ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ ред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдереЛрдбрд╝реА рд╕реА рд╕реНрдерд┐рддрд┐ рдХрд╛ рдЕрдиреБрдХрд░рдг рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ рдЬрдм рдЖрдкрдХреЛ рджреЛ рдЕрд▓рдЧ-рдЕрд▓рдЧ рд╕реЗрд╡рд╛рдУрдВ рдкрд░ рджрд╕реНрддрдХ рджреЗрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ, рдЬрд┐рдиреНрд╣реЗрдВ рдмрджрд▓рдиреЗ рдХрд╛ рдЕрд╡рд╕рд░ рдЖрдкрдХреЗ рдкрд╛рд╕ рдирд╣реАрдВ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП Google рдФрд░ рдлреЗрд╕рдмреБрдХред рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣рд╛рдВ рд╕рд░реНрд╡рд░ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдРрд╕рд╛ рдХрд░рдирд╛ рдмреЗрд╣рддрд░ рд╣реИ, рдФрд░ рдмреИрдХрдПрдВрдб рдкрд░ рдПрдХ рдЕрдиреБрд░реЛрдз рднреЗрдЬреЗрдВред рдпрд╣рд╛рдБ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдЙрджрд╛рд╣рд░рдг рд╣реИред рд▓реЛрдХрд▓рд╕реНрдЯреЛрд░реАрдЬ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЕрдкреВрд░реНрдг рд╕реНрдерд┐рддрд┐ рдХреЛ рдмрдЪрд╛рдиреЗ рдФрд░ рдЯрд╛рдЗрдорд░ рджреНрд╡рд╛рд░рд╛ рд╡рд╛рдкрд╕ рд░реЛрд▓ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдереЛрдбрд╝рд╛ рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рд╣реЛрдЧрд╛ , рд▓реЗрдХрд┐рди рдЬреИрд╕рд╛ рдХрд┐ рдореИрдВрдиреЗ рдпрд╣рд╛рдВ рдХрд┐рдпрд╛ рдерд╛ рдЙрддрдирд╛ рдЕрдирд╛рдбрд╝реА рдирд╣реАрдВредрдЖрджреЗрд╢ рджреГрд╢реНрдп рдХреЛрдб: 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);
}
}
}
рдХреЛрдб
1) рдореЙрдбрд▓
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) рд╕реЗрд╡рд╛
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) рджреЗрдЦреЗрдВ
@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();
}
}
рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ


рдХреЛрдгреАрдп 9 рдкрд░ рд╡рд┐рдХрд▓реНрдк
