Add To Cart - Functionality complete

This commit is contained in:
Kira 2022-09-08 19:22:48 -07:00
parent 0ef69dd498
commit 3bf434124c
16 changed files with 429 additions and 6 deletions

View File

@ -21,10 +21,10 @@ namespace ShopOnline.Api.Controllers
try try
{ {
var products = await this.productRepository.GetItems(); var products = await this.productRepository.GetItems();
//foreach (var product in products) foreach (var product in products)
//{ {
// Console.WriteLine("Product [" + product.Id + "]: " + product.Price + " / " + product.Quantity); Console.WriteLine("Product [" + product.Id + "]: " + product.Price + " / " + product.Quantity);
//} }
var productCategories = await this.productRepository.GetCategories(); var productCategories = await this.productRepository.GetCategories();
if (products == null || productCategories == null) if (products == null || productCategories == null)

View File

@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ShopOnline.Api.Extensions;
using ShopOnline.Api.Repositories.Contracts;
using ShopOnline.Models.Dtos;
namespace ShopOnline.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
private readonly IShoppingCartRepository shoppingCartRepository;
private readonly IProductRepository productRepository;
public ShoppingCartController(IShoppingCartRepository shoppingCartRepository,
IProductRepository productRepository)
{
this.shoppingCartRepository = shoppingCartRepository;
this.productRepository = productRepository;
}
[HttpGet]
[Route("{userId}/GetItems")]
public async Task<ActionResult<IEnumerable<CartItemDto>>> GetItems(int userId)
{
try
{
var cartItems = await shoppingCartRepository.GetItems(userId);
if(cartItems == null)
{
return NoContent();
}
var products = await this.productRepository.GetItems();
if(products == null)
{
throw new Exception("No products exist in the system.");
}
var cartItemsDto = cartItems.ConvertToDto(products);
return Ok(cartItemsDto);
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpGet("{id:int}")]
public async Task<ActionResult<CartItemDto>> GetItem(int id)
{
try
{
var cartItem = await this.shoppingCartRepository.GetItem(id);
if (cartItem == null)
{
return NotFound();
}
var product = await productRepository.GetItem(cartItem.ProductId);
if(product == null)
{
return NotFound();
}
var cartItemDto = cartItem.ConvertToDto(product);
return Ok(cartItemDto);
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpPost]
public async Task<ActionResult<CartItemDto>> PostItem([FromBody] CartItemToAddDto cartItemToAddDto)
{
try
{
var newCartItem = await this.shoppingCartRepository.AddItem(cartItemToAddDto);
if(newCartItem == null)
{
return NoContent();
}
var product = await productRepository.GetItem(newCartItem.ProductId);
if(product == null)
{
throw new Exception($"Something went wrong when attempting to retrieve product (productID:({cartItemToAddDto.ProductId}))");
}
var newCartItemDto = newCartItem.ConvertToDto(product);
return CreatedAtAction(nameof(GetItem), new { id = newCartItemDto.Id }, newCartItemDto);
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
}
}

View File

@ -40,5 +40,40 @@ namespace ShopOnline.Api.Extensions
}; };
} }
public static IEnumerable<CartItemDto> ConvertToDto(this IEnumerable<CartItem> cartItems, IEnumerable<Product> products)
{
return (from cartItem in cartItems
join product in products
on cartItem.ProductId equals product.Id
select new CartItemDto
{
Id = cartItem.Id,
ProductId = cartItem.ProductId,
ProductName = product.Name,
ProductDescription = product.Description,
ProductImageURL = product.ImageURL,
Price = product.Price,
CartId = cartItem.CartId,
Quantity = cartItem.Quantity,
TotalPrice = product.Price * cartItem.Quantity
}).ToList();
}
public static CartItemDto ConvertToDto(this CartItem cartItem, Product product)
{
return new CartItemDto
{
Id = cartItem.Id,
ProductId = cartItem.ProductId,
ProductName = product.Name,
ProductDescription = product.Description,
ProductImageURL = product.ImageURL,
Price = product.Price,
CartId = cartItem.CartId,
Quantity = cartItem.Quantity,
TotalPrice = product.Price * cartItem.Quantity
};
}
} }
} }

View File

@ -18,6 +18,7 @@ builder.Services.AddDbContextPool<ShopOnlineDbContext>(options =>
); );
builder.Services.AddScoped<IProductRepository, ProductRepository>(); builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();
var app = builder.Build(); var app = builder.Build();

View File

