Order History (#49)

* working on creating and viewing orders.
* Working on wiring up listing of orders
* List orders page works as expected. Needed to support ThenInclude scenarios. Currently using strings.
This commit is contained in:
Steve Smith
2017-09-22 11:25:32 -04:00
committed by GitHub
parent db6ad75aee
commit d776ce7cb2
39 changed files with 698 additions and 60 deletions

View File

@@ -6,7 +6,17 @@
/// </summary>
public class CatalogItemOrdered // ValueObject
{
public string CatalogItemId { get; private set; }
public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)
{
CatalogItemId = catalogItemId;
ProductName = productName;
PictureUri = pictureUri;
}
private CatalogItemOrdered()
{
// required by EF
}
public int CatalogItemId { get; private set; }
public string ProductName { get; private set; }
public string PictureUri { get; private set; }
}

View File

@@ -2,20 +2,31 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Collections.Generic;
using System.Text;
namespace ApplicationCore.Entities.OrderAggregate
{
public class Order : BaseEntity, IAggregateRoot
{
private Order()
{
}
public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
{
ShipToAddress = shipToAddress;
_orderItems = items;
BuyerId = buyerId;
}
public string BuyerId { get; private set; }
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
public Address ShipToAddress { get; private set; }
// DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
private readonly List<OrderItem> _orderItems;
// but only through the method Order.AddOrderItem() which includes behavior.
private readonly List<OrderItem> _orderItems = new List<OrderItem>();
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
// Using List<>.AsReadOnly()
@@ -23,22 +34,15 @@ namespace ApplicationCore.Entities.OrderAggregate
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
}
public class OrderItem : BaseEntity
{
public CatalogItemOrdered ItemOrdered { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
protected OrderItem()
public decimal Total()
{
var total = 0m;
foreach (var item in _orderItems)
{
total += item.UnitPrice * item.Units;
}
return total;
}
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{
ItemOrdered = itemOrdered;
UnitPrice = unitPrice;
Units = units;
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace ApplicationCore.Entities.OrderAggregate
{
public class OrderItem : BaseEntity
{
public CatalogItemOrdered ItemOrdered { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
protected OrderItem()
{
}
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{
ItemOrdered = itemOrdered;
UnitPrice = unitPrice;
Units = units;
}
}
}

View File

@@ -6,6 +6,7 @@
/// <typeparam name="T"></typeparam>
public interface IAppLogger<T>
{
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
}
}

View File

@@ -0,0 +1,12 @@
using ApplicationCore.Entities.OrderAggregate;
using System.Threading.Tasks;
namespace ApplicationCore.Interfaces
{
public interface IOrderRepository : IRepository<Order>, IAsyncRepository<Order>
{
Order GetByIdWithItems(int id);
Task<Order> GetByIdWithItemsAsync(int id);
}
}

View File

@@ -0,0 +1,10 @@
using ApplicationCore.Entities.OrderAggregate;
using System.Threading.Tasks;
namespace ApplicationCore.Interfaces
{
public interface IOrderService
{
Task CreateOrderAsync(int basketId, Address shippingAddress);
}
}

View File

@@ -1,5 +1,6 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ApplicationCore.Interfaces
{

View File

@@ -8,6 +8,7 @@ namespace ApplicationCore.Interfaces
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
void AddInclude(Expression<Func<T, object>> includeExpression);
}
}

View File

@@ -1,9 +1,5 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
namespace ApplicationCore.Interfaces
namespace ApplicationCore.Interfaces
{
public interface IUriComposer
{
string ComposePicUri(string uriTemplate);

View File

@@ -3,6 +3,7 @@ using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using ApplicationCore.Entities.OrderAggregate;
namespace ApplicationCore.Specifications
{
@@ -28,6 +29,8 @@ namespace ApplicationCore.Specifications
public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
public void AddInclude(Expression<Func<Basket, object>> includeExpression)
{
Includes.Add(includeExpression);

View File

@@ -24,6 +24,8 @@ namespace ApplicationCore.Specifications
public List<Expression<Func<CatalogItem, object>>> Includes { get; } = new List<Expression<Func<CatalogItem, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
public void AddInclude(Expression<Func<CatalogItem, object>> includeExpression)
{
Includes.Add(includeExpression);

View File

@@ -0,0 +1,35 @@
using ApplicationCore.Interfaces;
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using ApplicationCore.Entities.OrderAggregate;
namespace ApplicationCore.Specifications
{
public class CustomerOrdersWithItemsSpecification : ISpecification<Order>
{
private readonly string _buyerId;
public CustomerOrdersWithItemsSpecification(string buyerId)
{
_buyerId = buyerId;
AddInclude(o => o.OrderItems);
AddInclude("OrderItems.ItemOrdered");
}
public Expression<Func<Order, bool>> Criteria => o => o.BuyerId == _buyerId;
public List<Expression<Func<Order, object>>> Includes { get; } = new List<Expression<Func<Order, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
public void AddInclude(Expression<Func<Order, object>> includeExpression)
{
Includes.Add(includeExpression);
}
public void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
}
}

View File

@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.EntityFrameworkCore.Metadata;
using ApplicationCore.Entities.OrderAggregate;
namespace Infrastructure.Data
{
@@ -20,6 +21,8 @@ namespace Infrastructure.Data
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
@@ -27,6 +30,8 @@ namespace Infrastructure.Data
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
builder.Entity<CatalogType>(ConfigureCatalogType);
builder.Entity<CatalogItem>(ConfigureCatalogItem);
builder.Entity<Order>(ConfigureOrder);
builder.Entity<OrderItem>(ConfigureOrderItem);
}
private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
@@ -36,7 +41,7 @@ namespace Infrastructure.Data
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
}
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
private void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
{
builder.ToTable("Catalog");
@@ -63,7 +68,7 @@ namespace Infrastructure.Data
.HasForeignKey(ci => ci.CatalogTypeId);
}
void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
private void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
{
builder.ToTable("CatalogBrand");
@@ -78,7 +83,7 @@ namespace Infrastructure.Data
.HasMaxLength(100);
}
void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
private void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
{
builder.ToTable("CatalogType");
@@ -92,5 +97,14 @@ namespace Infrastructure.Data
.IsRequired()
.HasMaxLength(100);
}
private void ConfigureOrder(EntityTypeBuilder<Order> builder)
{
builder.OwnsOne(o => o.ShipToAddress);
}
private void ConfigureOrderItem(EntityTypeBuilder<OrderItem> builder)
{
builder.OwnsOne(i => i.ItemOrdered);
}
}
}

View File

@@ -14,19 +14,19 @@ namespace Infrastructure.Data
/// <typeparam name="T"></typeparam>
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
{
private readonly CatalogContext _dbContext;
protected readonly CatalogContext _dbContext;
public EfRepository(CatalogContext dbContext)
{
_dbContext = dbContext;
}
public T GetById(int id)
public virtual T GetById(int id)
{
return _dbContext.Set<T>().Find(id);
}
public async Task<T> GetByIdAsync(int id)
public virtual async Task<T> GetByIdAsync(int id)
{
return await _dbContext.Set<T>().FindAsync(id);
}
@@ -45,8 +45,11 @@ namespace Infrastructure.Data
{
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
return queryableResultWithIncludes
(current, include) => current.Include(include));
var secondaryResult = spec.IncludeStrings
.Aggregate(queryableResultWithIncludes,
(current, include) => current.Include(include));
return secondaryResult
.Where(spec.Criteria)
.AsEnumerable();
}
@@ -54,8 +57,12 @@ namespace Infrastructure.Data
{
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
return await queryableResultWithIncludes
(current, include) => current.Include(include));
var secondaryResult = spec.IncludeStrings
.Aggregate(queryableResultWithIncludes,
(current, include) => current.Include(include));
return await secondaryResult
.Where(spec.Criteria)
.ToListAsync();
}

View File

@@ -0,0 +1,31 @@
using ApplicationCore.Entities.OrderAggregate;
using ApplicationCore.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace Infrastructure.Data
{
public class OrderRepository : EfRepository<Order>, IOrderRepository
{
public OrderRepository(CatalogContext dbContext) : base(dbContext)
{
}
public Order GetByIdWithItems(int id)
{
return _dbContext.Orders
.Include(o => o.OrderItems)
.Include("OrderItems.ItemOrdered")
.FirstOrDefault();
}
public Task<Order> GetByIdWithItemsAsync(int id)
{
return _dbContext.Orders
.Include(o => o.OrderItems)
.Include("OrderItems.ItemOrdered")
.FirstOrDefaultAsync();
}
}
}

View File

@@ -3,6 +3,10 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
@@ -17,7 +21,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Data\Migrations\" />
<Folder Include="Services\" />
</ItemGroup>
</Project>

View File

@@ -10,9 +10,14 @@ namespace Infrastructure.Logging
{
_logger = loggerFactory.CreateLogger<T>();
}
public void LogWarning(string message, params object[] args)
{
_logger.LogWarning(message, args);
}
public void LogInformation(string message, params object[] args)
{
_logger.LogInformation(message, args);
}
}
}

View File

@@ -0,0 +1,40 @@
using ApplicationCore.Interfaces;
using ApplicationCore.Entities.OrderAggregate;
using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
namespace Infrastructure.Services
{
public class OrderService : IOrderService
{
private readonly IAsyncRepository<Order> _orderRepository;
private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IAsyncRepository<CatalogItem> _itemRepository;
public OrderService(IAsyncRepository<Basket> basketRepository,
IAsyncRepository<CatalogItem> itemRepository,
IAsyncRepository<Order> orderRepository)
{
_orderRepository = orderRepository;
_basketRepository = basketRepository;
_itemRepository = itemRepository;
}
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
var items = new List<OrderItem>();
foreach (var item in basket.Items)
{
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri);
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);
items.Add(orderItem);
}
var order = new Order(basket.BuyerId, shippingAddress, items);
await _orderRepository.AddAsync(order);
}
}
}

