Shady nagy/net6 (#614)

* udated to .net6

* used the .net6 version RC2

* added editconfig.

* App core new Scoped Namespaces style.

* BlazorAdmin new Scoped Namespaces style.

* Blazor Shared new Scoped Namespaces style.

* Infra new Scoped Namespaces style.

* public api new Scoped Namespaces style.

* web new Scoped Namespaces style.

* FunctionalTests new Scoped Namespaces style.

* Integrational tests new Scoped Namespaces style.

* unit tests new Scoped Namespaces style.

* update github action.

* update github action.

* change the global.
This commit is contained in:
Shady Nagy
2021-11-06 01:55:48 +02:00
committed by GitHub
parent 64f150dc07
commit 9db2feb930
252 changed files with 6307 additions and 6413 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Microsoft.eShopWeb.ApplicationCore</RootNamespace>
</PropertyGroup>

View File

@@ -1,7 +1,6 @@
namespace Microsoft.eShopWeb
namespace Microsoft.eShopWeb;
public class CatalogSettings
{
public class CatalogSettings
{
public string CatalogBaseUrl { get; set; }
}
public string CatalogBaseUrl { get; set; }
}

View File

@@ -1,13 +1,12 @@
namespace Microsoft.eShopWeb.ApplicationCore.Constants
namespace Microsoft.eShopWeb.ApplicationCore.Constants;
public class AuthorizationConstants
{
public class AuthorizationConstants
{
public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes";
public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes";
// TODO: Don't use this in production
public const string DEFAULT_PASSWORD = "Pass@word1";
// TODO: Don't use this in production
public const string DEFAULT_PASSWORD = "Pass@word1";
// TODO: Change this to an environment variable
public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes";
}
// TODO: Change this to an environment variable
public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes";
}

View File

@@ -1,9 +1,8 @@
namespace Microsoft.eShopWeb.ApplicationCore.Entities
namespace Microsoft.eShopWeb.ApplicationCore.Entities;
// This can easily be modified to be BaseEntity<T> and public T Id to support different key types.
// Using non-generic integer types for simplicity and to ease caching logic
public abstract class BaseEntity
{
// This can easily be modified to be BaseEntity<T> and public T Id to support different key types.
// Using non-generic integer types for simplicity and to ease caching logic
public abstract class BaseEntity
{
public virtual int Id { get; protected set; }
}
public virtual int Id { get; protected set; }
}

View File

@@ -1,39 +1,38 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
public class Basket : BaseEntity, IAggregateRoot
{
public class Basket : BaseEntity, IAggregateRoot
public string BuyerId { get; private set; }
private readonly List<BasketItem> _items = new List<BasketItem>();
public IReadOnlyCollection<BasketItem> Items => _items.AsReadOnly();
public Basket(string buyerId)
{
public string BuyerId { get; private set; }
private readonly List<BasketItem> _items = new List<BasketItem>();
public IReadOnlyCollection<BasketItem> Items => _items.AsReadOnly();
BuyerId = buyerId;
}
public Basket(string buyerId)
public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)
{
if (!Items.Any(i => i.CatalogItemId == catalogItemId))
{
BuyerId = buyerId;
_items.Add(new BasketItem(catalogItemId, quantity, unitPrice));
return;
}
var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
existingItem.AddQuantity(quantity);
}
public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1)
{
if (!Items.Any(i => i.CatalogItemId == catalogItemId))
{
_items.Add(new BasketItem(catalogItemId, quantity, unitPrice));
return;
}
var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
existingItem.AddQuantity(quantity);
}
public void RemoveEmptyItems()
{
_items.RemoveAll(i => i.Quantity == 0);
}
public void RemoveEmptyItems()
{
_items.RemoveAll(i => i.Quantity == 0);
}
public void SetNewBuyerId(string buyerId)
{
BuyerId = buyerId;
}
public void SetNewBuyerId(string buyerId)
{
BuyerId = buyerId;
}
}

View File

