Refactoring Services (#61)

* Refactoring ViewModels into Razor Pages models

* Cleaning up Basket viewcomponent

* Refactoring services.
Fixed bug in basket item counter.
This commit is contained in:
Steve Smith
2017-10-23 10:52:33 -04:00
committed by GitHub
parent dea73a5f5e
commit 16d81ae450
28 changed files with 218 additions and 230 deletions

View File

@@ -1,12 +1,11 @@
using Microsoft.eShopWeb.RazorPages.ViewModels; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Interfaces namespace ApplicationCore.Interfaces
{ {
public interface IBasketService public interface IBasketService
{ {
Task<BasketViewModel> GetOrCreateBasketForUser(string userName); Task<int> GetBasketItemCountAsync(string userName);
Task TransferBasketAsync(string anonymousId, string userName); Task TransferBasketAsync(string anonymousId, string userName);
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity); Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
Task SetQuantities(int basketId, Dictionary<string, int> quantities); Task SetQuantities(int basketId, Dictionary<string, int> quantities);

View File

@@ -1,4 +1,5 @@
using ApplicationCore.Entities.OrderAggregate; using ApplicationCore.Entities.OrderAggregate;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ApplicationCore.Interfaces namespace ApplicationCore.Interfaces

View File

@@ -1,13 +1,11 @@
using ApplicationCore.Interfaces; using ApplicationCore.Interfaces;
using System.Threading.Tasks;
using System.Collections.Generic;
using ApplicationCore.Specifications; using ApplicationCore.Specifications;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.Interfaces;
using Microsoft.eShopWeb.ViewModels;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.Services namespace ApplicationCore.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
@@ -27,55 +25,6 @@ namespace Microsoft.eShopWeb.Services
_itemRepository = itemRepository; _itemRepository = itemRepository;
} }
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if(basket == null)
{
return await CreateBasketForUser(userName);
}
return CreateViewModelFromBasket(basket);
}
private BasketViewModel CreateViewModelFromBasket(Basket basket)
{
var viewModel = new BasketViewModel();
viewModel.Id = basket.Id;
viewModel.BuyerId = basket.BuyerId;
viewModel.Items = basket.Items.Select(i =>
{
var itemModel = new BasketItemViewModel()
{
Id = i.Id,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
CatalogItemId = i.CatalogItemId
};
var item = _itemRepository.GetById(i.CatalogItemId);
itemModel.PictureUrl = _uriComposer.ComposePicUri(item.PictureUri);
itemModel.ProductName = item.Name;
return itemModel;
})
.ToList();
return viewModel;
}
public async Task<BasketViewModel> CreateBasketForUser(string userId)
{
var basket = new Basket() { BuyerId = userId };
await _basketRepository.AddAsync(basket);
return new BasketViewModel()
{
BuyerId = basket.BuyerId,
Id = basket.Id,
Items = new List<BasketItemViewModel>()
};
}
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity) public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
{ {
var basket = await _basketRepository.GetByIdAsync(basketId); var basket = await _basketRepository.GetByIdAsync(basketId);
@@ -85,27 +34,41 @@ namespace Microsoft.eShopWeb.Services
await _basketRepository.UpdateAsync(basket); await _basketRepository.UpdateAsync(basket);
} }
public async Task SetQuantities(int basketId, Dictionary<string,int> quantities) public async Task DeleteBasketAsync(int basketId)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
}
public async Task<int> GetBasketItemCountAsync(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if (basket == null)
{
_logger.LogInformation($"No basket found for {userName}");
return 0;
}
int count = basket.Items.Sum(i => i.Quantity);
_logger.LogInformation($"Basket for {userName} has {count} items.");
return count;
}
public async Task SetQuantities(int basketId, Dictionary<string, int> quantities)
{ {
var basket = await _basketRepository.GetByIdAsync(basketId); var basket = await _basketRepository.GetByIdAsync(basketId);
foreach (var item in basket.Items) foreach (var item in basket.Items)
{ {
if (quantities.TryGetValue(item.Id.ToString(), out var quantity)) if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
{ {
_logger.LogWarning($"Updating quantity of item ID:{item.Id} to {quantity}."); _logger.LogInformation($"Updating quantity of item ID:{item.Id} to {quantity}.");
item.Quantity = quantity; item.Quantity = quantity;
} }
} }
await _basketRepository.UpdateAsync(basket); await _basketRepository.UpdateAsync(basket);
} }
public async Task DeleteBasketAsync(int basketId)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
}
public async Task TransferBasketAsync(string anonymousId, string userName) public async Task TransferBasketAsync(string anonymousId, string userName)
{ {
var basketSpec = new BasketWithItemsSpecification(anonymousId); var basketSpec = new BasketWithItemsSpecification(anonymousId);

View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic; using System.Collections.Generic;
namespace Infrastructure.Services namespace ApplicationCore.Services
{ {
public class OrderService : IOrderService public class OrderService : IOrderService
{ {

View File

@@ -21,6 +21,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Data\Migrations\" /> <Folder Include="Data\Migrations\" />
<Folder Include="Services\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -5,9 +5,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Infrastructure.Identity; using Infrastructure.Identity;
using System; using System;
using Web;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.eShopWeb.Interfaces; using ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.Controllers namespace Microsoft.eShopWeb.Controllers
{ {

View File

@@ -22,8 +22,10 @@ namespace Microsoft.eShopWeb.Controllers
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<BasketController> _logger; private readonly IAppLogger<BasketController> _logger;
private readonly IOrderService _orderService; private readonly IOrderService _orderService;
private readonly IBasketViewModelService _basketViewModelService;
public BasketController(IBasketService basketService, public BasketController(IBasketService basketService,
IBasketViewModelService basketViewModelService,
IOrderService orderService, IOrderService orderService,
IUriComposer uriComposer, IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager, SignInManager<ApplicationUser> signInManager,
@@ -34,6 +36,7 @@ namespace Microsoft.eShopWeb.Controllers
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger; _logger = logger;
_orderService = orderService; _orderService = orderService;
_basketViewModelService = basketViewModelService;
} }
[HttpGet] [HttpGet]
@@ -87,10 +90,10 @@ namespace Microsoft.eShopWeb.Controllers
{ {
if (_signInManager.IsSignedIn(HttpContext.User)) if (_signInManager.IsSignedIn(HttpContext.User))
{ {
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name); return await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
} }
string anonymousId = GetOrSetBasketCookie(); string anonymousId = GetOrSetBasketCookie();
return await _basketService.GetOrCreateBasketForUser(anonymousId); return await _basketViewModelService.GetOrCreateBasketForUser(anonymousId);
} }
private string GetOrSetBasketCookie() private string GetOrSetBasketCookie()

View File

@@ -1,15 +1,10 @@
using Microsoft.eShopWeb.ViewModels; using Microsoft.eShopWeb.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.eShopWeb.Interfaces namespace Microsoft.eShopWeb.Interfaces
{ {
public interface IBasketService public interface IBasketViewModelService
{ {
Task<BasketViewModel> GetOrCreateBasketForUser(string userName); Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
Task TransferBasketAsync(string anonymousId, string userName);
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
Task DeleteBasketAsync(int basketId);
} }
} }

View File

@@ -0,0 +1,76 @@
using ApplicationCore.Interfaces;
using ApplicationCore.Specifications;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.Interfaces;
using Microsoft.eShopWeb.ViewModels;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.Services
{
public class BasketViewModelService : IBasketViewModelService
{
private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IUriComposer _uriComposer;
private readonly IRepository<CatalogItem> _itemRepository;
public BasketViewModelService(IAsyncRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository,
IUriComposer uriComposer)
{
_basketRepository = basketRepository;
_uriComposer = uriComposer;
_itemRepository = itemRepository;
}
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if(basket == null)
{
return await CreateBasketForUser(userName);
}
return CreateViewModelFromBasket(basket);
}
private BasketViewModel CreateViewModelFromBasket(Basket basket)
{
var viewModel = new BasketViewModel();
viewModel.Id = basket.Id;
viewModel.BuyerId = basket.BuyerId;
viewModel.Items = basket.Items.Select(i =>
{
var itemModel = new BasketItemViewModel()
{
Id = i.Id,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
CatalogItemId = i.CatalogItemId
};
var item = _itemRepository.GetById(i.CatalogItemId);
itemModel.PictureUrl = _uriComposer.ComposePicUri(item.PictureUri);
itemModel.ProductName = item.Name;
return itemModel;
})
.ToList();
return viewModel;
}
private async Task<BasketViewModel> CreateBasketForUser(string userId)
{
var basket = new Basket() { BuyerId = userId };
await _basketRepository.AddAsync(basket);
return new BasketViewModel()
{
BuyerId = basket.BuyerId,
Id = basket.Id,
Items = new List<BasketItemViewModel>()
};
}
}
}

View File

@@ -11,6 +11,10 @@ using ApplicationCore.Specifications;
namespace Microsoft.eShopWeb.Services namespace Microsoft.eShopWeb.Services
{ {
/// <summary>
/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works
/// with UI-specific types (view models and SelectListItem types).
/// </summary>
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly ILogger<CatalogService> _logger; private readonly ILogger<CatalogService> _logger;

View File

@@ -3,7 +3,6 @@ using ApplicationCore.Services;
using Infrastructure.Data; using Infrastructure.Data;
using Infrastructure.Identity; using Infrastructure.Identity;
using Infrastructure.Logging; using Infrastructure.Logging;
using Infrastructure.Services;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -93,6 +92,7 @@ namespace Microsoft.eShopWeb
services.AddScoped<ICatalogService, CachedCatalogService>(); services.AddScoped<ICatalogService, CachedCatalogService>();
services.AddScoped<IBasketService, BasketService>(); services.AddScoped<IBasketService, BasketService>();
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
services.AddScoped<IOrderService, OrderService>(); services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<CatalogService>(); services.AddScoped<CatalogService>();

View File

@@ -12,10 +12,10 @@ namespace Web.ViewComponents
{ {
public class Basket : ViewComponent public class Basket : ViewComponent
{ {
private readonly IBasketService _basketService; private readonly IBasketViewModelService _basketService;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
public Basket(IBasketService basketService, public Basket(IBasketViewModelService basketService,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
{ {
_basketService = basketService; _basketService = basketService;

View File

@@ -0,0 +1,11 @@
using Microsoft.eShopWeb.RazorPages.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Interfaces
{
public interface IBasketViewModelService
{
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
}
}

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels; using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Infrastructure.Identity; using Infrastructure.Identity;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{ {
@@ -23,6 +24,24 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Account
[BindProperty] [BindProperty]
public RegisterViewModel UserDetails { get; set; } public RegisterViewModel UserDetails { get; set; }
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task<IActionResult> OnPost(string returnUrl = "/Index") public async Task<IActionResult> OnPost(string returnUrl = "/Index")
{ {
@@ -38,7 +57,6 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Account
AddErrors(result); AddErrors(result);
} }
return Page(); return Page();
} }
private void AddErrors(IdentityResult result) private void AddErrors(IdentityResult result)
@@ -48,6 +66,5 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Account
ModelState.AddModelError("", error.Description); ModelState.AddModelError("", error.Description);
} }
} }
} }
} }

View File

@@ -1,14 +1,12 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Infrastructure.Identity; using Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{ {

View File

@@ -22,8 +22,10 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Basket
private readonly IAppLogger<IndexModel> _logger; private readonly IAppLogger<IndexModel> _logger;
private readonly IOrderService _orderService; private readonly IOrderService _orderService;
private string _username = null; private string _username = null;
private readonly IBasketViewModelService _basketViewModelService;
public IndexModel(IBasketService basketService, public IndexModel(IBasketService basketService,
IBasketViewModelService basketViewModelService,
IUriComposer uriComposer, IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager, SignInManager<ApplicationUser> signInManager,
IAppLogger<IndexModel> logger, IAppLogger<IndexModel> logger,
@@ -34,6 +36,7 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Basket
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger; _logger = logger;
_orderService = orderService; _orderService = orderService;
_basketViewModelService = basketViewModelService;
} }
public BasketViewModel BasketModel { get; set; } = new BasketViewModel(); public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
@@ -83,12 +86,12 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Basket
{ {
if (_signInManager.IsSignedIn(HttpContext.User)) if (_signInManager.IsSignedIn(HttpContext.User))
{ {
BasketModel = await _basketService.GetOrCreateBasketForUser(User.Identity.Name); BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
} }
else else
{ {
GetOrSetBasketCookieAndUserName(); GetOrSetBasketCookieAndUserName();
BasketModel = await _basketService.GetOrCreateBasketForUser(_username); BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
} }
} }

View File

@@ -1,4 +1,5 @@
@model BasketComponentViewModel @using Microsoft.eShopWeb.RazorPages.ViewComponents
@model Basket.BasketComponentViewModel
@{ @{
ViewData["Title"] = "My Basket"; ViewData["Title"] = "My Basket";

View File

@@ -1,8 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using ApplicationCore.Interfaces; using ApplicationCore.Interfaces;
using System.Linq; using System.Linq;
using System;
using ApplicationCore.Entities.OrderAggregate;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.RazorPages.Pages.Order namespace Microsoft.eShopWeb.RazorPages.Pages.Order
{ {
@@ -17,6 +19,27 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Order
public OrderViewModel OrderDetails { get; set; } = new OrderViewModel(); public OrderViewModel OrderDetails { get; set; } = new OrderViewModel();
public class OrderViewModel
{
public int OrderNumber { get; set; }
public DateTimeOffset OrderDate { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
public Address ShippingAddress { get; set; }
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
}
public class OrderItemViewModel
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal Discount { get; set; }
public int Units { get; set; }
public string PictureUrl { get; set; }
}
public async Task OnGet(int orderId) public async Task OnGet(int orderId)
{ {

View File

@@ -1,10 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using ApplicationCore.Interfaces; using ApplicationCore.Interfaces;
using ApplicationCore.Specifications; using ApplicationCore.Specifications;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System;
namespace Microsoft.eShopWeb.RazorPages.Pages.Order namespace Microsoft.eShopWeb.RazorPages.Pages.Order
{ {
@@ -17,28 +17,25 @@ namespace Microsoft.eShopWeb.RazorPages.Pages.Order
_orderRepository = orderRepository; _orderRepository = orderRepository;
} }
public List<OrderViewModel> Orders { get; set; } = new List<OrderViewModel>(); public List<OrderSummary> Orders { get; set; } = new List<OrderSummary>();
public class OrderSummary
{
public int OrderNumber { get; set; }
public DateTimeOffset OrderDate { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
}
public async Task OnGet() public async Task OnGet()
{ {
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name)); var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
Orders = orders Orders = orders
.Select(o => new OrderViewModel() .Select(o => new OrderSummary()
{ {
OrderDate = o.OrderDate, OrderDate = o.OrderDate,
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
{
Discount = 0,
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,
ProductName = oi.ItemOrdered.ProductName,
UnitPrice = oi.UnitPrice,
Units = oi.Units
}).ToList(),
OrderNumber = o.Id, OrderNumber = o.Id,
ShippingAddress = o.ShipToAddress,
Status = "Pending", Status = "Pending",
Total = o.Total() Total = o.Total()

View File

@@ -30,7 +30,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>
} }

View File

@@ -9,21 +9,18 @@ using Microsoft.eShopWeb.RazorPages.ViewModels;
namespace Microsoft.eShopWeb.RazorPages.Services namespace Microsoft.eShopWeb.RazorPages.Services
{ {
public class BasketService : IBasketService public class BasketViewModelService : IBasketViewModelService
{ {
private readonly IAsyncRepository<Basket> _basketRepository; private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IUriComposer _uriComposer; private readonly IUriComposer _uriComposer;
private readonly IAppLogger<BasketService> _logger;
private readonly IRepository<CatalogItem> _itemRepository; private readonly IRepository<CatalogItem> _itemRepository;
public BasketService(IAsyncRepository<Basket> basketRepository, public BasketViewModelService(IAsyncRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository, IRepository<CatalogItem> itemRepository,
IUriComposer uriComposer, IUriComposer uriComposer)
IAppLogger<BasketService> logger)
{ {
_basketRepository = basketRepository; _basketRepository = basketRepository;
_uriComposer = uriComposer; _uriComposer = uriComposer;
this._logger = logger;
_itemRepository = itemRepository; _itemRepository = itemRepository;
} }
@@ -32,7 +29,7 @@ namespace Microsoft.eShopWeb.RazorPages.Services
var basketSpec = new BasketWithItemsSpecification(userName); var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault(); var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if(basket == null) if (basket == null)
{ {
return await CreateBasketForUser(userName); return await CreateBasketForUser(userName);
} }
@@ -63,7 +60,7 @@ namespace Microsoft.eShopWeb.RazorPages.Services
return viewModel; return viewModel;
} }
public async Task<BasketViewModel> CreateBasketForUser(string userId) private async Task<BasketViewModel> CreateBasketForUser(string userId)
{ {
var basket = new Basket() { BuyerId = userId }; var basket = new Basket() { BuyerId = userId };
await _basketRepository.AddAsync(basket); await _basketRepository.AddAsync(basket);
@@ -75,44 +72,5 @@ namespace Microsoft.eShopWeb.RazorPages.Services
Items = new List<BasketItemViewModel>() Items = new List<BasketItemViewModel>()
}; };
} }
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
basket.AddItem(catalogItemId, price, quantity);
await _basketRepository.UpdateAsync(basket);
}
public async Task SetQuantities(int basketId, Dictionary<string,int> quantities)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
foreach (var item in basket.Items)
{
if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
{
_logger.LogWarning($"Updating quantity of item ID:{item.Id} to {quantity}.");
item.Quantity = quantity;
}
}
await _basketRepository.UpdateAsync(basket);
}
public async Task DeleteBasketAsync(int basketId)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
}
public async Task TransferBasketAsync(string anonymousId, string userName)
{
var basketSpec = new BasketWithItemsSpecification(anonymousId);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if (basket == null) return;
basket.BuyerId = userName;
await _basketRepository.UpdateAsync(basket);
}
} }
} }

View File

@@ -12,6 +12,10 @@ using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Services namespace Microsoft.eShopWeb.RazorPages.Services
{ {
/// <summary>
/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works
/// with UI-specific types (view models and SelectListItem types).
/// </summary>
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly ILogger<CatalogService> _logger; private readonly ILogger<CatalogService> _logger;

View File

@@ -3,7 +3,6 @@ using ApplicationCore.Services;
using Infrastructure.Data; using Infrastructure.Data;
using Infrastructure.Identity; using Infrastructure.Identity;
using Infrastructure.Logging; using Infrastructure.Logging;
using Infrastructure.Services;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -93,6 +92,7 @@ namespace Microsoft.eShopWeb.RazorPages
services.AddScoped<ICatalogService, CachedCatalogService>(); services.AddScoped<ICatalogService, CachedCatalogService>();
services.AddScoped<IBasketService, BasketService>(); services.AddScoped<IBasketService, BasketService>();
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
services.AddScoped<IOrderService, OrderService>(); services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<CatalogService>(); services.AddScoped<CatalogService>();

View File

@@ -1,10 +1,8 @@
using Infrastructure.Identity; using ApplicationCore.Interfaces;
using Infrastructure.Identity;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.ViewComponents namespace Microsoft.eShopWeb.RazorPages.ViewComponents
@@ -21,22 +19,26 @@ namespace Microsoft.eShopWeb.RazorPages.ViewComponents
_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); string userName = GetUsername();
vm.ItemsCount = (await _basketService.GetBasketItemCountAsync(userName));
return View(vm); return View(vm);
} }
private async Task<BasketViewModel> GetBasketViewModelAsync() public class BasketComponentViewModel
{
public int ItemsCount { get; set; }
}
private string GetUsername()
{ {
if (_signInManager.IsSignedIn(HttpContext.User)) if (_signInManager.IsSignedIn(HttpContext.User))
{ {
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name); return User.Identity.Name;
} }
string anonymousId = GetBasketIdFromCookie(); return GetBasketIdFromCookie();
if (anonymousId == null) return new BasketViewModel();
return await _basketService.GetOrCreateBasketForUser(anonymousId);
} }
private string GetBasketIdFromCookie() private string GetBasketIdFromCookie()

View File

@@ -1,7 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class BasketComponentViewModel
{
public int ItemsCount { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class OrderItemViewModel
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal Discount { get; set; }
public int Units { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
using ApplicationCore.Entities.OrderAggregate;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class OrderViewModel
{
public int OrderNumber { get; set; }
public DateTimeOffset OrderDate { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
public Address ShippingAddress { get; set; }
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
}