View File

@@ -36,6 +36,12 @@ namespace Microsoft.eShopWeb.Controllers
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
if (!String.IsNullOrEmpty(returnUrl) &&
returnUrl.ToLower().Contains("checkout"))
{
ViewData["ReturnUrl"] = "/Basket/Index";
}
return View();
}

View File

@@ -8,6 +8,8 @@ using Infrastructure.Identity;
using System;
using Web;
using System.Collections.Generic;
using ApplicationCore.Entities.OrderAggregate;
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.eShopWeb.Controllers
{
@@ -19,8 +21,10 @@ namespace Microsoft.eShopWeb.Controllers
private readonly IUriComposer _uriComposer;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<BasketController> _logger;
private readonly IOrderService _orderService;
public BasketController(IBasketService basketService,
IOrderService orderService,
IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager,
IAppLogger<BasketController> logger)
@@ -29,6 +33,7 @@ namespace Microsoft.eShopWeb.Controllers
_uriComposer = uriComposer;
_signInManager = signInManager;
_logger = logger;
_orderService = orderService;
}
[HttpGet]
@@ -65,19 +70,15 @@ namespace Microsoft.eShopWeb.Controllers
}
[HttpPost]
public async Task<IActionResult> Checkout(List<BasketItemViewModel> model)
[Authorize]
public async Task<IActionResult> Checkout(Dictionary<string, int> items)
{
// TODO: Get model binding working with collection of items
var basket = await GetBasketViewModelAsync();
//await _basketService.SetQuantities(basket.Id, quantities);
var basketViewModel = await GetBasketViewModelAsync();
await _basketService.SetQuantities(basketViewModel.Id, items);
foreach (var item in basket.Items)
{
_logger.LogWarning($"Id: {item.Id}; Qty: {item.Quantity}");
}
// redirect to OrdersController
await _orderService.CreateOrderAsync(basketViewModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
await _basketService.Checkout(basket.Id);
await _basketService.DeleteBasketAsync(basketViewModel.Id);
return View("Checkout");
}

View File

@@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.eShopWeb.ViewModels;
using System;
using ApplicationCore.Entities.OrderAggregate;
using ApplicationCore.Interfaces;
using System.Linq;
using ApplicationCore.Specifications;
namespace Microsoft.eShopWeb.Controllers
{
[Authorize]
[Route("[controller]/[action]")]
public class OrderController : Controller
{
private readonly IOrderRepository _orderRepository;
public OrderController(IOrderRepository orderRepository) {
_orderRepository = orderRepository;
}
public async Task<IActionResult> Index()
{
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
var viewModel = orders
.Select(o => new OrderViewModel()
{
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,
ShippingAddress = o.ShipToAddress,
Status = "Pending",
Total = o.Total()
});
return View(viewModel);
}
[HttpGet("{orderId}")]
public async Task<IActionResult> Detail(int orderId)
{
var order = await _orderRepository.GetByIdWithItemsAsync(orderId);
var viewModel = new OrderViewModel()
{
OrderDate = order.OrderDate,
OrderItems = order.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 = order.Id,
ShippingAddress = order.ShipToAddress,
Status = "Pending",
Total = order.Total()
};
return View(viewModel);
}
private OrderViewModel GetOrder()
{
var order = new OrderViewModel()
{
OrderDate = DateTimeOffset.Now.AddDays(-1),
OrderNumber = 12354,
Status = "Submitted",
Total = 123.45m,
ShippingAddress = new Address("123 Main St.", "Kent", "OH", "United States", "44240")
};
order.OrderItems.Add(new OrderItemViewModel()
{
ProductId = 1,
PictureUrl = "",
ProductName = "Something",
UnitPrice = 5.05m,
Units = 2
});
return order;
}
}
}

View File

@@ -1,5 +1,4 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ViewModels;
using Microsoft.eShopWeb.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -11,6 +10,6 @@ namespace ApplicationCore.Interfaces
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 Checkout(int basketId);
Task DeleteBasketAsync(int basketId);
}
}