@@ -1,34 +1,33 @@
using Ardalis.GuardClauses;
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
public class BasketItem : BaseEntity
{
public class BasketItem : BaseEntity
public decimal UnitPrice { get; private set; }
public int Quantity { get; private set; }
public int CatalogItemId { get; private set; }
public int BasketId { get; private set; }
public BasketItem(int catalogItemId, int quantity, decimal unitPrice)
{
CatalogItemId = catalogItemId;
UnitPrice = unitPrice;
SetQuantity(quantity);
}
public decimal UnitPrice { get; private set; }
public int Quantity { get; private set; }
public int CatalogItemId { get; private set; }
public int BasketId { get; private set; }
public void AddQuantity(int quantity)
{
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
public BasketItem(int catalogItemId, int quantity, decimal unitPrice)
{
CatalogItemId = catalogItemId;
UnitPrice = unitPrice;
SetQuantity(quantity);
}
Quantity += quantity;
}
public void AddQuantity(int quantity)
{
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
public void SetQuantity(int quantity)
{
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
Quantity += quantity;
}
public void SetQuantity(int quantity)
{
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
Quantity = quantity;
}
Quantity = quantity;
}
}

View File

@@ -1,26 +1,25 @@
using Ardalis.GuardClauses;
using System.Collections.Generic;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate;
public class Buyer : BaseEntity, IAggregateRoot
{
public class Buyer : BaseEntity, IAggregateRoot
public string IdentityGuid { get; private set; }
private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
private Buyer()
{
public string IdentityGuid { get; private set; }
// required by EF
}
private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
private Buyer()
{
// required by EF
}
public Buyer(string identity) : this()
{
Guard.Against.NullOrEmpty(identity, nameof(identity));
IdentityGuid = identity;
}
public Buyer(string identity) : this()
{
Guard.Against.NullOrEmpty(identity, nameof(identity));
IdentityGuid = identity;
}
}

View File

@@ -1,9 +1,8 @@
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate;
public class PaymentMethod : BaseEntity
{
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; }
}

View File

@@ -1,13 +1,12 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.ApplicationCore.Entities
namespace Microsoft.eShopWeb.ApplicationCore.Entities;
public class CatalogBrand : BaseEntity, IAggregateRoot
{
public class CatalogBrand : BaseEntity, IAggregateRoot
public string Brand { get; private set; }
public CatalogBrand(string brand)
{
public string Brand { get; private set; }
public CatalogBrand(string brand)
{
Brand = brand;
}
Brand = brand;
}
}

View File

