Basket : Improve, reduce, remove duplication call to database (#610)
This commit is contained in:
@@ -7,17 +7,5 @@ namespace Microsoft.eShopWeb.ApplicationCore.Exceptions
|
|||||||
public BasketNotFoundException(int basketId) : base($"No basket found with id {basketId}")
|
public BasketNotFoundException(int basketId) : base($"No basket found with id {basketId}")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BasketNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasketNotFoundException(string message) : base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasketNotFoundException(string message, Exception innerException) : base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
||||||
@@ -6,8 +7,8 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
|||||||
public interface IBasketService
|
public interface IBasketService
|
||||||
{
|
{
|
||||||
Task TransferBasketAsync(string anonymousId, string userName);
|
Task TransferBasketAsync(string anonymousId, string userName);
|
||||||
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1);
|
Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1);
|
||||||
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
|
Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||||
Task DeleteBasketAsync(int basketId);
|
Task DeleteBasketAsync(int basketId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,21 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1)
|
public async Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1)
|
||||||
{
|
{
|
||||||
var basketSpec = new BasketWithItemsSpecification(basketId);
|
var basketSpec = new BasketWithItemsSpecification(username);
|
||||||
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
||||||
Guard.Against.NullBasket(basketId, basket);
|
|
||||||
|
if (basket == null)
|
||||||
|
{
|
||||||
|
basket = new Basket(username);
|
||||||
|
await _basketRepository.AddAsync(basket);
|
||||||
|
}
|
||||||
|
|
||||||
basket.AddItem(catalogItemId, price, quantity);
|
basket.AddItem(catalogItemId, price, quantity);
|
||||||
|
|
||||||
await _basketRepository.UpdateAsync(basket);
|
await _basketRepository.UpdateAsync(basket);
|
||||||
|
return basket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBasketAsync(int basketId)
|
public async Task DeleteBasketAsync(int basketId)
|
||||||
@@ -36,7 +42,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services
|
|||||||
await _basketRepository.DeleteAsync(basket);
|
await _basketRepository.DeleteAsync(basket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetQuantities(int basketId, Dictionary<string, int> quantities)
|
public async Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities)
|
||||||
{
|
{
|
||||||
Guard.Against.Null(quantities, nameof(quantities));
|
Guard.Against.Null(quantities, nameof(quantities));
|
||||||
var basketSpec = new BasketWithItemsSpecification(basketId);
|
var basketSpec = new BasketWithItemsSpecification(basketId);
|
||||||
@@ -53,6 +59,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services
|
|||||||
}
|
}
|
||||||
basket.RemoveEmptyItems();
|
basket.RemoveEmptyItems();
|
||||||
await _basketRepository.UpdateAsync(basket);
|
await _basketRepository.UpdateAsync(basket);
|
||||||
|
return basket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TransferBasketAsync(string anonymousId, string userName)
|
public async Task TransferBasketAsync(string anonymousId, string userName)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.eShopWeb.Web.Pages.Basket;
|
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||||
|
using Microsoft.eShopWeb.Web.Pages.Basket;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.Web.Interfaces
|
namespace Microsoft.eShopWeb.Web.Interfaces
|
||||||
@@ -6,5 +7,9 @@ namespace Microsoft.eShopWeb.Web.Interfaces
|
|||||||
public interface IBasketViewModelService
|
public interface IBasketViewModelService
|
||||||
{
|
{
|
||||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||||
|
|
||||||
|
Task<int> CountTotalBasketItems(string username);
|
||||||
|
|
||||||
|
Task<BasketViewModel> Map(Basket basket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
|
||||||
using Microsoft.eShopWeb.Web.Interfaces;
|
using Microsoft.eShopWeb.Web.Interfaces;
|
||||||
using Microsoft.eShopWeb.Web.ViewModels;
|
using Microsoft.eShopWeb.Web.ViewModels;
|
||||||
using System;
|
using System;
|
||||||
@@ -16,16 +14,12 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket
|
|||||||
public class IndexModel : PageModel
|
public class IndexModel : PageModel
|
||||||
{
|
{
|
||||||
private readonly IBasketService _basketService;
|
private readonly IBasketService _basketService;
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
|
||||||
private string _username = null;
|
|
||||||
private readonly IBasketViewModelService _basketViewModelService;
|
private readonly IBasketViewModelService _basketViewModelService;
|
||||||
|
|
||||||
public IndexModel(IBasketService basketService,
|
public IndexModel(IBasketService basketService,
|
||||||
IBasketViewModelService basketViewModelService,
|
IBasketViewModelService basketViewModelService)
|
||||||
SignInManager<ApplicationUser> signInManager)
|
|
||||||
{
|
{
|
||||||
_basketService = basketService;
|
_basketService = basketService;
|
||||||
_signInManager = signInManager;
|
|
||||||
_basketViewModelService = basketViewModelService;
|
_basketViewModelService = basketViewModelService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +27,7 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket
|
|||||||
|
|
||||||
public async Task OnGet()
|
public async Task OnGet()
|
||||||
{
|
{
|
||||||
await SetBasketModelAsync();
|
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)
|
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)
|
||||||
@@ -42,64 +36,58 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket
|
|||||||
{
|
{
|
||||||
return RedirectToPage("/Index");
|
return RedirectToPage("/Index");
|
||||||
}
|
}
|
||||||
await SetBasketModelAsync();
|
|
||||||
|
|
||||||
await _basketService.AddItemToBasket(BasketModel.Id, productDetails.Id, productDetails.Price);
|
var username = GetOrSetBasketCookieAndUserName();
|
||||||
|
var basket = await _basketService.AddItemToBasket(username,
|
||||||
|
productDetails.Id, productDetails.Price);
|
||||||
|
|
||||||
await SetBasketModelAsync();
|
BasketModel = await _basketViewModelService.Map(basket);
|
||||||
|
|
||||||
return RedirectToPage();
|
return RedirectToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnPostUpdate(IEnumerable<BasketItemViewModel> items)
|
public async Task OnPostUpdate(IEnumerable<BasketItemViewModel> items)
|
||||||
{
|
{
|
||||||
await SetBasketModelAsync();
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var basketView = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||||
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
||||||
await _basketService.SetQuantities(BasketModel.Id, updateModel);
|
var basket = await _basketService.SetQuantities(basketView.Id, updateModel);
|
||||||
|
BasketModel = await _basketViewModelService.Map(basket);
|
||||||
await SetBasketModelAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetBasketModelAsync()
|
private string GetOrSetBasketCookieAndUserName()
|
||||||
{
|
{
|
||||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
string userName = null;
|
||||||
{
|
|
||||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GetOrSetBasketCookieAndUserName();
|
|
||||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
|
|
||||||
|
|
||||||
}
|
if (Request.HttpContext.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return Request.HttpContext.User.Identity.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetOrSetBasketCookieAndUserName()
|
|
||||||
{
|
|
||||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||||
{
|
{
|
||||||
_username = Request.Cookies[Constants.BASKET_COOKIENAME];
|
userName = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||||
|
|
||||||
if (!Request.HttpContext.User.Identity.IsAuthenticated)
|
if (!Request.HttpContext.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
if (!Guid.TryParse(_username, out var _))
|
if (!Guid.TryParse(userName, out var _))
|
||||||
{
|
{
|
||||||
_username = null;
|
userName = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_username != null) return;
|
if (userName != null) return userName;
|
||||||
|
|
||||||
_username = Guid.NewGuid().ToString();
|
userName = Guid.NewGuid().ToString();
|
||||||
var cookieOptions = new CookieOptions { IsEssential = true };
|
var cookieOptions = new CookieOptions { IsEssential = true };
|
||||||
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
||||||
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);
|
Response.Cookies.Append(Constants.BASKET_COOKIENAME, userName, cookieOptions);
|
||||||
|
|
||||||
|
return userName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||||
using Microsoft.eShopWeb.Web.Interfaces;
|
using Microsoft.eShopWeb.Web.Interfaces;
|
||||||
using Microsoft.eShopWeb.Web.Pages.Basket;
|
|
||||||
using Microsoft.eShopWeb.Web.ViewModels;
|
using Microsoft.eShopWeb.Web.ViewModels;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent
|
namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent
|
||||||
@@ -22,25 +20,27 @@ namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent
|
|||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(string userName)
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
{
|
{
|
||||||
var vm = new BasketComponentViewModel();
|
var vm = new BasketComponentViewModel
|
||||||
vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity);
|
{
|
||||||
|
ItemsCount = await CountTotalBasketItems()
|
||||||
|
};
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BasketViewModel> GetBasketViewModelAsync()
|
private async Task<int> CountTotalBasketItems()
|
||||||
{
|
{
|
||||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||||
{
|
{
|
||||||
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name);
|
return await _basketService.CountTotalBasketItems(User.Identity.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
string anonymousId = GetAnnonymousIdFromCookie();
|
string anonymousId = GetAnnonymousIdFromCookie();
|
||||||
if (anonymousId == null)
|
if (anonymousId == null)
|
||||||
return new BasketViewModel();
|
return 0;
|
||||||
|
|
||||||
return await _basketService.GetOrCreateBasketForUser(anonymousId);
|
return await _basketService.CountTotalBasketItems(anonymousId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetAnnonymousIdFromCookie()
|
private string GetAnnonymousIdFromCookie()
|
||||||
|
|||||||
@@ -34,18 +34,7 @@ namespace Microsoft.eShopWeb.Web.Services
|
|||||||
{
|
{
|
||||||
return await CreateBasketForUser(userName);
|
return await CreateBasketForUser(userName);
|
||||||
}
|
}
|
||||||
return await CreateViewModelFromBasket(basket);
|
var viewModel = await Map(basket);
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<BasketViewModel> CreateViewModelFromBasket(Basket basket)
|
|
||||||
{
|
|
||||||
var viewModel = new BasketViewModel
|
|
||||||
{
|
|
||||||
Id = basket.Id,
|
|
||||||
BuyerId = basket.BuyerId,
|
|
||||||
Items = await GetBasketItems(basket.Items)
|
|
||||||
};
|
|
||||||
|
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,5 +73,24 @@ namespace Microsoft.eShopWeb.Web.Services
|
|||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<BasketViewModel> Map(Basket basket)
|
||||||
|
{
|
||||||
|
return new BasketViewModel()
|
||||||
|
{
|
||||||
|
BuyerId = basket.BuyerId,
|
||||||
|
Id = basket.Id,
|
||||||
|
Items = await GetBasketItems(basket.Items)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountTotalBasketItems(string username)
|
||||||
|
{
|
||||||
|
var basketSpec = new BasketWithItemsSpecification(username);
|
||||||
|
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
||||||
|
if (basket == null)
|
||||||
|
return 0;
|
||||||
|
return basket.Items.Sum(i => i.Quantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="col-lg-1 col-xs-12">
|
<section class="col-lg-1 col-xs-12">
|
||||||
@await Component.InvokeAsync("Basket", User.Identity.Name)
|
@await Component.InvokeAsync("Basket")
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTes
|
|||||||
|
|
||||||
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
||||||
|
|
||||||
await basketService.AddItemToBasket(basket.Id, 1, 1.50m);
|
await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m);
|
||||||
|
|
||||||
_mockBasketRepo.Verify(x => x.GetBySpecAsync(It.IsAny<BasketWithItemsSpecification>(), default), Times.Once);
|
_mockBasketRepo.Verify(x => x.GetBySpecAsync(It.IsAny<BasketWithItemsSpecification>(), default), Times.Once);
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTes
|
|||||||
|
|
||||||
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
||||||
|
|
||||||
await basketService.AddItemToBasket(basket.Id, 1, 1.50m);
|
await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m);
|
||||||
|
|
||||||
_mockBasketRepo.Verify(x => x.UpdateAsync(basket, default), Times.Once);
|
_mockBasketRepo.Verify(x => x.UpdateAsync(basket, default), Times.Once);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user