View File

@@ -98,12 +98,10 @@ namespace Web.Services
await _basketRepository.UpdateAsync(basket);
}
public async Task Checkout(int basketId)
public async Task DeleteBasketAsync(int basketId)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
// TODO: Actually Process the order
await _basketRepository.DeleteAsync(basket);
}

View File

@@ -1,8 +1,10 @@
using ApplicationCore.Interfaces;
using ApplicationCore.Entities.OrderAggregate;
using ApplicationCore.Interfaces;
using ApplicationCore.Services;
using Infrastructure.Data;
using Infrastructure.Identity;
using Infrastructure.Logging;
using Infrastructure.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@@ -12,6 +14,7 @@ using Microsoft.eShopWeb.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Text;
using Web.Services;
@@ -53,12 +56,22 @@ namespace Microsoft.eShopWeb
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.LoginPath = "/Account/Signin";
options.LogoutPath = "/Account/Signout";
});
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
services.AddMemoryCache();
services.AddScoped<ICatalogService, CachedCatalogService>();
services.AddScoped<IBasketService, BasketService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<CatalogService>();
services.Configure<CatalogSettings>(Configuration);
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopWeb.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

@@ -0,0 +1,21 @@
using ApplicationCore.Entities.OrderAggregate;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.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,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopWeb.ViewModels
{

View File

@@ -0,0 +1,88 @@
@using Microsoft.eShopWeb.ViewModels
@model OrderViewModel
@{
ViewData["Title"] = "My Order History";
}
@{
ViewData["Title"] = "Order Detail";
}
<div class="esh-orders_detail">
<div class="container">
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-3">Order number</section>
<section class="esh-orders_detail-title col-xs-3">Date</section>
<section class="esh-orders_detail-title col-xs-3">Total</section>
<section class="esh-orders_detail-title col-xs-3">Status</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-3">@Model.OrderNumber</section>
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDate</section>
<section class="esh-orders_detail-item col-xs-3">$@Model.Total</section>
<section class="esh-orders_detail-title col-xs-3">@Model.Status</section>
</article>
</section>
@*<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Description</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section>
</article>
</section>*@
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Shipping Address</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Street</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.City</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Country</section>
</article>
</section>
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section>
</article>
@for (int i = 0; i < Model.OrderItems.Count; i++)
{
var item = Model.OrderItems[i];
<article class="esh-orders_detail-items esh-orders_detail-items--border row">
<section class="esh-orders_detail-item col-md-4 hidden-md-down">
<img class="esh-orders_detail-image" src="@item.PictureUrl">
</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
</article>
}
</section>
<section class="esh-orders_detail-section esh-orders_detail-section--right">
<article class="esh-orders_detail-titles esh-basket-titles--clean row">
<section class="esh-orders_detail-title col-xs-9"></section>
<section class="esh-orders_detail-title col-xs-2">TOTAL</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-9"></section>
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.Total</section>
</article>
</section>
</div>
</div>

View File

@@ -0,0 +1,39 @@
@using Microsoft.eShopWeb.ViewModels
@model IEnumerable<OrderViewModel>
@{
ViewData["Title"] = "My Order History";
}
<div class="esh-orders">
<div class="container">
<h1>@ViewData["Title"]</h1>
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
</article>
@if (Model != null && Model.Any())
{
@foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-xs-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article>
}
}
</div>
</div>