@@ -1,66 +1,65 @@
using Ardalis.GuardClauses;
using System;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System;
namespace Microsoft.eShopWeb.ApplicationCore.Entities
namespace Microsoft.eShopWeb.ApplicationCore.Entities;
public class CatalogItem : BaseEntity, IAggregateRoot
{
public class CatalogItem : BaseEntity, IAggregateRoot
public string Name { get; private set; }
public string Description { get; private set; }
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 int CatalogBrandId { get; private set; }
public CatalogBrand CatalogBrand { get; private set; }
public CatalogItem(int catalogTypeId,
int catalogBrandId,
string description,
string name,
decimal price,
string pictureUri)
{
public string Name { get; private set; }
public string Description { get; private set; }
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 int CatalogBrandId { get; private set; }
public CatalogBrand CatalogBrand { get; private set; }
public CatalogItem(int catalogTypeId,
int catalogBrandId,
string description,
string name,
decimal price,
string pictureUri)
{
CatalogTypeId = catalogTypeId;
CatalogBrandId = catalogBrandId;
Description = description;
Name = name;
Price = price;
PictureUri = pictureUri;
}
public void UpdateDetails(string name, string description, decimal price)
{
Guard.Against.NullOrEmpty(name, nameof(name));
Guard.Against.NullOrEmpty(description, nameof(description));
Guard.Against.NegativeOrZero(price, nameof(price));
Name = name;
Description = description;
Price = price;
}
public void UpdateBrand(int catalogBrandId)
{
Guard.Against.Zero(catalogBrandId, nameof(catalogBrandId));
CatalogBrandId = catalogBrandId;
}
public void UpdateType(int catalogTypeId)
{
Guard.Against.Zero(catalogTypeId, nameof(catalogTypeId));
CatalogTypeId = catalogTypeId;
}
public void UpdatePictureUri(string pictureName)
{
if (string.IsNullOrEmpty(pictureName))
{
PictureUri = string.Empty;
return;
}
PictureUri = $"images\\products\\{pictureName}?{new DateTime().Ticks}";
}
CatalogTypeId = catalogTypeId;
CatalogBrandId = catalogBrandId;
Description = description;
Name = name;
Price = price;
PictureUri = pictureUri;
}
}
public void UpdateDetails(string name, string description, decimal price)
{
Guard.Against.NullOrEmpty(name, nameof(name));
Guard.Against.NullOrEmpty(description, nameof(description));
Guard.Against.NegativeOrZero(price, nameof(price));
Name = name;
Description = description;
Price = price;
}
public void UpdateBrand(int catalogBrandId)
{
Guard.Against.Zero(catalogBrandId, nameof(catalogBrandId));
CatalogBrandId = catalogBrandId;
}
public void UpdateType(int catalogTypeId)
{
Guard.Against.Zero(catalogTypeId, nameof(catalogTypeId));
CatalogTypeId = catalogTypeId;
}
public void UpdatePictureUri(string pictureName)
{
if (string.IsNullOrEmpty(pictureName))
{
PictureUri = string.Empty;
return;
}
PictureUri = $"images\\products\\{pictureName}?{new DateTime().Ticks}";
}
}

View File

@@ -1,13 +1,12 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.ApplicationCore.Entities
namespace Microsoft.eShopWeb.ApplicationCore.Entities;
public class CatalogType : BaseEntity, IAggregateRoot
{
public class CatalogType : BaseEntity, IAggregateRoot
public string Type { get; private set; }
public CatalogType(string type)
{
public string Type { get; private set; }
public CatalogType(string type)
{
Type = type;
}
Type = type;
}
}

View File

@@ -1,26 +1,25 @@
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
public class Address // ValueObject
{
public class Address // ValueObject
public string Street { get; private set; }
public string City { get; private set; }
public string State { get; private set; }
public string Country { get; private set; }
public string ZipCode { get; private set; }
private Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
public string Street { get; private set; }
public string City { get; private set; }
public string State { get; private set; }
public string Country { get; private set; }
public string ZipCode { get; private set; }
private Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
}

View File

@@ -1,31 +1,30 @@
using Ardalis.GuardClauses;
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
/// <summary>
/// Represents a snapshot of the item that was ordered. If catalog item details change, details of
/// the item that was part of a completed order should not change.
/// </summary>
public class CatalogItemOrdered // ValueObject
{
/// <summary>
/// Represents a snapshot of the item that was ordered. If catalog item details change, details of
/// the item that was part of a completed order should not change.
/// </summary>
public class CatalogItemOrdered // ValueObject
public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)
{
public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)
{
Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue);
Guard.Against.NullOrEmpty(productName, nameof(productName));
Guard.Against.NullOrEmpty(pictureUri, nameof(pictureUri));
Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue);
Guard.Against.NullOrEmpty(productName, nameof(productName));
Guard.Against.NullOrEmpty(pictureUri, nameof(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; }
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

@@ -1,52 +1,51 @@
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System;
using System;
using System.Collections.Generic;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
public class Order : BaseEntity, IAggregateRoot
{
public class Order : BaseEntity, IAggregateRoot
private Order()
{
private Order()
// required by EF
}
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;
_orderItems = items;
}
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 Order.AddOrderItem() which includes behavior.
private readonly List<OrderItem> _orderItems = new List<OrderItem>();
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// 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 IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
public decimal Total()
{
var total = 0m;
foreach (var item in _orderItems)
{
// required by EF
}
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;
_orderItems = items;
}
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 Order.AddOrderItem() which includes behavior.
private readonly List<OrderItem> _orderItems = new List<OrderItem>();
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// 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 IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
public decimal Total()
{
var total = 0m;
foreach (var item in _orderItems)
{
total += item.UnitPrice * item.Units;
}
return total;
total += item.UnitPrice * item.Units;
}
return total;
}
}