@ -0,0 +1,15 @@
using ShopOnline.Api.Entities;
using ShopOnline.Models.Dtos;
namespace ShopOnline.Api.Repositories.Contracts
{
public interface IShoppingCartRepository
{
Task<CartItem>
AddItem(CartItemToAddDto cartItemToAddDto);
Task<CartItem> UpdateQuantity(int id, CartItemQuantityUpdateDto cartItemQuantityUpdateDto);
Task<CartItem> DeleteItem(int id);
Task<CartItem> GetItem(int id);
Task<IEnumerable<CartItem>> GetItems(int userId);
}
}

View File

@ -0,0 +1,88 @@
using Microsoft.EntityFrameworkCore;
using ShopOnline.Api.Data;
using ShopOnline.Api.Entities;
using ShopOnline.Api.Repositories.Contracts;
using ShopOnline.Models.Dtos;
namespace ShopOnline.Api.Repositories
{
public class ShoppingCartRepository : IShoppingCartRepository
{
private readonly ShopOnlineDbContext shopOnlineDbContext;
public ShoppingCartRepository(ShopOnlineDbContext shopOnlineDbContext)
{
this.shopOnlineDbContext = shopOnlineDbContext;
}
private async Task<bool> CartItemExists(int cartId, int productId)
{
return await this.shopOnlineDbContext.CartItems.AnyAsync(c => c.CartId == cartId && c.ProductId == productId);
}
public async Task<CartItem> AddItem(CartItemToAddDto cartItemToAddDto)
{
if(await CartItemExists(cartItemToAddDto.CartId, cartItemToAddDto.ProductId) == false)
{
var item = await (from product in this.shopOnlineDbContext.Products
where product.Id == cartItemToAddDto.ProductId
select new CartItem
{
CartId = cartItemToAddDto.CartId,
ProductId = product.Id,
Quantity = cartItemToAddDto.Quantity,
}).SingleOrDefaultAsync();
if(item != null)
{
var result = await this.shopOnlineDbContext.CartItems.AddAsync(item);
await this.shopOnlineDbContext.SaveChangesAsync();
return result.Entity;
}
}
return null;
}
public Task<CartItem> DeleteItem(int id)
{
throw new NotImplementedException();
}
public async Task<CartItem> GetItem(int id)
{
return await (from cart in this.shopOnlineDbContext.Carts
join cartItem in this.shopOnlineDbContext.CartItems
on cart.Id equals cartItem.CartId
where cartItem.Id == id
select new CartItem
{
Id = cartItem.Id,
ProductId = cartItem.ProductId,
Quantity = cartItem.Quantity,
CartId = cartItem.CartId
}).SingleOrDefaultAsync();
}
public async Task<IEnumerable<CartItem>> GetItems(int userId)
{
return await (from cart in this.shopOnlineDbContext.Carts
join cartItem in this.shopOnlineDbContext.CartItems
on cart.Id equals cartItem.CartId
where cart.UserId == userId
select new CartItem
{
Id = cartItem.Id,
ProductId = cartItem.ProductId,
Quantity = cartItem.Quantity,
CartId = cartItem.CartId
}).ToListAsync();
}
public Task<CartItem> UpdateQuantity(int id, CartItemQuantityUpdateDto cartItemQuantityUpdateDto)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,8 @@
namespace ShopOnline.Web
{
public static class HardCoded
{
public const int UserId = 1;
public const int CartId = 1;
}
}

View File

@ -9,7 +9,7 @@
height: 64px; height: 64px;
margin: 8px; margin: 8px;
border-radius: 50%; border-radius: 50%;
background: black; background: #ffde00;
animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite;
} }

View File

@ -22,6 +22,14 @@ else
<p class="mb-4"> <p class="mb-4">
<b>@Product.Price.ToString("C")&nbsp;(@Product.Quantity in stock)</b> <b>@Product.Price.ToString("C")&nbsp;(@Product.Quantity in stock)</b>
</p> </p>
<div>
<button class="btn btn-success"
@onclick = "() => AddToCart_Click(new CartItemToAddDto {
CartId = HardCoded.CartId, ProductId = Product.Id, Quantity = 1,
})"
>Add To Cart</button>
</div>
</div> </div>
</div> </div>
} }

View File

@ -11,6 +11,10 @@ namespace ShopOnline.Web.Pages
public int Id { get; set; } public int Id { get; set; }
[Inject] [Inject]
public IProductService ProductService { get; set; } public IProductService ProductService { get; set; }
[Inject]
public IShoppingCartService ShoppingCartService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
public ProductDto Product { get; set; } public ProductDto Product { get; set; }
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
@ -25,5 +29,19 @@ namespace ShopOnline.Web.Pages
ErrorMessage = ex.Message; ErrorMessage = ex.Message;
} }
} }
protected async Task AddToCart_Click(CartItemToAddDto cartItemToAddDto)
{
try
{
var cartItemDto = await ShoppingCartService.AddItem(cartItemToAddDto);
NavigationManager.NavigateTo("/ShoppingCart");
}
catch (Exception)
{
}
}
} }
} }