View File

@@ -6,7 +6,7 @@
<a class="esh-basketstatus "
asp-area=""
asp-controller="Cart"
asp-controller="Basket"
asp-action="Index">
<div class="esh-basketstatus-image">
<img src="~/images/cart.png" />

View File

@@ -20,6 +20,7 @@
<link rel="stylesheet" href="~/css/catalog/pager.css" />
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
</head>
<body>
<header class="navbar navbar-light navbar-static-top">
@@ -46,7 +47,6 @@
<article class="row">
<section class="col-sm-6">
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
</section>
<section class="col-sm-6">

View File

@@ -1,8 +1,8 @@
[
//{
// "outputFile": "wwwroot/css/orders/orders.component.css",
// "inputFile": "wwwroot/css/orders/orders.component.scss"
//},
{
"outputFile": "wwwroot/css/orders/orders.component.css",
"inputFile": "wwwroot/css/orders/orders.component.scss"
},
//{
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css",
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss"

View File

@@ -0,0 +1,50 @@
.esh-orders {
min-height: 80vh;
overflow-x: hidden; }
.esh-orders-header {
background-color: #00A69C;
height: 4rem; }
.esh-orders-back {
color: rgba(255, 255, 255, 0.4);
line-height: 4rem;
text-decoration: none;
text-transform: uppercase;
transition: color 0.35s; }
.esh-orders-back:hover {
color: #FFFFFF;
transition: color 0.35s; }
.esh-orders-titles {
padding-bottom: 1rem;
padding-top: 2rem; }
.esh-orders-title {
text-transform: uppercase; }
.esh-orders-items {
height: 2rem;
line-height: 2rem;
position: relative; }
.esh-orders-items:nth-of-type(2n + 1):before {
background-color: #EEEEFF;
content: '';
height: 100%;
left: 0;
margin-left: -100vw;
position: absolute;
top: 0;
width: 200vw;
z-index: -1; }
.esh-orders-item {
font-weight: 300; }
.esh-orders-item--hover {
opacity: 0;
pointer-events: none; }
.esh-orders-items:hover .esh-orders-item--hover {
opacity: 1;
pointer-events: all; }
.esh-orders-link {
color: #83D01B;
text-decoration: none;
transition: color 0.35s; }
.esh-orders-link:hover {
color: #75b918;
transition: color 0.35s; }

View File

@@ -0,0 +1 @@
.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}