View File

@@ -1,21 +1,20 @@
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate
namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
public class OrderItem : BaseEntity
{
public class OrderItem : BaseEntity
public CatalogItemOrdered ItemOrdered { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
private OrderItem()
{
public CatalogItemOrdered ItemOrdered { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
// required by EF
}
private OrderItem()
{
// required by EF
}
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{
ItemOrdered = itemOrdered;
UnitPrice = unitPrice;
Units = units;
}
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{
ItemOrdered = itemOrdered;
UnitPrice = unitPrice;
Units = units;
}
}

View File

@@ -1,11 +1,10 @@
using System;
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions;
public class BasketNotFoundException : Exception
{
public class BasketNotFoundException : Exception
public BasketNotFoundException(int basketId) : base($"No basket found with id {basketId}")
{
public BasketNotFoundException(int basketId) : base($"No basket found with id {basketId}")
{
}
}
}

View File

@@ -1,14 +1,12 @@
using System;
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions
{
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions;
public class DuplicateException : Exception
public class DuplicateException : Exception
{
public DuplicateException(string message) : base(message)
{
public DuplicateException(string message) : base(message)
{
}
}
}

View File

@@ -1,24 +1,23 @@
using System;
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions;
public class EmptyBasketOnCheckoutException : Exception
{
public class EmptyBasketOnCheckoutException : Exception
public EmptyBasketOnCheckoutException()
: base($"Basket cannot have 0 items on checkout")
{
public EmptyBasketOnCheckoutException()
: base($"Basket cannot have 0 items on checkout")
{
}
}
protected EmptyBasketOnCheckoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
protected EmptyBasketOnCheckoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
public EmptyBasketOnCheckoutException(string message) : base(message)
{
}
public EmptyBasketOnCheckoutException(string message) : base(message)
{
}
public EmptyBasketOnCheckoutException(string message, Exception innerException) : base(message, innerException)
{
}
public EmptyBasketOnCheckoutException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,22 +1,21 @@
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
namespace Ardalis.GuardClauses
namespace Ardalis.GuardClauses;
public static class BasketGuards
{
public static class BasketGuards
public static void NullBasket(this IGuardClause guardClause, int basketId, Basket basket)
{
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())
throw new EmptyBasketOnCheckoutException();
}
if (basket == null)
throw new BasketNotFoundException(basketId);
}
}
public static void EmptyBasketOnCheckout(this IGuardClause guardClause, IReadOnlyCollection<BasketItem> basketItems)
{
if (!basketItems.Any())
throw new EmptyBasketOnCheckoutException();
}
}

View File

@@ -1,18 +1,17 @@
using System.Text.Json;
namespace Microsoft.eShopWeb
namespace Microsoft.eShopWeb;
public static class JsonExtensions
{
public static class JsonExtensions
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
PropertyNameCaseInsensitive = true
};
public static T FromJson<T>(this string json) =>
JsonSerializer.Deserialize<T>(json, _jsonOptions);
public static T FromJson<T>(this string json) =>
JsonSerializer.Deserialize<T>(json, _jsonOptions);
public static string ToJson<T>(this T obj) =>
JsonSerializer.Serialize<T>(obj, _jsonOptions);
}
public static string ToJson<T>(this T obj) =>
JsonSerializer.Serialize<T>(obj, _jsonOptions);
}

View File

@@ -1,5 +1,4 @@
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
{
public interface IAggregateRoot
{ }
}
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IAggregateRoot
{ }

View File

@@ -1,12 +1,11 @@
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
/// <summary>
/// This type eliminates the need to depend directly on the ASP.NET Core logging types.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IAppLogger<T>
{
/// <summary>
/// This type eliminates the need to depend directly on the ASP.NET Core logging types.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IAppLogger<T>
{
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
}
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
}

View File

@@ -1,9 +1,8 @@
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IBasketQueryService
{
public interface IBasketQueryService
{
Task<int> CountTotalBasketItems(string username);
}
Task<int> CountTotalBasketItems(string username);
}

View File

@@ -1,14 +1,13 @@
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IBasketService
{
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 DeleteBasketAsync(int basketId);
}
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 DeleteBasketAsync(int basketId);
}

View File

@@ -1,10 +1,8 @@
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
{
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}

View File

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

View File

@@ -1,8 +1,7 @@
using Ardalis.Specification;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IReadRepository<T> : IReadRepositoryBase<T> where T : class, IAggregateRoot
{
public interface IReadRepository<T> : IReadRepositoryBase<T> where T : class, IAggregateRoot
{
}
}

View File

@@ -1,8 +1,7 @@
using Ardalis.Specification;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
{
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
{
}
}

View File

@@ -1,9 +1,8 @@
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface ITokenClaimsService
{
public interface ITokenClaimsService
{
Task<string> GetTokenAsync(string userName);
}
Task<string> GetTokenAsync(string userName);
}

View File

@@ -1,7 +1,6 @@
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces;
public interface IUriComposer
{
public interface IUriComposer
{
string ComposePicUri(string uriTemplate);
}
string ComposePicUri(string uriTemplate);
}

View File

@@ -1,87 +1,86 @@
using Ardalis.GuardClauses;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.ApplicationCore.Services
namespace Microsoft.eShopWeb.ApplicationCore.Services;
public class BasketService : IBasketService
{
public class BasketService : IBasketService
private readonly IRepository<Basket> _basketRepository;
private readonly IAppLogger<BasketService> _logger;
public BasketService(IRepository<Basket> basketRepository,
IAppLogger<BasketService> logger)
{
private readonly IRepository<Basket> _basketRepository;
private readonly IAppLogger<BasketService> _logger;
_basketRepository = basketRepository;
_logger = logger;
}
public BasketService(IRepository<Basket> basketRepository,
IAppLogger<BasketService> logger)
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);
if (basket == null)
{
_basketRepository = basketRepository;
_logger = logger;
basket = new Basket(username);
await _basketRepository.AddAsync(basket);
}
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);
basket.AddItem(catalogItemId, price, quantity);
if (basket == null)
await _basketRepository.UpdateAsync(basket);
return basket;
}
public async Task DeleteBasketAsync(int basketId)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
}
public async Task<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);
foreach (var item in basket.Items)
{
if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
{
basket = new Basket(username);
await _basketRepository.AddAsync(basket);
if (_logger != null) _logger.LogInformation($"Updating quantity of item ID:{item.Id} to {quantity}.");
item.SetQuantity(quantity);
}
basket.AddItem(catalogItemId, price, quantity);
await _basketRepository.UpdateAsync(basket);
return basket;
}
basket.RemoveEmptyItems();
await _basketRepository.UpdateAsync(basket);
return basket;
}
public async Task DeleteBasketAsync(int basketId)
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);
if (anonymousBasket == null) return;
var userBasketSpec = new BasketWithItemsSpecification(userName);
var userBasket = await _basketRepository.GetBySpecAsync(userBasketSpec);
if (userBasket == null)
{
var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
userBasket = new Basket(userName);
await _basketRepository.AddAsync(userBasket);
}
public async Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities)
foreach (var item in anonymousBasket.Items)
{
Guard.Against.Null(quantities, nameof(quantities));
var basketSpec = new BasketWithItemsSpecification(basketId);
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
Guard.Against.NullBasket(basketId, basket);
foreach (var item in basket.Items)
{
if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
{
if (_logger != null) _logger.LogInformation($"Updating quantity of item ID:{item.Id} to {quantity}.");
item.SetQuantity(quantity);
}
}
basket.RemoveEmptyItems();
await _basketRepository.UpdateAsync(basket);
return basket;
}
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);
if (anonymousBasket == null) return;
var userBasketSpec = new BasketWithItemsSpecification(userName);
var userBasket = await _basketRepository.GetBySpecAsync(userBasketSpec);
if (userBasket == null)
{
userBasket = new Basket(userName);
await _basketRepository.AddAsync(userBasket);
}
foreach (var item in anonymousBasket.Items)
{
userBasket.AddItem(item.CatalogItemId, item.UnitPrice, item.Quantity);
}
await _basketRepository.UpdateAsync(userBasket);
await _basketRepository.DeleteAsync(anonymousBasket);
userBasket.AddItem(item.CatalogItemId, item.UnitPrice, item.Quantity);
}
await _basketRepository.UpdateAsync(userBasket);
await _basketRepository.DeleteAsync(anonymousBasket);
}
}

