From 984740d422ae0fd03644df3ca13e8f5046a0337a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Michel?= Date: Mon, 1 Nov 2021 22:09:34 +0100 Subject: [PATCH] Basket : Improve, reduce, remove duplication call to database (#610) --- .../Exceptions/BasketNotFoundException.cs | 12 ---- .../Interfaces/IBasketService.cs | 7 ++- src/ApplicationCore/Services/BasketService.cs | 15 +++-- src/Web/Interfaces/IBasketViewModelService.cs | 7 ++- src/Web/Pages/Basket/Index.cshtml.cs | 56 ++++++++----------- .../Components/BasketComponent/Basket.cs | 18 +++--- src/Web/Services/BasketViewModelService.cs | 34 ++++++----- src/Web/Views/Shared/_LoginPartial.cshtml | 2 +- .../BasketServiceTests/AddItemToBasket.cs | 4 +- 9 files changed, 76 insertions(+), 79 deletions(-) diff --git a/src/ApplicationCore/Exceptions/BasketNotFoundException.cs b/src/ApplicationCore/Exceptions/BasketNotFoundException.cs index 1e98888..b3a20bd 100644 --- a/src/ApplicationCore/Exceptions/BasketNotFoundException.cs +++ b/src/ApplicationCore/Exceptions/BasketNotFoundException.cs @@ -7,17 +7,5 @@ namespace Microsoft.eShopWeb.ApplicationCore.Exceptions 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) - { - } } } diff --git a/src/ApplicationCore/Interfaces/IBasketService.cs b/src/ApplicationCore/Interfaces/IBasketService.cs index 38412be..2dd34a3 100644 --- a/src/ApplicationCore/Interfaces/IBasketService.cs +++ b/src/ApplicationCore/Interfaces/IBasketService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using System.Collections.Generic; using System.Threading.Tasks; namespace Microsoft.eShopWeb.ApplicationCore.Interfaces @@ -6,8 +7,8 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces public interface IBasketService { Task TransferBasketAsync(string anonymousId, string userName); - Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1); - Task SetQuantities(int basketId, Dictionary quantities); + Task AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1); + Task SetQuantities(int basketId, Dictionary quantities); Task DeleteBasketAsync(int basketId); } } diff --git a/src/ApplicationCore/Services/BasketService.cs b/src/ApplicationCore/Services/BasketService.cs index 2b6697d..2fdd9ab 100644 --- a/src/ApplicationCore/Services/BasketService.cs +++ b/src/ApplicationCore/Services/BasketService.cs @@ -19,15 +19,21 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services _logger = logger; } - public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1) + public async Task 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); - Guard.Against.NullBasket(basketId, basket); + + if (basket == null) + { + basket = new Basket(username); + await _basketRepository.AddAsync(basket); + } basket.AddItem(catalogItemId, price, quantity); await _basketRepository.UpdateAsync(basket); + return basket; } public async Task DeleteBasketAsync(int basketId) @@ -36,7 +42,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services await _basketRepository.DeleteAsync(basket); } - public async Task SetQuantities(int basketId, Dictionary quantities) + public async Task SetQuantities(int basketId, Dictionary quantities) { Guard.Against.Null(quantities, nameof(quantities)); var basketSpec = new BasketWithItemsSpecification(basketId); @@ -53,6 +59,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Services } basket.RemoveEmptyItems(); await _basketRepository.UpdateAsync(basket); + return basket; } public async Task TransferBasketAsync(string anonymousId, string userName) diff --git a/src/Web/Interfaces/IBasketViewModelService.cs b/src/Web/Interfaces/IBasketViewModelService.cs index 3be5c84..82e4aad 100644 --- a/src/Web/Interfaces/IBasketViewModelService.cs +++ b/src/Web/Interfaces/IBasketViewModelService.cs @@ -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; namespace Microsoft.eShopWeb.Web.Interfaces @@ -6,5 +7,9 @@ namespace Microsoft.eShopWeb.Web.Interfaces public interface IBasketViewModelService { Task GetOrCreateBasketForUser(string userName); + + Task CountTotalBasketItems(string username); + + Task Map(Basket basket); } } diff --git a/src/Web/Pages/Basket/Index.cshtml.cs b/src/Web/Pages/Basket/Index.cshtml.cs index b5c342a..bf74172 100644 --- a/src/Web/Pages/Basket/Index.cshtml.cs +++ b/src/Web/Pages/Basket/Index.cshtml.cs @@ -1,9 +1,7 @@ using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.ViewModels; using System; @@ -16,16 +14,12 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket public class IndexModel : PageModel { private readonly IBasketService _basketService; - private readonly SignInManager _signInManager; - private string _username = null; private readonly IBasketViewModelService _basketViewModelService; public IndexModel(IBasketService basketService, - IBasketViewModelService basketViewModelService, - SignInManager signInManager) + IBasketViewModelService basketViewModelService) { _basketService = basketService; - _signInManager = signInManager; _basketViewModelService = basketViewModelService; } @@ -33,7 +27,7 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket public async Task OnGet() { - await SetBasketModelAsync(); + BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName()); } public async Task OnPost(CatalogItemViewModel productDetails) @@ -42,64 +36,58 @@ namespace Microsoft.eShopWeb.Web.Pages.Basket { 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(); } public async Task OnPostUpdate(IEnumerable items) { - await SetBasketModelAsync(); - if (!ModelState.IsValid) { return; } + var basketView = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName()); var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity); - await _basketService.SetQuantities(BasketModel.Id, updateModel); - - await SetBasketModelAsync(); + var basket = await _basketService.SetQuantities(basketView.Id, updateModel); + BasketModel = await _basketViewModelService.Map(basket); } - private async Task SetBasketModelAsync() + private string GetOrSetBasketCookieAndUserName() { - if (_signInManager.IsSignedIn(HttpContext.User)) - { - BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name); - } - else - { - GetOrSetBasketCookieAndUserName(); - BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username); + string userName = null; + if (Request.HttpContext.User.Identity.IsAuthenticated) + { + return Request.HttpContext.User.Identity.Name; } - } - private void GetOrSetBasketCookieAndUserName() - { 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 (!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 }; cookieOptions.Expires = DateTime.Today.AddYears(10); - Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions); + Response.Cookies.Append(Constants.BASKET_COOKIENAME, userName, cookieOptions); + + return userName; } } } diff --git a/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs b/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs index 70f6896..6954164 100644 --- a/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs +++ b/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs @@ -2,10 +2,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopWeb.Infrastructure.Identity; using Microsoft.eShopWeb.Web.Interfaces; -using Microsoft.eShopWeb.Web.Pages.Basket; using Microsoft.eShopWeb.Web.ViewModels; using System; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent @@ -22,25 +20,27 @@ namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent _signInManager = signInManager; } - public async Task InvokeAsync(string userName) + public async Task InvokeAsync() { - var vm = new BasketComponentViewModel(); - vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity); + var vm = new BasketComponentViewModel + { + ItemsCount = await CountTotalBasketItems() + }; return View(vm); } - private async Task GetBasketViewModelAsync() + private async Task CountTotalBasketItems() { if (_signInManager.IsSignedIn(HttpContext.User)) { - return await _basketService.GetOrCreateBasketForUser(User.Identity.Name); + return await _basketService.CountTotalBasketItems(User.Identity.Name); } string anonymousId = GetAnnonymousIdFromCookie(); if (anonymousId == null) - return new BasketViewModel(); + return 0; - return await _basketService.GetOrCreateBasketForUser(anonymousId); + return await _basketService.CountTotalBasketItems(anonymousId); } private string GetAnnonymousIdFromCookie() diff --git a/src/Web/Services/BasketViewModelService.cs b/src/Web/Services/BasketViewModelService.cs index 4c032da..ce66cfd 100644 --- a/src/Web/Services/BasketViewModelService.cs +++ b/src/Web/Services/BasketViewModelService.cs @@ -34,20 +34,9 @@ namespace Microsoft.eShopWeb.Web.Services { return await CreateBasketForUser(userName); } - return await CreateViewModelFromBasket(basket); - } - - private async Task CreateViewModelFromBasket(Basket basket) - { - var viewModel = new BasketViewModel - { - Id = basket.Id, - BuyerId = basket.BuyerId, - Items = await GetBasketItems(basket.Items) - }; - + var viewModel = await Map(basket); return viewModel; - } + } private async Task CreateBasketForUser(string userId) { @@ -84,5 +73,24 @@ namespace Microsoft.eShopWeb.Web.Services return items; } + + public async Task Map(Basket basket) + { + return new BasketViewModel() + { + BuyerId = basket.BuyerId, + Id = basket.Id, + Items = await GetBasketItems(basket.Items) + }; + } + + public async Task 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); + } } } diff --git a/src/Web/Views/Shared/_LoginPartial.cshtml b/src/Web/Views/Shared/_LoginPartial.cshtml index 4a72226..ac3f106 100644 --- a/src/Web/Views/Shared/_LoginPartial.cshtml +++ b/src/Web/Views/Shared/_LoginPartial.cshtml @@ -35,7 +35,7 @@
- @await Component.InvokeAsync("Basket", User.Identity.Name) + @await Component.InvokeAsync("Basket")
} diff --git a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs index 320991d..e46a154 100644 --- a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs +++ b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs @@ -22,7 +22,7 @@ namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTes 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(), default), Times.Once); } @@ -36,7 +36,7 @@ namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTes 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); }