Refactor to introduce nullable types and reduce compiler warnings (#801)
* Introduce nullable types to Core, Infrastructure and PublicApi projects * Refactor unit tests * Fixed failing tests * Introduce Parameter object for CatalogItem update method * Refactor CataloItemDetails param object * Refactor following PR comments
This commit is contained in:
committed by
GitHub
parent
aa6305eab1
commit
a72dd775ee
@@ -3,10 +3,12 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopWeb.ApplicationCore</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
|
||||
<PackageReference Include="Ardalis.Result" Version="4.1.0" />
|
||||
<PackageReference Include="Ardalis.Specification" Version="6.1.0" />
|
||||
<PackageReference Include="MediatR" Version="10.0.1" />
|
||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public class CatalogSettings
|
||||
{
|
||||
public string CatalogBaseUrl { get; set; }
|
||||
public string? CatalogBaseUrl { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ardalis.GuardClauses;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
@@ -25,7 +26,7 @@ public class Basket : BaseEntity, IAggregateRoot
|
||||
_items.Add(new BasketItem(catalogItemId, quantity, unitPrice));
|
||||
return;
|
||||
}
|
||||
var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
|
||||
var existingItem = Items.First(i => i.CatalogItemId == catalogItemId);
|
||||
existingItem.AddQuantity(quantity);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ public class Buyer : BaseEntity, IAggregateRoot
|
||||
|
||||
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
|
||||
|
||||
private Buyer()
|
||||
{
|
||||
// required by EF
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private Buyer() { }
|
||||
|
||||
public Buyer(string identity) : this()
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public class PaymentMethod : BaseEntity
|
||||
{
|
||||
public string Alias { get; private set; }
|
||||
public string CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe
|
||||
public string Last4 { get; private set; }
|
||||
public string? Alias { get; private set; }
|
||||
public string? CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe
|
||||
public string? Last4 { get; private set; }
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ public class CatalogItem : BaseEntity, IAggregateRoot
|
||||
public decimal Price { get; private set; }
|
||||
public string PictureUri { get; private set; }
|
||||
public int CatalogTypeId { get; private set; }
|
||||
public CatalogType CatalogType { get; private set; }
|
||||
public CatalogType? CatalogType { get; private set; }
|
||||
public int CatalogBrandId { get; private set; }
|
||||
public CatalogBrand CatalogBrand { get; private set; }
|
||||
public CatalogBrand? CatalogBrand { get; private set; }
|
||||
|
||||
public CatalogItem(int catalogTypeId,
|
||||
int catalogBrandId,
|
||||
@@ -30,15 +30,15 @@ public class CatalogItem : BaseEntity, IAggregateRoot
|
||||
PictureUri = pictureUri;
|
||||
}
|
||||
|
||||
public void UpdateDetails(string name, string description, decimal price)
|
||||
public void UpdateDetails(CatalogItemDetails details)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(name, nameof(name));
|
||||
Guard.Against.NullOrEmpty(description, nameof(description));
|
||||
Guard.Against.NegativeOrZero(price, nameof(price));
|
||||
Guard.Against.NullOrEmpty(details.Name, nameof(details.Name));
|
||||
Guard.Against.NullOrEmpty(details.Description, nameof(details.Description));
|
||||
Guard.Against.NegativeOrZero(details.Price, nameof(details.Price));
|
||||
|
||||
Name = name;
|
||||
Description = description;
|
||||
Price = price;
|
||||
Name = details.Name;
|
||||
Description = details.Description;
|
||||
Price = details.Price;
|
||||
}
|
||||
|
||||
public void UpdateBrand(int catalogBrandId)
|
||||
@@ -62,4 +62,18 @@ public class CatalogItem : BaseEntity, IAggregateRoot
|
||||
}
|
||||
PictureUri = $"images\\products\\{pictureName}?{new DateTime().Ticks}";
|
||||
}
|
||||
|
||||
public readonly record struct CatalogItemDetails
|
||||
{
|
||||
public string? Name { get; }
|
||||
public string? Description { get; }
|
||||
public decimal Price { get; }
|
||||
|
||||
public CatalogItemDetails(string? name, string? description, decimal price)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
Price = price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class Address // ValueObject
|
||||
|
||||
public string ZipCode { get; private set; }
|
||||
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private Address() { }
|
||||
|
||||
public Address(string street, string city, string state, string country, string zipcode)
|
||||
|
||||
@@ -19,10 +19,8 @@ public class CatalogItemOrdered // ValueObject
|
||||
PictureUri = pictureUri;
|
||||
}
|
||||
|
||||
private CatalogItemOrdered()
|
||||
{
|
||||
// required by EF
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private CatalogItemOrdered() {}
|
||||
|
||||
public int CatalogItemId { get; private set; }
|
||||
public string ProductName { get; private set; }
|
||||
|
||||
@@ -7,16 +7,12 @@ namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
|
||||
public class Order : BaseEntity, IAggregateRoot
|
||||
{
|
||||
private Order()
|
||||
{
|
||||
// required by EF
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private Order() {}
|
||||
|
||||
public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));
|
||||
Guard.Against.Null(shipToAddress, nameof(shipToAddress));
|
||||
Guard.Against.Null(items, nameof(items));
|
||||
|
||||
BuyerId = buyerId;
|
||||
ShipToAddress = shipToAddress;
|
||||
|
||||
@@ -6,10 +6,8 @@ public class OrderItem : BaseEntity
|
||||
public decimal UnitPrice { get; private set; }
|
||||
public int Units { get; private set; }
|
||||
|
||||
private OrderItem()
|
||||
{
|
||||
// required by EF
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private OrderItem() {}
|
||||
|
||||
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
|
||||
{
|
||||
|
||||
@@ -7,12 +7,6 @@ namespace Ardalis.GuardClauses;
|
||||
|
||||
public static class BasketGuards
|
||||
{
|
||||
public static void NullBasket(this IGuardClause guardClause, int basketId, Basket basket)
|
||||
{
|
||||
if (basket == null)
|
||||
throw new BasketNotFoundException(basketId);
|
||||
}
|
||||
|
||||
public static void EmptyBasketOnCheckout(this IGuardClause guardClause, IReadOnlyCollection<BasketItem> basketItems)
|
||||
{
|
||||
if (!basketItems.Any())
|
||||
|
||||
@@ -9,7 +9,7 @@ public static class JsonExtensions
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public static T FromJson<T>(this string json) =>
|
||||
public static T? FromJson<T>(this string json) =>
|
||||
JsonSerializer.Deserialize<T>(json, _jsonOptions);
|
||||
|
||||
public static string ToJson<T>(this T obj) =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.Result;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
@@ -8,6 +9,6 @@ public interface IBasketService
|
||||
{
|
||||
Task TransferBasketAsync(string anonymousId, string userName);
|
||||
Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1);
|
||||
Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||
Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||
Task DeleteBasketAsync(int basketId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using Ardalis.Result;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
@@ -22,7 +23,7 @@ public class BasketService : IBasketService
|
||||
public async Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(username);
|
||||
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
||||
var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
@@ -39,15 +40,15 @@ public class BasketService : IBasketService
|
||||
public async Task DeleteBasketAsync(int basketId)
|
||||
{
|
||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||
Guard.Against.Null(basket, nameof(basket));
|
||||
await _basketRepository.DeleteAsync(basket);
|
||||
}
|
||||
|
||||
public async Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities)
|
||||
public async Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> quantities)
|
||||
{
|
||||
Guard.Against.Null(quantities, nameof(quantities));
|
||||
var basketSpec = new BasketWithItemsSpecification(basketId);
|
||||
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
||||
Guard.Against.NullBasket(basketId, basket);
|
||||
var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);
|
||||
if (basket == null) return Result<Basket>.NotFound();
|
||||
|
||||
foreach (var item in basket.Items)
|
||||
{
|
||||
@@ -64,13 +65,11 @@ public class BasketService : IBasketService
|
||||
|
||||
public async Task TransferBasketAsync(string anonymousId, string userName)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(anonymousId, nameof(anonymousId));
|
||||
Guard.Against.NullOrEmpty(userName, nameof(userName));
|
||||
var anonymousBasketSpec = new BasketWithItemsSpecification(anonymousId);
|
||||
var anonymousBasket = await _basketRepository.GetBySpecAsync(anonymousBasketSpec);
|
||||
var anonymousBasket = await _basketRepository.FirstOrDefaultAsync(anonymousBasketSpec);
|
||||
if (anonymousBasket == null) return;
|
||||
var userBasketSpec = new BasketWithItemsSpecification(userName);
|
||||
var userBasket = await _basketRepository.GetBySpecAsync(userBasketSpec);
|
||||
var userBasket = await _basketRepository.FirstOrDefaultAsync(userBasketSpec);
|
||||
if (userBasket == null)
|
||||
{
|
||||
userBasket = new Basket(userName);
|
||||
|
||||
@@ -30,9 +30,9 @@ public class OrderService : IOrderService
|
||||
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(basketId);
|
||||
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
|
||||
var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);
|
||||
|
||||
Guard.Against.NullBasket(basketId, basket);
|
||||
Guard.Against.Null(basket, nameof(basket));
|
||||
Guard.Against.EmptyBasketOnCheckout(basket.Items);
|
||||
|
||||
var catalogItemsSpecification = new CatalogItemsSpecification(basket.Items.Select(item => item.CatalogItemId).ToArray());
|
||||
|
||||
Reference in New Issue
Block a user