View File

@@ -1,54 +1,53 @@
using Ardalis.GuardClauses;
using System.Linq;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.ApplicationCore.Services
namespace Microsoft.eShopWeb.ApplicationCore.Services;
public class OrderService : IOrderService
{
public class OrderService : IOrderService
private readonly IRepository<Order> _orderRepository;
private readonly IUriComposer _uriComposer;
private readonly IRepository<Basket> _basketRepository;
private readonly IRepository<CatalogItem> _itemRepository;
public OrderService(IRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository,
IRepository<Order> orderRepository,
IUriComposer uriComposer)
{
private readonly IRepository<Order> _orderRepository;
private readonly IUriComposer _uriComposer;
private readonly IRepository<Basket> _basketRepository;
private readonly IRepository<CatalogItem> _itemRepository;
_orderRepository = orderRepository;
_uriComposer = uriComposer;
_basketRepository = basketRepository;
_itemRepository = itemRepository;
}
public OrderService(IRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository,
IRepository<Order> orderRepository,
IUriComposer uriComposer)
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
{
var basketSpec = new BasketWithItemsSpecification(basketId);
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
Guard.Against.NullBasket(basketId, basket);
Guard.Against.EmptyBasketOnCheckout(basket.Items);
var catalogItemsSpecification = new CatalogItemsSpecification(basket.Items.Select(item => item.CatalogItemId).ToArray());
var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);
var items = basket.Items.Select(basketItem =>
{
_orderRepository = orderRepository;
_uriComposer = uriComposer;
_basketRepository = basketRepository;
_itemRepository = itemRepository;
}
var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, _uriComposer.ComposePicUri(catalogItem.PictureUri));
var orderItem = new OrderItem(itemOrdered, basketItem.UnitPrice, basketItem.Quantity);
return orderItem;
}).ToList();
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
{
var basketSpec = new BasketWithItemsSpecification(basketId);
var basket = await _basketRepository.GetBySpecAsync(basketSpec);
var order = new Order(basket.BuyerId, shippingAddress, items);
Guard.Against.NullBasket(basketId, basket);
Guard.Against.EmptyBasketOnCheckout(basket.Items);
var catalogItemsSpecification = new CatalogItemsSpecification(basket.Items.Select(item => item.CatalogItemId).ToArray());
var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);
var items = basket.Items.Select(basketItem =>
{
var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, _uriComposer.ComposePicUri(catalogItem.PictureUri));
var orderItem = new OrderItem(itemOrdered, basketItem.UnitPrice, basketItem.Quantity);
return orderItem;
}).ToList();
var order = new Order(basket.BuyerId, shippingAddress, items);
await _orderRepository.AddAsync(order);
}
await _orderRepository.AddAsync(order);
}
}

View File

@@ -1,16 +1,15 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.ApplicationCore.Services
namespace Microsoft.eShopWeb.ApplicationCore.Services;
public class UriComposer : IUriComposer
{
public class UriComposer : IUriComposer
private readonly CatalogSettings _catalogSettings;
public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings;
public string ComposePicUri(string uriTemplate)
{
private readonly CatalogSettings _catalogSettings;
public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings;
public string ComposePicUri(string uriTemplate)
{
return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl);
}
return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl);
}
}