View File

@ -0,0 +1,39 @@
@page "/ShoppingCart"
@inherits ShoppingCartBase
@if(ShoppingCartItems == null && ErrorMessage == null) {
<DisplaySpinner></DisplaySpinner>
}
else if(ErrorMessage != null) {
<DisplayError ErrorMessage="@ErrorMessage"></DisplayError>
}
else {
<h3 class="mb-5">Shopping Cart</h3>
<div class="row mb-5">
<div class="col-md-9">
@foreach(var item in ShoppingCartItems) {
<div class="row mb-4">
<div class="col-md-4">
<img src="@item.ProductImageURL" width="300" class="img-thumbnail" >
</div>
<div class="col-md-8">
<h5>@item.ProductName</h5>
<div class="mb-4">@item.ProductDescription</div>
<span>Price: <b>@item.Price.ToString("C")</b></span>
</div>
</div>
}
</div>
<div class="col-md-3">
<h5>Cart Summary</h5>
<div class="mt-2">
<div>Total - </div>
<a href="#" class="btn btn-success">
<span class="oi oi-credit-card"></span>&nbsp; Proceed to Checkout
</a>
</div>
</div>
</div>
}

View File

@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Components;
using ShopOnline.Models.Dtos;
using ShopOnline.Web.Services.Contracts;
namespace ShopOnline.Web.Pages
{
public class ShoppingCartBase:ComponentBase
{
[Inject]
public IShoppingCartService ShoppingCartService { get; set; }
public IEnumerable<CartItemDto> ShoppingCartItems { get; set; }
public string ErrorMessage { get; set; }
protected override async Task OnInitializedAsync()
{
try
{
ShoppingCartItems = await ShoppingCartService.GetItems(HardCoded.UserId);
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
}
}
}

View File

@ -10,5 +10,6 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7125/") }); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7125/") });
builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IShoppingCartService, ShoppingCartService>();
await builder.Build().RunAsync(); await builder.Build().RunAsync();

View File

@ -0,0 +1,10 @@
using ShopOnline.Models.Dtos;
namespace ShopOnline.Web.Services.Contracts
{
public interface IShoppingCartService
{
Task<IEnumerable<CartItemDto>> GetItems(int userId);
Task<CartItemDto> AddItem(CartItemToAddDto cartItemToAddDto);
}
}

View File

@ -0,0 +1,67 @@
using ShopOnline.Models.Dtos;
using ShopOnline.Web.Services.Contracts;
using System.Net.Http.Json;
namespace ShopOnline.Web.Services
{
public class ShoppingCartService : IShoppingCartService
{
private readonly HttpClient httpClient;
public ShoppingCartService(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<CartItemDto> AddItem(CartItemToAddDto cartItemToAddDto)
{
try
{
var response = await httpClient.PostAsJsonAsync<CartItemToAddDto>("api/ShoppingCart", cartItemToAddDto);
if (response.IsSuccessStatusCode)
{
if(response.StatusCode == System.Net.HttpStatusCode.NoContent)
{
return default(CartItemDto);
}
return await response.Content.ReadFromJsonAsync<CartItemDto>();
}
else
{
var message = await response.Content.ReadAsStringAsync();
throw new Exception($"Http status: {response.StatusCode}\n\tMessage: {message}");
}
}
catch
{
throw;
}
}
public async Task<IEnumerable<CartItemDto>> GetItems(int userId)
{
try
{
var response = await httpClient.GetAsync($"api/ShoppingCart/{userId}/GetItems");
if(response.IsSuccessStatusCode)
{
if(response.StatusCode == System.Net.HttpStatusCode.NoContent)
{
return Enumerable.Empty<CartItemDto>();
}
return await response.Content.ReadFromJsonAsync<IEnumerable<CartItemDto>>();
}
else
{
var message = await response.Content.ReadAsStringAsync();
throw new Exception($"Http status: {response.StatusCode}\n\tMessage: {message}");
}
}
catch(Exception)
{
throw;
}
}
}
}

View File

@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Web", "ShopOnlin
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Api", "ShopOnline.Api\ShopOnline.Api.csproj", "{B1FE41C0-D80F-414D-AE0F-3164789CF796}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Api", "ShopOnline.Api\ShopOnline.Api.csproj", "{B1FE41C0-D80F-414D-AE0F-3164789CF796}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShopOnline.Models", "ShopOnline.Models\ShopOnline.Models.csproj", "{C0674431-3C6E-4F51-8A5C-0523243EB33D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Models", "ShopOnline.Models\ShopOnline.Models.csproj", "{C0674431-3C6E-4F51-8A5C-0523243EB33D}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution