рдирдорд╕реНрдХрд╛рд░, рд╣реЗрдмреНрд░! рдореИрдВ рдмреНрд▓реЗрдЬрд╝рд░ рдкрд░ рдСрдирд▓рд╛рдЗрди рд╕реНрдЯреЛрд░ рдХрд░рдирд╛ рдЬрд╛рд░реА рд░рдЦрддрд╛ рд╣реВрдВред рдЗрд╕ рднрд╛рдЧ рдореЗрдВ, рдореИрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реВрдБрдЧрд╛ рдХрд┐ рдХреИрд╕реЗ рдореИрдВрдиреЗ рдЗрд╕рдореЗрдВ рдорд╛рд▓ рдХреА рдПрдХ рдЯреЛрдХрд░реА рджреЗрдЦрдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝реА рдФрд░ рд░рд╛рдЬреНрдп рдХреЗ рд╕рд╛рде рд╕рдВрдЧрдард┐рдд рдХрд╛рдо рдХрд┐рдпрд╛ред рд╡рд┐рд╡рд░рдг рдХреЗ рд▓рд┐рдП, рдмрд┐рд▓реНрд▓реА рдореЗрдВ рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИредрд╕рд╛рдордЧреНрд░реА
рд╕рдВрджрд░реНрдн
тЖТ рд╕реНрд░реЛрддтЖТ рдбреЛрдХрд░ рд░рдЬрд┐рд╕реНрдЯреНрд░реА рдкрд░ рдЫрд╡рд┐рдпрд╛рдВрд╕реНрдЯреЗрдЯрдлреБрд▓
рдореБрдЭреЗ рдкрд╕рдВрдж рдирд╣реАрдВ рдерд╛ рдХрд┐ рдкреГрд╖реНрдареЛрдВ рдХреЗ рдмреАрдЪ рд╕реНрд╡рд┐рдЪ рдХрд░рддреЗ рд╕рдордп рд░рд╛рдЬреНрдп рдЦреЛ рдЬрд╛рддрд╛ рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╡реЗ рдХреНрд╖реЗрддреНрд░ рдЬрд┐рдирдХреЗ рджреНрд╡рд╛рд░рд╛ рдореИрдВрдиреЗ рдЙрддреНрдкрд╛рджреЛрдВ рдХреЛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд┐рдпрд╛ред рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рд╕реНрдЯреЗрдЯрдлреБрд▓ рд╕рд┐рдВрдЧрд▓рдЯрди рд╕реЗрд╡рд╛рдУрдВ рдкрд░ рд╕реНрд╡рд┐рдЪ рдХрд┐рдпрд╛ рдФрд░ рдбреАрдЖрдИ рдХрдВрдЯреЗрдирд░ рдореЗрдВ рдкреЗрдЬ рдХреЗ рд╡реНрдпреВрдореЗрд▓ рдХреЛ рдПрдХ рд╕рд┐рдВрдЧрд▓рдЯрди рдХреЗ рд░реВрдк рдореЗрдВ рдкрдВрдЬреАрдХреГрдд рдХрд┐рдпрд╛ред рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ, рдореИрдВрдиреЗ рдПрдХ рд╕реНрдЯреЗрдЯ рд╕реНрдЯреЛрд░ рдХреЗ рд░реВрдк рдореЗрдВ 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;
рдореИрдВ рдкреЗрдЬ рд╣реЗрдбрд░ рдореЗрдВ рдЯреЛрдХрд░реА рдореЗрдВ рдЙрддреНрдкрд╛рджреЛрдВ рдХреА рд╡рд░реНрддрдорд╛рди рд╕рдВрдЦреНрдпрд╛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛ред рд╕рдорд╕реНрдпрд╛ рдпрд╣ рд╣реИ рдХрд┐ рд╢реАрд░реНрд╖рдХ рдЦрд░реАрджрд╛рд░реА рдХрд╛рд░реНрдЯ рдкреЗрдЬ рдХрд╛ рдмрдЪреНрдЪрд╛ рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП, рдпрд╣ рдЕрдкрдиреЗ рд░рд╛рдЬреНрдп рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреА рдЙрдкреЗрдХреНрд╖рд╛ рдХрд░рддрд╛ рд╣реИред рддрд╛рдХрд┐ рд╡рд╣ I рдХреЛ рдкреБрдирдГ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗ рдФрд░ рдЗрд╕ рдШрдЯрдирд╛ рдХреЛ рдЬреЛрдбрд╝реЗ, рдФрд░ рдЗрд╕рдореЗрдВ рдореИрдВрдиреЗ рдРрд╕реЗ рд╣реА рдПрдХ рд╣реИрдВрдбрд▓рд░ рдХреЛ рдпрд╣рд╛рдБ рд▓рдЯрдХрд╛ рджрд┐рдпрд╛:@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) рдбрд┐ рдХрдВрдЯреЗрдирд░ рдореЗрдВ рдкрдВрдЬреАрдХрд░рдг
services.AddTransient<IApiRepository, ApiRepository>();
services.AddSingleton<IBasketService, BasketService>();
services.AddSingleton<BasketViewModel>();
рдХреЛрдгреАрдп 9 рдкрд░ рд╡рд┐рдХрд▓реНрдк
рдЕрдм рддрдХ, рдмреНрд▓реЗрдЬрд╝рд░ рдкрд░ рд╡рд┐рдХрд╛рд╕ рдореБрдЭреЗ рдХреЛрдгреАрдп рд╕реЗ рдЕрдзрд┐рдХ рдЦреБрд╢реА рджреЗрддрд╛ рд╣реИред