View File

@@ -1,22 +1,21 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
{
public sealed class BasketWithItemsSpecification : Specification<Basket>, ISingleResultSpecification
{
public BasketWithItemsSpecification(int basketId)
{
Query
.Where(b => b.Id == basketId)
.Include(b => b.Items);
}
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public BasketWithItemsSpecification(string buyerId)
{
Query
.Where(b => b.BuyerId == buyerId)
.Include(b => b.Items);
}
public sealed class BasketWithItemsSpecification : Specification<Basket>, ISingleResultSpecification
{
public BasketWithItemsSpecification(int basketId)
{
Query
.Where(b => b.Id == basketId)
.Include(b => b.Items);
}
public BasketWithItemsSpecification(string buyerId)
{
Query
.Where(b => b.BuyerId == buyerId)
.Include(b => b.Items);
}
}

View File

@@ -1,21 +1,20 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class CatalogFilterPaginatedSpecification : Specification<CatalogItem>
{
public class CatalogFilterPaginatedSpecification : Specification<CatalogItem>
public CatalogFilterPaginatedSpecification(int skip, int take, int? brandId, int? typeId)
: base()
{
public CatalogFilterPaginatedSpecification(int skip, int take, int? brandId, int? typeId)
: base()
if (take == 0)
{
if (take == 0)
{
take = int.MaxValue;
}
Query
.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
(!typeId.HasValue || i.CatalogTypeId == typeId))
.Skip(skip).Take(take);
take = int.MaxValue;
}
Query
.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
(!typeId.HasValue || i.CatalogTypeId == typeId))
.Skip(skip).Take(take);
}
}

View File

@@ -1,14 +1,13 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class CatalogFilterSpecification : Specification<CatalogItem>
{
public class CatalogFilterSpecification : Specification<CatalogItem>
public CatalogFilterSpecification(int? brandId, int? typeId)
{
public CatalogFilterSpecification(int? brandId, int? typeId)
{
Query.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
(!typeId.HasValue || i.CatalogTypeId == typeId));
}
Query.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
(!typeId.HasValue || i.CatalogTypeId == typeId));
}
}

View File

@@ -1,13 +1,12 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class CatalogItemNameSpecification : Specification<CatalogItem>
{
public class CatalogItemNameSpecification : Specification<CatalogItem>
public CatalogItemNameSpecification(string catalogItemName)
{
public CatalogItemNameSpecification(string catalogItemName)
{
Query.Where(item => catalogItemName == item.Name);
}
Query.Where(item => catalogItemName == item.Name);
}
}

View File

@@ -1,15 +1,14 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System;
using System.Linq;
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class CatalogItemsSpecification : Specification<CatalogItem>
{
public class CatalogItemsSpecification : Specification<CatalogItem>
public CatalogItemsSpecification(params int[] ids)
{
public CatalogItemsSpecification(params int[] ids)
{
Query.Where(c => ids.Contains(c.Id));
}
Query.Where(c => ids.Contains(c.Id));
}
}

View File

@@ -1,15 +1,14 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class CustomerOrdersWithItemsSpecification : Specification<Order>
{
public class CustomerOrdersWithItemsSpecification : Specification<Order>
public CustomerOrdersWithItemsSpecification(string buyerId)
{
public CustomerOrdersWithItemsSpecification(string buyerId)
{
Query.Where(o => o.BuyerId == buyerId)
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
Query.Where(o => o.BuyerId == buyerId)
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
}

View File

@@ -1,16 +1,15 @@
using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
namespace Microsoft.eShopWeb.ApplicationCore.Specifications;
public class OrderWithItemsByIdSpec : Specification<Order>, ISingleResultSpecification
{
public class OrderWithItemsByIdSpec : Specification<Order>, ISingleResultSpecification
public OrderWithItemsByIdSpec(int orderId)
{
public OrderWithItemsByIdSpec(int orderId)
{
Query
.Where(order => order.Id == orderId)
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
Query
.Where(order => order.Id == orderId)
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
}