View File

@@ -0,0 +1,91 @@
@import '../variables';
.esh-orders {
min-height: 80vh;
overflow-x: hidden;
$header-height: 4rem;
&-header
{
background-color: #00A69C;
height: $header-height;
}
&-back {
color: rgba($color-foreground-brighter, .4);
line-height: $header-height;
text-decoration: none;
text-transform: uppercase;
transition: color $animation-speed-default;
&:hover
{
color: $color-foreground-brighter;
transition: color $animation-speed-default;
}
}
&-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
&-title {
text-transform: uppercase;
}
&-items {
$height: 2rem;
height: $height;
line-height: $height;
position: relative;
&:nth-of-type(2n + 1)
{
&:before
{
background-color: $color-background-bright;
content: '';
height: 100%;
left: 0;
margin-left: -100vw;
position: absolute;
top: 0;
width: 200vw;
z-index: -1;
}
}
}
&-item {
font-weight: $font-weight-semilight;
&--hover
{
opacity: 0;
pointer-events: none;
}
}
&-items:hover &-item--hover {
opacity: 1;
pointer-events: all;
}
&-link {
color: $color-secondary;
text-decoration: none;
transition: color $animation-speed-default;
&:hover
{
color: $color-secondary-dark;
transition: color $animation-speed-default;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,9 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="xunit" Version="2.2.0" />

View File

@@ -4,6 +4,11 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="xunit" Version="2.2.0" />

View File

@@ -4,6 +4,10 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />