Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Basket persistence #41

Merged
merged 2 commits into from
Aug 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/ApplicationCore/Entities/Basket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ namespace Microsoft.eShopWeb.ApplicationCore.Entities
public class Basket : BaseEntity
{
public string BuyerId { get; set; }
public List<BasketItem> Items { get; set; } = new List<BasketItem>();
private readonly List<BasketItem> _items = new List<BasketItem>();
public IEnumerable<BasketItem> Items => _items.ToList();

public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)
{
if (!Items.Any(i => i.CatalogItemId == catalogItemId))
{
Items.Add(new BasketItem()
_items.Add(new BasketItem()
{
CatalogItemId = catalogItemId,
Quantity = quantity,
Expand Down
12 changes: 10 additions & 2 deletions src/ApplicationCore/Specifications/BasketWithItemsSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ public BasketWithItemsSpecification(int basketId)
BasketId = basketId;
AddInclude(b => b.Items);
}
public BasketWithItemsSpecification(string buyerId)
{
BuyerId = buyerId;
AddInclude(b => b.Items);
}

public int BasketId { get; }
public int? BasketId { get; }
public string BuyerId { get; }

public Expression<Func<Basket, bool>> Criteria => b => b.Id == BasketId;
public Expression<Func<Basket, bool>> Criteria => b =>
(BasketId.HasValue && b.Id == BasketId.Value)
|| (BuyerId != null && b.BuyerId == BuyerId);

public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();

Expand Down
13 changes: 12 additions & 1 deletion src/Infrastructure/Data/CatalogContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.EntityFrameworkCore;
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Infrastructure.Data
{
Expand All @@ -14,13 +16,22 @@ public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }

protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Basket>(ConfigureBasket);
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
builder.Entity<CatalogType>(ConfigureCatalogType);
builder.Entity<CatalogItem>(ConfigureCatalogItem);
}

private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
{
var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items));

navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}

void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
{
builder.ToTable("Catalog");
Expand Down
7 changes: 7 additions & 0 deletions src/Web/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Web
{
public static class Constants
{
public const string BASKET_COOKIENAME = "eShop";
}
}
19 changes: 15 additions & 4 deletions src/Web/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Infrastructure.Identity;
using System;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using ApplicationCore.Interfaces;
using Web;

namespace Microsoft.eShopWeb.Controllers
{
Expand All @@ -14,20 +18,21 @@ public class AccountController : Controller
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly string _externalCookieScheme;
private readonly IBasketService _basketService;

public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions

)
IOptions<IdentityCookieOptions> identityCookieOptions,
IBasketService basketService)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_basketService = basketService;
}

// GET: /Account/SignIn
// GET: /Account/SignIn
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> SignIn(string returnUrl = null)
Expand All @@ -53,6 +58,12 @@ public async Task<IActionResult> SignIn(LoginViewModel model, string returnUrl =
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
if (!String.IsNullOrEmpty(anonymousBasketId))
{
_basketService.TransferBasket(anonymousBasketId, model.Email);
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
}
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
Expand Down
86 changes: 86 additions & 0 deletions src/Web/Controllers/BasketController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using ApplicationCore.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopWeb.ViewModels;
using Microsoft.AspNetCore.Identity;
using Infrastructure.Identity;
using System;
using Web;

namespace Microsoft.eShopWeb.Controllers
{
[Route("[controller]/[action]")]
public class BasketController : Controller
{
private readonly IBasketService _basketService;
private const string _basketSessionKey = "basketId";
private readonly IUriComposer _uriComposer;
private readonly SignInManager<ApplicationUser> _signInManager;

public BasketController(IBasketService basketService,
IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager)
{
_basketService = basketService;
_uriComposer = uriComposer;
_signInManager = signInManager;
}

[HttpGet]
public async Task<IActionResult> Index()
{
var basketModel = await GetBasketViewModelAsync();

return View(basketModel);
}

// POST: /Basket/AddToBasket
[HttpPost]
public async Task<IActionResult> AddToBasket(CatalogItemViewModel productDetails)
{
if (productDetails?.Id == null)
{
return RedirectToAction("Index", "Catalog");
}
var basketViewModel = await GetBasketViewModelAsync();

await _basketService.AddItemToBasket(basketViewModel.Id, productDetails.Id, productDetails.Price, 1);

return RedirectToAction("Index");
}

[HttpPost]
public async Task<IActionResult> Checkout()
{
var basket = await GetBasketViewModelAsync();

await _basketService.Checkout(basket.Id);

return View("Checkout");
}

private async Task<BasketViewModel> GetBasketViewModelAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name);
}
string anonymousId = GetOrSetBasketCookie();
return await _basketService.GetOrCreateBasketForUser(anonymousId);
}

private string GetOrSetBasketCookie()
{
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
{
return Request.Cookies[Constants.BASKET_COOKIENAME];
}
string anonymousId = Guid.NewGuid().ToString();
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTime.Today.AddYears(10);
Response.Cookies.Append(Constants.BASKET_COOKIENAME, anonymousId, cookieOptions);
return anonymousId;
}
}
}
72 changes: 0 additions & 72 deletions src/Web/Controllers/CartController.cs

This file was deleted.

7 changes: 2 additions & 5 deletions src/Web/Interfaces/IBasketService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ namespace ApplicationCore.Interfaces
{
public interface IBasketService
{
Task<BasketViewModel> GetBasket(int basketId);
Task<BasketViewModel> CreateBasket();
Task<BasketViewModel> CreateBasketForUser(string userId);

Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
Task TransferBasket(string anonymousId, string userName);
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);

Task Checkout(int basketId);
}
}
55 changes: 33 additions & 22 deletions src/Web/Services/BasketService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,43 @@ public BasketService(IRepository<Basket> basketRepository,
_uriComposer = uriComposer;
_itemRepository = itemRepository;
}
public async Task<BasketViewModel> GetBasket(int basketId)

public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(basketId);
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
if (basket == null)

if(basket == null)
{
return await CreateBasket();
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 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;
})
};
var item = _itemRepository.GetById(i.CatalogItemId);
itemModel.PictureUrl = _uriComposer.ComposePicUri(item.PictureUri);
itemModel.ProductName = item.Name;
return itemModel;
})
.ToList();
return viewModel;
}

public Task<BasketViewModel> CreateBasket()
{
return CreateBasketForUser(null);
}

public async Task<BasketViewModel> CreateBasketForUser(string userId)
{
var basket = new Basket() { BuyerId = userId };
Expand Down Expand Up @@ -89,5 +90,15 @@ public async Task Checkout(int basketId)

_basketRepository.Delete(basket);
}

public Task TransferBasket(string anonymousId, string userName)
{
var basketSpec = new BasketWithItemsSpecification(anonymousId);
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
if (basket == null) return Task.CompletedTask;
basket.BuyerId = userName;
_basketRepository.Update(basket);
return Task.CompletedTask;
}
}
}
Loading