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:
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal file
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal file
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal file
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApplicationCore.Interfaces
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
31
src/Infrastructure/Data/OrderRepository.cs
Normal file
31
src/Infrastructure/Data/OrderRepository.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/Infrastructure/Services/OrderService.cs
Normal file
40
src/Infrastructure/Services/OrderService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
96
src/Web/Controllers/OrderController.cs
Normal file
96
src/Web/Controllers/OrderController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>()));
|
||||
|
||||
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal file
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
21
src/Web/ViewModels/OrderViewModel.cs
Normal file
21
src/Web/ViewModels/OrderViewModel.cs
Normal 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>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.ViewModels
|
||||
{
|
||||
|
||||
88
src/Web/Views/Order/Detail.cshtml
Normal file
88
src/Web/Views/Order/Detail.cshtml
Normal 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>
|
||||
39
src/Web/Views/Order/Index.cshtml
Normal file
39
src/Web/Views/Order/Index.cshtml
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
50
src/Web/wwwroot/css/orders/orders.component.css
Normal file
50
src/Web/wwwroot/css/orders/orders.component.css
Normal 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; }
|
||||
|
||||
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal 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}
|
||||
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal file
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal 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 |
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user